diff --git a/.agents/skills/prcheckloop/SKILL.md b/.agents/skills/prcheckloop/SKILL.md new file mode 100644 index 00000000000..70d9e19af66 --- /dev/null +++ b/.agents/skills/prcheckloop/SKILL.md @@ -0,0 +1,209 @@ +--- +name: prcheckloop +description: > + Iteratively gets a GitHub pull request's checks green. Detects the PR for the + current branch or uses a provided PR number, waits for every check on the + latest head SHA to appear and finish, investigates failing checks, fixes + actionable code or test issues, pushes, and repeats. Escalates with a precise + blocker when failures are external, flaky, or not safely fixable. Use when a + PR still has unsuccessful checks after review fixes, including after greploop. +--- + +# PRCheckloop + +Get a GitHub PR to a fully green check state, or exit with a concrete blocker. + +## Scope + +- GitHub PRs only. If the repo is GitLab, stop and use `check-pr`. +- Focus on checks for the latest PR head SHA, not old commits. +- Focus on CI/status checks, not review comments or PR template cleanup. +- If the user also wants review-comment cleanup, pair this with `check-pr`. + +## Inputs + +- **PR number** (optional): If not provided, detect the PR for the current branch. +- **Max iterations**: default `5`. + +## Workflow + +### 1. Identify the PR + +If no PR number is provided, detect it from the current branch: + +```bash +gh pr view --json number,headRefName,headRefOid,url,isDraft +``` + +If needed, switch to the PR branch before making changes. + +Stop early if: + +- `gh` is not authenticated +- there is no PR for the branch +- the repo is not hosted on GitHub + +### 2. Track the latest head SHA + +Always work against the current PR head SHA: + +```bash +PR_JSON=$(gh pr view "$PR_NUMBER" --json number,headRefName,headRefOid,url) +HEAD_SHA=$(echo "$PR_JSON" | jq -r .headRefOid) +PR_URL=$(echo "$PR_JSON" | jq -r .url) +``` + +Ignore failing checks from older SHAs. After every push, refresh `HEAD_SHA` and +restart the inspection loop. + +### 3. Inventory checks for that SHA + +Fetch both GitHub check runs and legacy commit status contexts: + +```bash +gh api "repos/{owner}/{repo}/commits/$HEAD_SHA/check-runs?per_page=100" +gh api "repos/{owner}/{repo}/commits/$HEAD_SHA/status" +``` + +For a compact PR-level view, this GraphQL payload is useful: + +```bash +gh api graphql -f query=' +query($owner:String!, $repo:String!, $pr:Int!) { + repository(owner:$owner, name:$repo) { + pullRequest(number:$pr) { + headRefOid + url + statusCheckRollup { + contexts(first:100) { + nodes { + __typename + ... on CheckRun { name status conclusion detailsUrl workflowName } + ... on StatusContext { context state targetUrl description } + } + } + } + } + } +}' -F owner=OWNER -F repo=REPO -F pr="$PR_NUMBER" +``` + +### 4. Wait for checks to actually run + +After a new push, checks can take a moment to appear. Poll every 15-30 seconds +until one of these is true: + +- checks have appeared and every item is in a terminal state +- checks have appeared and at least one failed +- no checks appear after a reasonable wait, usually 2 minutes + +Treat these as terminal success states: + +- check runs: `SUCCESS`, `NEUTRAL`, `SKIPPED` +- status contexts: `SUCCESS` + +Treat these as pending: + +- check runs: `QUEUED`, `PENDING`, `WAITING`, `REQUESTED`, `IN_PROGRESS` +- status contexts: `PENDING` + +Treat these as failures: + +- check runs: `FAILURE`, `TIMED_OUT`, `CANCELLED`, `ACTION_REQUIRED`, `STARTUP_FAILURE`, `STALE` +- status contexts: `FAILURE`, `ERROR` + +If no checks appear for the latest SHA, inspect `.github/workflows/`, workflow +path filters, and branch protection expectations. If the missing check cannot be +caused or fixed from the repo, escalate. + +### 5. Investigate failing checks + +For GitHub Actions failures, inspect runs and failed logs for the current SHA: + +```bash +gh run list --commit "$HEAD_SHA" --json databaseId,workflowName,status,conclusion,url,headSha +gh run view --json databaseId,name,workflowName,status,conclusion,jobs,url,headSha +gh run view --log-failed +``` + +For each failing check, classify it: + +| Failure type | Action | +|---|---| +| Code/test regression | Reproduce locally, fix, and verify | +| Lint/type/build mismatch | Run the matching local command from the workflow and fix it | +| Flake or transient infra issue | Rerun once if evidence supports flakiness | +| External service/status app failure | Escalate with the details URL and owner guess | +| Missing secret/permission/branch protection issue | Escalate immediately | + +Only rerun a failed job once without code changes. Do not loop on reruns. + +### 6. Fix actionable failures + +If the failure is actionable from the checked-out code: + +1. Read the workflow or failing command to identify the real gate. +2. Reproduce locally where reasonable. +3. Make the smallest correct fix. +4. Run focused verification first, then broader verification if needed. +5. Commit in a logical commit. +6. Push before re-checking the PR. + +Do not stop at a local fix. The loop is only complete when the remote PR checks +for the new head SHA are green. + +### 7. Push and repeat + +After each fix: + +```bash +git push +sleep 5 +``` + +Then refresh the PR metadata, get the new `HEAD_SHA`, and restart from Step 3. + +Exit the loop only when: + +- all checks for the latest head SHA are green, or +- a blocker remains after reasonable repair effort, or +- the max iteration count is reached + +### 8. Escalate blockers precisely + +If you cannot get the PR green, report: + +- PR URL +- latest head SHA +- exact failing or missing check names +- details URLs +- what you already tried +- why it is blocked +- who should likely unblock it +- the next concrete action + +Good blocker examples: + +- external status app outage +- missing GitHub secret or permission +- required check name mismatch in branch protection +- persistent flake after one rerun +- failure needs credentials or infrastructure access you do not have + +## Output + +When the skill completes, report: + +- PR URL and branch +- final head SHA +- green/pending/failing check summary +- fixes made and verification run +- whether changes were pushed +- blocker summary if not fully green + +## Notes + +- This skill is intentionally narrower than `check-pr`: it is a repair loop for + PR checks. +- This skill complements `greploop`: Greptile can be perfect while CI is still + red. diff --git a/.env.example b/.env.example index b1cab5a5f21..8ec9d57e964 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,4 @@ DATABASE_URL=postgres://paperclip:paperclip@localhost:5432/paperclip PORT=3100 SERVE_UI=false +BETTER_AUTH_SECRET=paperclip-dev-secret diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b22c2d098d9..0b8981e1516 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -8,3 +8,10 @@ scripts/rollback-latest.sh @cryppadotta @devinfoley doc/RELEASING.md @cryppadotta @devinfoley doc/PUBLISHING.md @cryppadotta @devinfoley doc/RELEASE-AUTOMATION-SETUP.md @cryppadotta @devinfoley + +# Package files — dependency changes require review +# package.json matches recursively at all depths (covers root + all workspaces) +package.json @cryppadotta @devinfoley +pnpm-lock.yaml @cryppadotta @devinfoley +pnpm-workspace.yaml @cryppadotta @devinfoley +.npmrc @cryppadotta @devinfoley diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 75c5a361884..02e836227c9 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -38,9 +38,25 @@ - +## Model Used + + + +- + ## Checklist - [ ] I have included a thinking path that traces from project context to this change +- [ ] I have specified the model used (with version and capability details) - [ ] I have run tests locally and they pass - [ ] I have added or updated tests where applicable - [ ] If this change affects the UI, I have included before/after screenshots diff --git a/.github/workflows/refresh-lockfile.yml b/.github/workflows/refresh-lockfile.yml index 7d2c9e45efb..a52d6f002f8 100644 --- a/.github/workflows/refresh-lockfile.yml +++ b/.github/workflows/refresh-lockfile.yml @@ -54,10 +54,11 @@ jobs: id: upsert-pr env: GH_TOKEN: ${{ github.token }} + REPO_OWNER: ${{ github.repository_owner }} run: | if git diff --quiet -- pnpm-lock.yaml; then echo "Lockfile unchanged, nothing to do." - echo "pr_created=false" >> "$GITHUB_OUTPUT" + echo "pr_url=" >> "$GITHUB_OUTPUT" exit 0 fi @@ -70,28 +71,26 @@ jobs: git commit -m "chore(lockfile): refresh pnpm-lock.yaml" git push --force origin "$BRANCH" - # Create PR if one doesn't already exist - existing=$(gh pr list --head "$BRANCH" --json number --jq '.[0].number') - if [ -z "$existing" ]; then - gh pr create \ + # Only reuse an open PR from this repository owner, not a fork with the same branch name. + pr_url="$( + gh pr list --state open --head "$BRANCH" --json url,headRepositoryOwner \ + --jq ".[] | select(.headRepositoryOwner.login == \"$REPO_OWNER\") | .url" | + head -n 1 + )" + if [ -z "$pr_url" ]; then + pr_url="$(gh pr create \ --head "$BRANCH" \ --title "chore(lockfile): refresh pnpm-lock.yaml" \ - --body "Auto-generated lockfile refresh after dependencies changed on master. This PR only updates pnpm-lock.yaml." - echo "Created new PR." + --body "Auto-generated lockfile refresh after dependencies changed on master. This PR only updates pnpm-lock.yaml.")" + echo "Created new PR: $pr_url" else - echo "PR #$existing already exists, branch updated via force push." + echo "PR already exists: $pr_url" fi - echo "pr_created=true" >> "$GITHUB_OUTPUT" + echo "pr_url=$pr_url" >> "$GITHUB_OUTPUT" - name: Enable auto-merge for lockfile PR - if: steps.upsert-pr.outputs.pr_created == 'true' + if: steps.upsert-pr.outputs.pr_url != '' env: GH_TOKEN: ${{ github.token }} run: | - pr_url="$(gh pr list --head chore/refresh-lockfile --json url --jq '.[0].url')" - if [ -z "$pr_url" ]; then - echo "Error: lockfile PR was not found." >&2 - exit 1 - fi - - gh pr merge --auto --squash --delete-branch "$pr_url" + gh pr merge --auto --squash --delete-branch "${{ steps.upsert-pr.outputs.pr_url }}" diff --git a/.gitignore b/.gitignore index b635827af75..cdeb446edc1 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ server/src/**/*.js.map server/src/**/*.d.ts server/src/**/*.d.ts.map tmp/ +feedback-export-* # Editor / tool temp files *.tmp diff --git a/AGENTS.md b/AGENTS.md index bdfa3e5d4d1..524c573abe0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -81,8 +81,8 @@ If you change schema/API behavior, update all impacted layers: 4. Do not replace strategic docs wholesale unless asked. Prefer additive updates. Keep `doc/SPEC.md` and `doc/SPEC-implementation.md` aligned. -5. Keep plan docs dated and centralized. -New plan documents belong in `doc/plans/` and should use `YYYY-MM-DD-slug.md` filenames. +5. Keep repo plan docs dated and centralized. +When you are creating a plan file in the repository itself, new plan documents belong in `doc/plans/` and should use `YYYY-MM-DD-slug.md` filenames. This does not replace Paperclip issue planning: if a Paperclip issue asks for a plan, update the issue `plan` document per the `paperclip` skill instead of creating a repo markdown file. ## 6. Database Change Workflow @@ -138,7 +138,18 @@ When adding endpoints: - Use company selection context for company-scoped pages - Surface failures clearly; do not silently ignore API errors -## 10. Definition of Done +## 10. Pull Request Requirements + +When creating a pull request (via `gh pr create` or any other method), you **must** read and fill in every section of [`.github/PULL_REQUEST_TEMPLATE.md`](.github/PULL_REQUEST_TEMPLATE.md). Do not craft ad-hoc PR bodies — use the template as the structure for your PR description. Required sections: + +- **Thinking Path** — trace reasoning from project context to this change (see `CONTRIBUTING.md` for examples) +- **What Changed** — bullet list of concrete changes +- **Verification** — how a reviewer can confirm it works +- **Risks** — what could go wrong +- **Model Used** — the AI model that produced or assisted with the change (provider, exact model ID, context window, capabilities). Write "None — human-authored" if no AI was used. +- **Checklist** — all items checked + +## 11. Definition of Done A change is done when all are true: @@ -146,3 +157,45 @@ A change is done when all are true: 2. Typecheck, tests, and build pass 3. Contracts are synced across db/shared/server/ui 4. Docs updated when behavior or commands change +5. PR description follows the [PR template](.github/PULL_REQUEST_TEMPLATE.md) with all sections filled in (including Model Used) + +## 11. Fork-Specific: HenkDz/paperclip + +This is a fork of `paperclipai/paperclip` with QoL patches and an **external-only** Hermes adapter story on branch `feat/externalize-hermes-adapter` ([tree](https://github.com/HenkDz/paperclip/tree/feat/externalize-hermes-adapter)). + +### Branch Strategy + +- `feat/externalize-hermes-adapter` → core has **no** `hermes-paperclip-adapter` dependency and **no** built-in `hermes_local` registration. Install Hermes via the Adapter Plugin manager (`@henkey/hermes-paperclip-adapter` or a `file:` path). +- Older fork branches may still document built-in Hermes; treat this file as authoritative for the externalize branch. + +### Hermes (plugin only) + +- Register through **Board → Adapter manager** (same as Droid). Type remains `hermes_local` once the package is loaded. +- UI uses generic **config-schema** + **ui-parser.js** from the package — no Hermes imports in `server/` or `ui/` source. +- Optional: `file:` entry in `~/.paperclip/adapter-plugins.json` for local dev of the adapter repo. + +### Local Dev + +- Fork runs on port 3101+ (auto-detects if 3100 is taken by upstream instance) +- `npx vite build` hangs on NTFS — use `node node_modules/vite/bin/vite.js build` instead +- Server startup from NTFS takes 30-60s — don't assume failure immediately +- Kill ALL paperclip processes before starting: `pkill -f "paperclip"; pkill -f "tsx.*index.ts"` +- Vite cache survives `rm -rf dist` — delete both: `rm -rf ui/dist ui/node_modules/.vite` + +### Fork QoL Patches (not in upstream) + +These are local modifications in the fork's UI. If re-copying source, these must be re-applied: + +1. **stderr_group** — amber accordion for MCP init noise in `RunTranscriptView.tsx` +2. **tool_group** — accordion for consecutive non-terminal tools (write, read, search, browser) +3. **Dashboard excerpt** — `LatestRunCard` strips markdown, shows first 3 lines/280 chars + +### Plugin System + +PR #2218 (`feat/external-adapter-phase1`) adds external adapter support. See root `AGENTS.md` for full details. + +- Adapters can be loaded as external plugins via `~/.paperclip/adapter-plugins.json` +- The plugin-loader should have ZERO hardcoded adapter imports — pure dynamic loading +- `createServerAdapter()` must include ALL optional fields (especially `detectModel`) +- Built-in UI adapters can shadow external plugin parsers — remove built-in when fully externalizing +- Reference external adapters: Hermes (`@henkey/hermes-paperclip-adapter` or `file:`) and Droid (npm) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1eba95e3eb1..39b8c5c289b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,8 +11,9 @@ We really appreciate both small fixes and thoughtful larger changes. - Pick **one** clear thing to fix/improve - Touch the **smallest possible number of files** - Make sure the change is very targeted and easy to review -- All automated checks pass (including Greptile comments) -- No new lint/test failures +- All tests pass and CI is green +- Greptile score is 5/5 with all comments addressed +- Use the [PR template](.github/PULL_REQUEST_TEMPLATE.md) These almost always get merged quickly when they're clean. @@ -26,11 +27,30 @@ These almost always get merged quickly when they're clean. - Before / After screenshots (or short video if UI/behavior change) - Clear description of what & why - Proof it works (manual testing notes) - - All tests passing - - All Greptile + other PR comments addressed + - All tests passing and CI green + - Greptile score 5/5 with all comments addressed + - [PR template](.github/PULL_REQUEST_TEMPLATE.md) fully filled out PRs that follow this path are **much** more likely to be accepted, even when they're large. +## PR Requirements (all PRs) + +### Use the PR Template + +Every pull request **must** follow the PR template at [`.github/PULL_REQUEST_TEMPLATE.md`](.github/PULL_REQUEST_TEMPLATE.md). If you create a PR via the GitHub API or other tooling that bypasses the template, copy its contents into your PR description manually. The template includes required sections: Thinking Path, What Changed, Verification, Risks, Model Used, and a Checklist. + +### Model Used (Required) + +Every PR must include a **Model Used** section specifying which AI model produced or assisted with the change. Include the provider, exact model ID/version, context window size, and any relevant capability details (e.g., reasoning mode, tool use). If no AI was used, write "None — human-authored". This applies to all contributors — human and AI alike. + +### Tests Must Pass + +All tests must pass before a PR can be merged. Run them locally first and verify CI is green after pushing. + +### Greptile Review + +We use [Greptile](https://greptile.com) for automated code review. Your PR must achieve a **5/5 Greptile score** with **all Greptile comments addressed** before it can be merged. If Greptile leaves comments, fix or respond to each one and request a re-review. + ## General Rules (both paths) - Write clear commit messages @@ -41,7 +61,7 @@ PRs that follow this path are **much** more likely to be accepted, even when the ## Writing a Good PR message -Please include a "thinking path" at the top of your PR message that explains from the top of the project down to what you fixed. E.g.: +Your PR description must follow the [PR template](.github/PULL_REQUEST_TEMPLATE.md). All sections are required. The "thinking path" at the top explains from the top of the project down to what you fixed. E.g.: ### Thinking Path Example 1: diff --git a/Dockerfile b/Dockerfile index 8b65b0e2108..36d5acab2da 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,23 @@ FROM node:lts-trixie-slim AS base +ARG USER_UID=1000 +ARG USER_GID=1000 RUN apt-get update \ - && apt-get install -y --no-install-recommends ca-certificates curl git \ - && rm -rf /var/lib/apt/lists/* -RUN corepack enable + && apt-get install -y --no-install-recommends ca-certificates gosu curl git wget ripgrep python3 \ + && mkdir -p -m 755 /etc/apt/keyrings \ + && wget -nv -O/etc/apt/keyrings/githubcli-archive-keyring.gpg https://cli.github.com/packages/githubcli-archive-keyring.gpg \ + && echo "20e0125d6f6e077a9ad46f03371bc26d90b04939fb95170f5a1905099cc6bcc0 /etc/apt/keyrings/githubcli-archive-keyring.gpg" | sha256sum -c - \ + && chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \ + && mkdir -p -m 755 /etc/apt/sources.list.d \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" > /etc/apt/sources.list.d/github-cli.list \ + && apt-get update \ + && apt-get install -y --no-install-recommends gh \ + && rm -rf /var/lib/apt/lists/* \ + && corepack enable + +# Modify the existing node user/group to have the specified UID/GID to match host user +RUN usermod -u $USER_UID --non-unique node \ + && groupmod -g $USER_GID --non-unique node \ + && usermod -g $USER_GID -d /paperclip node FROM base AS deps WORKDIR /app @@ -13,6 +28,7 @@ COPY ui/package.json ui/ COPY packages/shared/package.json packages/shared/ COPY packages/db/package.json packages/db/ COPY packages/adapter-utils/package.json packages/adapter-utils/ +COPY packages/mcp-server/package.json packages/mcp-server/ COPY packages/adapters/claude-local/package.json packages/adapters/claude-local/ COPY packages/adapters/codex-local/package.json packages/adapters/codex-local/ COPY packages/adapters/cursor-local/package.json packages/adapters/cursor-local/ @@ -35,12 +51,17 @@ RUN pnpm --filter @paperclipai/server build RUN test -f server/dist/index.js || (echo "ERROR: server build output missing" && exit 1) FROM base AS production +ARG USER_UID=1000 +ARG USER_GID=1000 WORKDIR /app COPY --chown=node:node --from=build /app /app RUN npm install --global --omit=dev @anthropic-ai/claude-code@latest @openai/codex@latest opencode-ai \ && mkdir -p /paperclip \ && chown node:node /paperclip +COPY scripts/docker-entrypoint.sh /usr/local/bin/ +RUN chmod +x /usr/local/bin/docker-entrypoint.sh + ENV NODE_ENV=production \ HOME=/paperclip \ HOST=0.0.0.0 \ @@ -48,12 +69,15 @@ ENV NODE_ENV=production \ SERVE_UI=true \ PAPERCLIP_HOME=/paperclip \ PAPERCLIP_INSTANCE_ID=default \ + USER_UID=${USER_UID} \ + USER_GID=${USER_GID} \ PAPERCLIP_CONFIG=/paperclip/instances/default/config.json \ PAPERCLIP_DEPLOYMENT_MODE=authenticated \ - PAPERCLIP_DEPLOYMENT_EXPOSURE=private + PAPERCLIP_DEPLOYMENT_EXPOSURE=private \ + OPENCODE_ALLOW_ALL_MODELS=true VOLUME ["/paperclip"] EXPOSE 3100 -USER node +ENTRYPOINT ["docker-entrypoint.sh"] CMD ["node", "--import", "./server/node_modules/tsx/dist/loader.mjs", "server/dist/index.js"] diff --git a/README.md b/README.md index 42f24cd1a87..c45776e74df 100644 --- a/README.md +++ b/README.md @@ -257,6 +257,19 @@ See [doc/DEVELOPING.md](doc/DEVELOPING.md) for the full development guide. Find Plugins and more at [awesome-paperclip](https://github.com/gsxdsm/awesome-paperclip) +## Telemetry + +Paperclip collects anonymous usage telemetry to help us understand how the product is used and improve it. No personal information, issue content, prompts, file paths, or secrets are ever collected. Private repository references are hashed with a per-install salt before being sent. + +Telemetry is **enabled by default** and can be disabled with any of the following: + +| Method | How | +|---|---| +| Environment variable | `PAPERCLIP_TELEMETRY_DISABLED=1` | +| Standard convention | `DO_NOT_TRACK=1` | +| CI environments | Automatically disabled when `CI=true` | +| Config file | Set `telemetry.enabled: false` in your Paperclip config | + ## Contributing We welcome contributions. See the [contributing guide](CONTRIBUTING.md) for details. diff --git a/adapter-plugin.md b/adapter-plugin.md new file mode 100644 index 00000000000..13994ba3b32 --- /dev/null +++ b/adapter-plugin.md @@ -0,0 +1,143 @@ +- Created branch: feat/external-adapter-phase1 + + I started phase 1 in the most merge-friendly way I could: small central changes, reusing existing registry patterns instead of inventing a whole new plugin system up front. + + What I changed + + 1. Server adapter registry is now mutable + Files: + - server/src/adapters/registry.ts + - server/src/adapters/index.ts + + Added: + - registerServerAdapter(adapter) + - unregisterServerAdapter(type) + - requireServerAdapter(type) + + Kept the existing built-in registry shape, but changed initialization so built-ins are registered into a mutable map on startup. + + Why this is merge-friendly: + - existing built-in adapter definitions stay where they already are + - existing lookup helpers still exist + - no big architectural rewrite yet + + 1. Runtime adapter validation moved to server routes + File: + - server/src/routes/agents.ts + + Added: + - assertKnownAdapterType(...) + + Used it in: + - /companies/:companyId/adapters/:type/models + - /companies/:companyId/adapters/:type/detect-model + - /companies/:companyId/adapters/:type/test-environment + - POST /companies/:companyId/agents + - POST /companies/:companyId/agent-hires + - PATCH /agents/:id when adapterType is touched + + Why: + - shared schemas can now allow external adapter strings + - server becomes the real source of truth for “is this adapter actually registered?” + + 1. Shared adapterType validation is now open-ended for inputs + Files: + - packages/shared/src/adapter-type.ts + - packages/shared/src/validators/agent.ts + - packages/shared/src/validators/access.ts + - packages/shared/src/index.ts + + Changed input validation from hardcoded z.enum(AGENT_ADAPTER_TYPES) to: + - agentAdapterTypeSchema + - optionalAgentAdapterTypeSchema + + These accept any non-empty string. + + Important: + - I did not remove AGENT_ADAPTER_TYPES from constants + - I did not change the built-in type unions yet + - this keeps the change minimal and limits blast radius + + So: + - input payloads can carry external adapter types + - actual acceptance still depends on server registry validation + + 1. UI adapter registry is now mutable too + Files: + - ui/src/adapters/registry.ts + - ui/src/adapters/index.ts + + Added: + - registerUIAdapter(adapter) + - unregisterUIAdapter(type) + - findUIAdapter(type) + + Built-ins are still registered from the same file, same pattern as server. + + 1. Began switching UI adapter lists toward the registry + Files: + - ui/src/adapters/metadata.ts + - ui/src/components/AgentConfigForm.tsx + - ui/src/pages/NewAgent.tsx + + Changed: + - AgentConfigForm adapter list now comes from registered UI adapters via metadata helper + - NewAgent supported adapter set now derives from listUIAdapters() + + Why: + - phase 1 should start reducing hardcoded UI lists + - but without rewriting every page at once + + 1. Tests added first, then implementation + New tests: + - server/src/__tests__/adapter-registry.test.ts + - server/src/__tests__/agent-adapter-validation-routes.test.ts + - ui/src/adapters/registry.test.ts + - packages/shared/src/adapter-types.test.ts + + Small test mock fix: + - server/src/__tests__/agent-skills-routes.test.ts + + Current status + + The branch is created and the phase-1 code is started. + + I also followed TDD in spirit: + - wrote failing tests first for missing register/unregister APIs + - then implemented the registry changes + + What is not finished yet + + I have not finished the green/stabilization pass yet. + + I still need to: + - run the targeted tests again after the implementation changes + - fix any TS / mocking fallout + - decide how far to take phase-1 UI wiring in this branch before stopping + + Also, I intentionally did not yet touch the bigger hardcoded surfaces like: + - NewAgentDialog + - OnboardingWizard + - InviteLanding + - duplicated adapterLabels maps + - plugin manifest / plugin loader adapter contributions + + That is deliberate. + For merge safety, I started with the smallest useful seam: + - dynamic server registry + - dynamic UI registry + - runtime validation based on registry + - first couple of UI consumers + + One note + There was already an unrelated dirty change in: + - server/scripts/dev-watch.ts + + I left that alone and did not make phase-1 changes there. + + Recommended next move + I should keep going on this same branch and do the stabilization pass: + 1. rerun server/ui/shared targeted tests + 2. fix failures + 3. run typechecks + 4. then extend the same registry-driven approach to the next safest UI surfaces diff --git a/cli/src/__tests__/allowed-hostname.test.ts b/cli/src/__tests__/allowed-hostname.test.ts index 572689c4d5d..8e17e56bfad 100644 --- a/cli/src/__tests__/allowed-hostname.test.ts +++ b/cli/src/__tests__/allowed-hostname.test.ts @@ -44,6 +44,9 @@ function writeBaseConfig(configPath: string) { baseUrlMode: "auto", disableSignUp: false, }, + telemetry: { + enabled: true, + }, storage: { provider: "local_disk", localDisk: { baseDir: "/tmp/paperclip-storage" }, diff --git a/cli/src/__tests__/company-delete.test.ts b/cli/src/__tests__/company-delete.test.ts index 73d5e440923..d6fc399f0b2 100644 --- a/cli/src/__tests__/company-delete.test.ts +++ b/cli/src/__tests__/company-delete.test.ts @@ -15,6 +15,10 @@ function makeCompany(overrides: Partial): Company { budgetMonthlyCents: 0, spentMonthlyCents: 0, requireBoardApprovalForNewAgents: false, + feedbackDataSharingEnabled: false, + feedbackDataSharingConsentAt: null, + feedbackDataSharingConsentByUserId: null, + feedbackDataSharingTermsVersion: null, brandColor: null, logoAssetId: null, logoUrl: null, diff --git a/cli/src/__tests__/company-import-url.test.ts b/cli/src/__tests__/company-import-url.test.ts index abc96f7da23..1f1548bdffe 100644 --- a/cli/src/__tests__/company-import-url.test.ts +++ b/cli/src/__tests__/company-import-url.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; import { isGithubShorthand, - isGithubUrl, + looksLikeRepoUrl, isHttpUrl, normalizeGithubImportSource, } from "../commands/client/company.js"; @@ -21,17 +21,17 @@ describe("isHttpUrl", () => { }); }); -describe("isGithubUrl", () => { +describe("looksLikeRepoUrl", () => { it("matches GitHub URLs", () => { - expect(isGithubUrl("https://github.com/org/repo")).toBe(true); + expect(looksLikeRepoUrl("https://github.com/org/repo")).toBe(true); }); - it("rejects non-GitHub HTTP URLs", () => { - expect(isGithubUrl("https://example.com/foo")).toBe(false); + it("rejects URLs without owner/repo path", () => { + expect(looksLikeRepoUrl("https://example.com/foo")).toBe(false); }); it("rejects local paths", () => { - expect(isGithubUrl("/tmp/my-company")).toBe(false); + expect(looksLikeRepoUrl("/tmp/my-company")).toBe(false); }); }); diff --git a/cli/src/__tests__/company.test.ts b/cli/src/__tests__/company.test.ts index d74674b2f48..922e95c7d3a 100644 --- a/cli/src/__tests__/company.test.ts +++ b/cli/src/__tests__/company.test.ts @@ -163,6 +163,10 @@ describe("renderCompanyImportPreview", () => { brandColor: null, logoPath: null, requireBoardApprovalForNewAgents: false, + feedbackDataSharingEnabled: false, + feedbackDataSharingConsentAt: null, + feedbackDataSharingConsentByUserId: null, + feedbackDataSharingTermsVersion: null, }, sidebar: { agents: ["ceo"], @@ -216,6 +220,7 @@ describe("renderCompanyImportPreview", () => { status: null, executionWorkspacePolicy: null, workspaces: [], + env: null, metadata: null, }, ], @@ -246,6 +251,7 @@ describe("renderCompanyImportPreview", () => { key: "OPENAI_API_KEY", description: null, agentSlug: "ceo", + projectSlug: null, kind: "secret", requirement: "required", defaultValue: null, @@ -261,6 +267,7 @@ describe("renderCompanyImportPreview", () => { key: "OPENAI_API_KEY", description: null, agentSlug: "ceo", + projectSlug: null, kind: "secret", requirement: "required", defaultValue: null, @@ -371,6 +378,10 @@ describe("import selection catalog", () => { brandColor: null, logoPath: "images/company-logo.png", requireBoardApprovalForNewAgents: false, + feedbackDataSharingEnabled: false, + feedbackDataSharingConsentAt: null, + feedbackDataSharingConsentByUserId: null, + feedbackDataSharingTermsVersion: null, }, sidebar: { agents: ["ceo"], @@ -424,6 +435,7 @@ describe("import selection catalog", () => { status: null, executionWorkspacePolicy: null, workspaces: [], + env: null, metadata: null, }, ], diff --git a/cli/src/__tests__/doctor.test.ts b/cli/src/__tests__/doctor.test.ts index 83a678317b3..2e1d7d85076 100644 --- a/cli/src/__tests__/doctor.test.ts +++ b/cli/src/__tests__/doctor.test.ts @@ -46,6 +46,9 @@ function createTempConfig(): string { baseUrlMode: "auto", disableSignUp: false, }, + telemetry: { + enabled: true, + }, storage: { provider: "local_disk", localDisk: { diff --git a/cli/src/__tests__/feedback.test.ts b/cli/src/__tests__/feedback.test.ts new file mode 100644 index 00000000000..e46b307d127 --- /dev/null +++ b/cli/src/__tests__/feedback.test.ts @@ -0,0 +1,177 @@ +import os from "node:os"; +import path from "node:path"; +import { mkdtemp, readFile } from "node:fs/promises"; +import { Command } from "commander"; +import { describe, expect, it } from "vitest"; +import type { FeedbackTrace } from "@paperclipai/shared"; +import { readZipArchive } from "../commands/client/zip.js"; +import { + buildFeedbackTraceQuery, + registerFeedbackCommands, + renderFeedbackReport, + summarizeFeedbackTraces, + writeFeedbackExportBundle, +} from "../commands/client/feedback.js"; + +function makeTrace(overrides: Partial = {}): FeedbackTrace { + return { + id: "trace-12345678", + companyId: "company-123", + feedbackVoteId: "vote-12345678", + issueId: "issue-123", + projectId: "project-123", + issueIdentifier: "PAP-123", + issueTitle: "Fix the feedback command", + authorUserId: "user-123", + targetType: "issue_comment", + targetId: "comment-123", + vote: "down", + status: "pending", + destination: "paperclip_labs_feedback_v1", + exportId: null, + consentVersion: "feedback-data-sharing-v1", + schemaVersion: "1", + bundleVersion: "1", + payloadVersion: "1", + payloadDigest: null, + payloadSnapshot: { + vote: { + value: "down", + reason: "Needed more detail", + }, + }, + targetSummary: { + label: "Comment", + excerpt: "The first answer was too vague.", + authorAgentId: "agent-123", + authorUserId: null, + createdAt: new Date("2026-03-31T12:00:00.000Z"), + documentKey: null, + documentTitle: null, + revisionNumber: null, + }, + redactionSummary: null, + attemptCount: 0, + lastAttemptedAt: null, + exportedAt: null, + failureReason: null, + createdAt: new Date("2026-03-31T12:01:00.000Z"), + updatedAt: new Date("2026-03-31T12:02:00.000Z"), + ...overrides, + }; +} + +describe("registerFeedbackCommands", () => { + it("registers the top-level feedback commands", () => { + const program = new Command(); + + expect(() => registerFeedbackCommands(program)).not.toThrow(); + + const feedback = program.commands.find((command) => command.name() === "feedback"); + expect(feedback).toBeDefined(); + expect(feedback?.commands.map((command) => command.name())).toEqual(["report", "export"]); + expect(feedback?.commands[0]?.options.filter((option) => option.long === "--company-id")).toHaveLength(1); + }); +}); + +describe("buildFeedbackTraceQuery", () => { + it("encodes all supported filters", () => { + expect( + buildFeedbackTraceQuery({ + targetType: "issue_comment", + vote: "down", + status: "pending", + projectId: "project-123", + issueId: "issue-123", + from: "2026-03-31T00:00:00.000Z", + to: "2026-03-31T23:59:59.999Z", + sharedOnly: true, + }), + ).toBe( + "?targetType=issue_comment&vote=down&status=pending&projectId=project-123&issueId=issue-123&from=2026-03-31T00%3A00%3A00.000Z&to=2026-03-31T23%3A59%3A59.999Z&sharedOnly=true&includePayload=true", + ); + }); +}); + +describe("renderFeedbackReport", () => { + it("includes summary counts and the optional reason", () => { + const traces = [ + makeTrace(), + makeTrace({ + id: "trace-87654321", + feedbackVoteId: "vote-87654321", + vote: "up", + status: "local_only", + payloadSnapshot: { + vote: { + value: "up", + reason: null, + }, + }, + }), + ]; + + const report = renderFeedbackReport({ + apiBase: "http://127.0.0.1:3100", + companyId: "company-123", + traces, + summary: summarizeFeedbackTraces(traces), + includePayloads: false, + }); + + expect(report).toContain("Paperclip Feedback Report"); + expect(report).toContain("thumbs up"); + expect(report).toContain("thumbs down"); + expect(report).toContain("Needed more detail"); + }); +}); + +describe("writeFeedbackExportBundle", () => { + it("writes votes, traces, a manifest, and a zip archive", async () => { + const tempDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-feedback-export-")); + const outputDir = path.join(tempDir, "feedback-export"); + const traces = [ + makeTrace(), + makeTrace({ + id: "trace-abcdef12", + feedbackVoteId: "vote-abcdef12", + issueIdentifier: "PAP-124", + issueId: "issue-124", + vote: "up", + status: "local_only", + payloadSnapshot: { + vote: { + value: "up", + reason: null, + }, + }, + }), + ]; + + const exported = await writeFeedbackExportBundle({ + apiBase: "http://127.0.0.1:3100", + companyId: "company-123", + traces, + outputDir, + }); + + expect(exported.manifest.summary.total).toBe(2); + expect(exported.manifest.summary.withReason).toBe(1); + + const manifest = JSON.parse(await readFile(path.join(outputDir, "index.json"), "utf8")) as { + files: { votes: string[]; traces: string[]; zip: string }; + }; + expect(manifest.files.votes).toHaveLength(2); + expect(manifest.files.traces).toHaveLength(2); + + const archive = await readFile(exported.zipPath); + const zip = await readZipArchive(archive); + expect(Object.keys(zip.files)).toEqual( + expect.arrayContaining([ + "index.json", + `votes/${manifest.files.votes[0]}`, + `traces/${manifest.files.traces[0]}`, + ]), + ); + }); +}); diff --git a/cli/src/__tests__/routines.test.ts b/cli/src/__tests__/routines.test.ts new file mode 100644 index 00000000000..e24a2b5bba3 --- /dev/null +++ b/cli/src/__tests__/routines.test.ts @@ -0,0 +1,249 @@ +import { randomUUID } from "node:crypto"; +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest"; +import { eq } from "drizzle-orm"; +import { + agents, + companies, + createDb, + projects, + routines, +} from "@paperclipai/db"; +import { + getEmbeddedPostgresTestSupport, + startEmbeddedPostgresTestDatabase, +} from "./helpers/embedded-postgres.js"; +import { disableAllRoutinesInConfig } from "../commands/routines.js"; + +const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport(); +const describeEmbeddedPostgres = embeddedPostgresSupport.supported ? describe : describe.skip; + +if (!embeddedPostgresSupport.supported) { + console.warn( + `Skipping embedded Postgres routines CLI tests on this host: ${embeddedPostgresSupport.reason ?? "unsupported environment"}`, + ); +} + +function writeTestConfig(configPath: string, tempRoot: string, connectionString: string) { + const config = { + $meta: { + version: 1, + updatedAt: new Date().toISOString(), + source: "doctor" as const, + }, + database: { + mode: "postgres" as const, + connectionString, + embeddedPostgresDataDir: path.join(tempRoot, "embedded-db"), + embeddedPostgresPort: 54329, + backup: { + enabled: false, + intervalMinutes: 60, + retentionDays: 30, + dir: path.join(tempRoot, "backups"), + }, + }, + logging: { + mode: "file" as const, + logDir: path.join(tempRoot, "logs"), + }, + server: { + deploymentMode: "local_trusted" as const, + exposure: "private" as const, + host: "127.0.0.1", + port: 3100, + allowedHostnames: [], + serveUi: false, + }, + auth: { + baseUrlMode: "auto" as const, + disableSignUp: false, + }, + storage: { + provider: "local_disk" as const, + localDisk: { + baseDir: path.join(tempRoot, "storage"), + }, + s3: { + bucket: "paperclip", + region: "us-east-1", + prefix: "", + forcePathStyle: false, + }, + }, + secrets: { + provider: "local_encrypted" as const, + strictMode: false, + localEncrypted: { + keyFilePath: path.join(tempRoot, "secrets", "master.key"), + }, + }, + }; + + mkdirSync(path.dirname(configPath), { recursive: true }); + writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8"); +} + +describeEmbeddedPostgres("disableAllRoutinesInConfig", () => { + let db!: ReturnType; + let tempDb: Awaited> | null = null; + let tempRoot = ""; + let configPath = ""; + + beforeAll(async () => { + tempDb = await startEmbeddedPostgresTestDatabase("paperclip-routines-cli-db-"); + db = createDb(tempDb.connectionString); + tempRoot = mkdtempSync(path.join(os.tmpdir(), "paperclip-routines-cli-config-")); + configPath = path.join(tempRoot, "config.json"); + writeTestConfig(configPath, tempRoot, tempDb.connectionString); + }, 20_000); + + afterEach(async () => { + await db.delete(routines); + await db.delete(projects); + await db.delete(agents); + await db.delete(companies); + }); + + afterAll(async () => { + await tempDb?.cleanup(); + if (tempRoot) { + rmSync(tempRoot, { recursive: true, force: true }); + } + }); + + it("pauses only non-archived routines for the selected company", async () => { + const companyId = randomUUID(); + const otherCompanyId = randomUUID(); + const projectId = randomUUID(); + const otherProjectId = randomUUID(); + const agentId = randomUUID(); + const otherAgentId = randomUUID(); + const activeRoutineId = randomUUID(); + const pausedRoutineId = randomUUID(); + const archivedRoutineId = randomUUID(); + const otherCompanyRoutineId = randomUUID(); + + await db.insert(companies).values([ + { + id: companyId, + name: "Paperclip", + issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`, + requireBoardApprovalForNewAgents: false, + }, + { + id: otherCompanyId, + name: "Other company", + issuePrefix: `T${otherCompanyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`, + requireBoardApprovalForNewAgents: false, + }, + ]); + + await db.insert(agents).values([ + { + id: agentId, + companyId, + name: "Coder", + adapterType: "process", + adapterConfig: {}, + runtimeConfig: {}, + permissions: {}, + }, + { + id: otherAgentId, + companyId: otherCompanyId, + name: "Other coder", + adapterType: "process", + adapterConfig: {}, + runtimeConfig: {}, + permissions: {}, + }, + ]); + + await db.insert(projects).values([ + { + id: projectId, + companyId, + name: "Project", + status: "in_progress", + }, + { + id: otherProjectId, + companyId: otherCompanyId, + name: "Other project", + status: "in_progress", + }, + ]); + + await db.insert(routines).values([ + { + id: activeRoutineId, + companyId, + projectId, + assigneeAgentId: agentId, + title: "Active routine", + status: "active", + }, + { + id: pausedRoutineId, + companyId, + projectId, + assigneeAgentId: agentId, + title: "Paused routine", + status: "paused", + }, + { + id: archivedRoutineId, + companyId, + projectId, + assigneeAgentId: agentId, + title: "Archived routine", + status: "archived", + }, + { + id: otherCompanyRoutineId, + companyId: otherCompanyId, + projectId: otherProjectId, + assigneeAgentId: otherAgentId, + title: "Other company routine", + status: "active", + }, + ]); + + const result = await disableAllRoutinesInConfig({ + config: configPath, + companyId, + }); + + expect(result).toMatchObject({ + companyId, + totalRoutines: 3, + pausedCount: 1, + alreadyPausedCount: 1, + archivedCount: 1, + }); + + const companyRoutines = await db + .select({ + id: routines.id, + status: routines.status, + }) + .from(routines) + .where(eq(routines.companyId, companyId)); + const statusById = new Map(companyRoutines.map((routine) => [routine.id, routine.status])); + + expect(statusById.get(activeRoutineId)).toBe("paused"); + expect(statusById.get(pausedRoutineId)).toBe("paused"); + expect(statusById.get(archivedRoutineId)).toBe("archived"); + + const otherCompanyRoutine = await db + .select({ + status: routines.status, + }) + .from(routines) + .where(eq(routines.id, otherCompanyRoutineId)); + expect(otherCompanyRoutine[0]?.status).toBe("active"); + }); +}); diff --git a/cli/src/__tests__/telemetry.test.ts b/cli/src/__tests__/telemetry.test.ts new file mode 100644 index 00000000000..9d42a609716 --- /dev/null +++ b/cli/src/__tests__/telemetry.test.ts @@ -0,0 +1,117 @@ +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +const ORIGINAL_ENV = { ...process.env }; +const CI_ENV_VARS = ["CI", "CONTINUOUS_INTEGRATION", "BUILD_NUMBER", "GITHUB_ACTIONS", "GITLAB_CI"]; + +function makeConfigPath(root: string, enabled: boolean): string { + const configPath = path.join(root, ".paperclip", "config.json"); + fs.mkdirSync(path.dirname(configPath), { recursive: true }); + fs.writeFileSync(configPath, JSON.stringify({ + $meta: { + version: 1, + updatedAt: "2026-03-31T00:00:00.000Z", + source: "configure", + }, + database: { + mode: "embedded-postgres", + embeddedPostgresDataDir: path.join(root, "runtime", "db"), + embeddedPostgresPort: 54329, + backup: { + enabled: true, + intervalMinutes: 60, + retentionDays: 30, + dir: path.join(root, "runtime", "backups"), + }, + }, + logging: { + mode: "file", + logDir: path.join(root, "runtime", "logs"), + }, + server: { + deploymentMode: "local_trusted", + exposure: "private", + host: "127.0.0.1", + port: 3100, + allowedHostnames: [], + serveUi: true, + }, + auth: { + baseUrlMode: "auto", + disableSignUp: false, + }, + telemetry: { + enabled, + }, + storage: { + provider: "local_disk", + localDisk: { + baseDir: path.join(root, "runtime", "storage"), + }, + s3: { + bucket: "paperclip", + region: "us-east-1", + prefix: "", + forcePathStyle: false, + }, + }, + secrets: { + provider: "local_encrypted", + strictMode: false, + localEncrypted: { + keyFilePath: path.join(root, "runtime", "secrets", "master.key"), + }, + }, + }, null, 2)); + return configPath; +} + +describe("cli telemetry", () => { + beforeEach(() => { + process.env = { ...ORIGINAL_ENV }; + for (const key of CI_ENV_VARS) { + delete process.env[key]; + } + vi.stubGlobal("fetch", vi.fn(async () => ({ ok: true }))); + }); + + afterEach(() => { + process.env = { ...ORIGINAL_ENV }; + vi.unstubAllGlobals(); + vi.resetModules(); + }); + + it("respects telemetry.enabled=false from the config file", async () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-cli-telemetry-")); + const configPath = makeConfigPath(root, false); + process.env.PAPERCLIP_HOME = path.join(root, "home"); + process.env.PAPERCLIP_INSTANCE_ID = "telemetry-test"; + + const { initTelemetryFromConfigFile } = await import("../telemetry.js"); + const client = initTelemetryFromConfigFile(configPath); + + expect(client).toBeNull(); + expect(fs.existsSync(path.join(root, "home", "instances", "telemetry-test", "telemetry", "state.json"))).toBe(false); + }); + + it("creates telemetry state only after the first event is tracked", async () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-cli-telemetry-")); + process.env.PAPERCLIP_HOME = path.join(root, "home"); + process.env.PAPERCLIP_INSTANCE_ID = "telemetry-test"; + + const { initTelemetry, flushTelemetry } = await import("../telemetry.js"); + const client = initTelemetry({ enabled: true }); + const statePath = path.join(root, "home", "instances", "telemetry-test", "telemetry", "state.json"); + + expect(client).not.toBeNull(); + expect(fs.existsSync(statePath)).toBe(false); + + client!.track("install.started", { setupMode: "quickstart" }); + + expect(fs.existsSync(statePath)).toBe(true); + + await flushTelemetry(); + }); +}); diff --git a/cli/src/__tests__/worktree.test.ts b/cli/src/__tests__/worktree.test.ts index 51e456bdf5a..774f5649cf6 100644 --- a/cli/src/__tests__/worktree.test.ts +++ b/cli/src/__tests__/worktree.test.ts @@ -10,10 +10,13 @@ import { readSourceAttachmentBody, rebindWorkspaceCwd, resolveSourceConfigPath, + resolveWorktreeReseedSource, + resolveWorktreeReseedTargetPaths, resolveGitWorktreeAddArgs, resolveWorktreeMakeTargetPath, worktreeInitCommand, worktreeMakeCommand, + worktreeReseedCommand, } from "../commands/worktree.js"; import { buildWorktreeConfig, @@ -78,6 +81,9 @@ function buildSourceConfig(): PaperclipConfig { publicBaseUrl: "http://127.0.0.1:3100", disableSignUp: false, }, + telemetry: { + enabled: true, + }, storage: { provider: "local_disk", localDisk: { @@ -509,7 +515,7 @@ describe("worktree helpers", () => { }); const config = JSON.parse(fs.readFileSync(path.join(repoRoot, ".paperclip", "config.json"), "utf8")); - expect(config.server.port).toBe(3102); + expect(config.server.port).toBeGreaterThan(3101); expect(config.database.embeddedPostgresPort).not.toBe(54330); expect(config.database.embeddedPostgresPort).not.toBe(config.server.port); expect(config.database.embeddedPostgresPort).toBeGreaterThan(54330); @@ -572,6 +578,234 @@ describe("worktree helpers", () => { } }); + it("requires an explicit reseed source", () => { + expect(() => resolveWorktreeReseedSource({})).toThrow( + "Pass --from or --from-config/--from-instance explicitly so the reseed source is unambiguous.", + ); + }); + + it("rejects mixed reseed source selectors", () => { + expect(() => resolveWorktreeReseedSource({ + from: "current", + fromInstance: "default", + })).toThrow( + "Use either --from or --from-config/--from-data-dir/--from-instance, not both.", + ); + }); + + it("derives worktree reseed target paths from the adjacent env file", () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-reseed-target-")); + const worktreeRoot = path.join(tempRoot, "repo"); + const configPath = path.join(worktreeRoot, ".paperclip", "config.json"); + const envPath = path.join(worktreeRoot, ".paperclip", ".env"); + + try { + fs.mkdirSync(path.dirname(configPath), { recursive: true }); + fs.writeFileSync(configPath, JSON.stringify(buildSourceConfig()), "utf8"); + fs.writeFileSync( + envPath, + [ + "PAPERCLIP_HOME=/tmp/paperclip-worktrees", + "PAPERCLIP_INSTANCE_ID=pap-1132-chat", + ].join("\n"), + "utf8", + ); + expect( + resolveWorktreeReseedTargetPaths({ + configPath, + rootPath: worktreeRoot, + }), + ).toMatchObject({ + cwd: worktreeRoot, + homeDir: "/tmp/paperclip-worktrees", + instanceId: "pap-1132-chat", + }); + } finally { + fs.rmSync(tempRoot, { recursive: true, force: true }); + } + }); + + it("rejects reseed targets without worktree env metadata", () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-reseed-target-missing-")); + const worktreeRoot = path.join(tempRoot, "repo"); + const configPath = path.join(worktreeRoot, ".paperclip", "config.json"); + + try { + fs.mkdirSync(path.dirname(configPath), { recursive: true }); + fs.writeFileSync(configPath, JSON.stringify(buildSourceConfig()), "utf8"); + fs.writeFileSync(path.join(worktreeRoot, ".paperclip", ".env"), "", "utf8"); + + expect(() => + resolveWorktreeReseedTargetPaths({ + configPath, + rootPath: worktreeRoot, + })).toThrow("does not look like a worktree-local Paperclip instance"); + } finally { + fs.rmSync(tempRoot, { recursive: true, force: true }); + } + }); + + it("reseed preserves the current worktree ports, instance id, and branding", async () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-reseed-")); + const repoRoot = path.join(tempRoot, "repo"); + const sourceRoot = path.join(tempRoot, "source"); + const homeDir = path.join(tempRoot, ".paperclip-worktrees"); + const currentInstanceId = "existing-worktree"; + const currentPaths = resolveWorktreeLocalPaths({ + cwd: repoRoot, + homeDir, + instanceId: currentInstanceId, + }); + const sourcePaths = resolveWorktreeLocalPaths({ + cwd: sourceRoot, + homeDir: path.join(tempRoot, ".paperclip-source"), + instanceId: "default", + }); + const originalCwd = process.cwd(); + const originalPaperclipConfig = process.env.PAPERCLIP_CONFIG; + + try { + fs.mkdirSync(path.dirname(currentPaths.configPath), { recursive: true }); + fs.mkdirSync(path.dirname(sourcePaths.configPath), { recursive: true }); + fs.mkdirSync(path.dirname(sourcePaths.secretsKeyFilePath), { recursive: true }); + fs.mkdirSync(repoRoot, { recursive: true }); + fs.mkdirSync(sourceRoot, { recursive: true }); + + const currentConfig = buildWorktreeConfig({ + sourceConfig: buildSourceConfig(), + paths: currentPaths, + serverPort: 3114, + databasePort: 54341, + }); + const sourceConfig = buildWorktreeConfig({ + sourceConfig: buildSourceConfig(), + paths: sourcePaths, + serverPort: 3200, + databasePort: 54400, + }); + fs.writeFileSync(currentPaths.configPath, JSON.stringify(currentConfig, null, 2), "utf8"); + fs.writeFileSync(sourcePaths.configPath, JSON.stringify(sourceConfig, null, 2), "utf8"); + fs.writeFileSync(sourcePaths.secretsKeyFilePath, "source-secret", "utf8"); + fs.writeFileSync( + currentPaths.envPath, + [ + `PAPERCLIP_HOME=${homeDir}`, + `PAPERCLIP_INSTANCE_ID=${currentInstanceId}`, + "PAPERCLIP_WORKTREE_NAME=existing-name", + "PAPERCLIP_WORKTREE_COLOR=\"#112233\"", + ].join("\n"), + "utf8", + ); + + delete process.env.PAPERCLIP_CONFIG; + process.chdir(repoRoot); + + await worktreeReseedCommand({ + fromConfig: sourcePaths.configPath, + yes: true, + }); + + const rewrittenConfig = JSON.parse(fs.readFileSync(currentPaths.configPath, "utf8")); + const rewrittenEnv = fs.readFileSync(currentPaths.envPath, "utf8"); + + expect(rewrittenConfig.server.port).toBe(3114); + expect(rewrittenConfig.database.embeddedPostgresPort).toBe(54341); + expect(rewrittenConfig.database.embeddedPostgresDataDir).toBe(currentPaths.embeddedPostgresDataDir); + expect(rewrittenEnv).toContain(`PAPERCLIP_INSTANCE_ID=${currentInstanceId}`); + expect(rewrittenEnv).toContain("PAPERCLIP_WORKTREE_NAME=existing-name"); + expect(rewrittenEnv).toContain("PAPERCLIP_WORKTREE_COLOR=\"#112233\""); + } finally { + process.chdir(originalCwd); + if (originalPaperclipConfig === undefined) { + delete process.env.PAPERCLIP_CONFIG; + } else { + process.env.PAPERCLIP_CONFIG = originalPaperclipConfig; + } + fs.rmSync(tempRoot, { recursive: true, force: true }); + } + }, 20_000); + + it("restores the current worktree config and instance data if reseed fails", async () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-reseed-rollback-")); + const repoRoot = path.join(tempRoot, "repo"); + const sourceRoot = path.join(tempRoot, "source"); + const homeDir = path.join(tempRoot, ".paperclip-worktrees"); + const currentInstanceId = "rollback-worktree"; + const currentPaths = resolveWorktreeLocalPaths({ + cwd: repoRoot, + homeDir, + instanceId: currentInstanceId, + }); + const sourcePaths = resolveWorktreeLocalPaths({ + cwd: sourceRoot, + homeDir: path.join(tempRoot, ".paperclip-source"), + instanceId: "default", + }); + const originalCwd = process.cwd(); + const originalPaperclipConfig = process.env.PAPERCLIP_CONFIG; + + try { + fs.mkdirSync(path.dirname(currentPaths.configPath), { recursive: true }); + fs.mkdirSync(path.dirname(sourcePaths.configPath), { recursive: true }); + fs.mkdirSync(currentPaths.instanceRoot, { recursive: true }); + fs.mkdirSync(path.dirname(sourcePaths.secretsKeyFilePath), { recursive: true }); + fs.mkdirSync(repoRoot, { recursive: true }); + fs.mkdirSync(sourceRoot, { recursive: true }); + + const currentConfig = buildWorktreeConfig({ + sourceConfig: buildSourceConfig(), + paths: currentPaths, + serverPort: 3114, + databasePort: 54341, + }); + const sourceConfig = { + ...buildSourceConfig(), + database: { + mode: "postgres", + connectionString: "", + }, + secrets: { + provider: "local_encrypted", + strictMode: false, + localEncrypted: { + keyFilePath: sourcePaths.secretsKeyFilePath, + }, + }, + } as PaperclipConfig; + + fs.writeFileSync(currentPaths.configPath, JSON.stringify(currentConfig, null, 2), "utf8"); + fs.writeFileSync(currentPaths.envPath, `PAPERCLIP_HOME=${homeDir}\nPAPERCLIP_INSTANCE_ID=${currentInstanceId}\n`, "utf8"); + fs.writeFileSync(path.join(currentPaths.instanceRoot, "marker.txt"), "keep me", "utf8"); + fs.writeFileSync(sourcePaths.configPath, JSON.stringify(sourceConfig, null, 2), "utf8"); + fs.writeFileSync(sourcePaths.secretsKeyFilePath, "source-secret", "utf8"); + + delete process.env.PAPERCLIP_CONFIG; + process.chdir(repoRoot); + + await expect(worktreeReseedCommand({ + fromConfig: sourcePaths.configPath, + yes: true, + })).rejects.toThrow("Source instance uses postgres mode but has no connection string"); + + const restoredConfig = JSON.parse(fs.readFileSync(currentPaths.configPath, "utf8")); + const restoredEnv = fs.readFileSync(currentPaths.envPath, "utf8"); + const restoredMarker = fs.readFileSync(path.join(currentPaths.instanceRoot, "marker.txt"), "utf8"); + + expect(restoredConfig.server.port).toBe(3114); + expect(restoredConfig.database.embeddedPostgresPort).toBe(54341); + expect(restoredEnv).toContain(`PAPERCLIP_INSTANCE_ID=${currentInstanceId}`); + expect(restoredMarker).toBe("keep me"); + } finally { + process.chdir(originalCwd); + if (originalPaperclipConfig === undefined) { + delete process.env.PAPERCLIP_CONFIG; + } else { + process.env.PAPERCLIP_CONFIG = originalPaperclipConfig; + } + fs.rmSync(tempRoot, { recursive: true, force: true }); + } + }); + it("rebinds same-repo workspace paths onto the current worktree root", () => { expect( rebindWorkspaceCwd({ diff --git a/cli/src/commands/client/company.ts b/cli/src/commands/client/company.ts index ac4fdc1c5f6..6e4c668817e 100644 --- a/cli/src/commands/client/company.ts +++ b/cli/src/commands/client/company.ts @@ -5,12 +5,14 @@ import * as p from "@clack/prompts"; import pc from "picocolors"; import type { Company, + FeedbackTrace, CompanyPortabilityFileEntry, CompanyPortabilityExportResult, CompanyPortabilityInclude, CompanyPortabilityPreviewResult, CompanyPortabilityImportResult, } from "@paperclipai/shared"; +import { getTelemetryClient, trackCompanyImported } from "../../telemetry.js"; import { ApiRequestError } from "../../client/http.js"; import { openUrl } from "../../client/board-auth.js"; import { binaryContentTypeByExtension, readZipArchive } from "./zip.js"; @@ -22,6 +24,11 @@ import { resolveCommandContext, type BaseClientOptions, } from "./common.js"; +import { + buildFeedbackTraceQuery, + normalizeFeedbackTraceExportFormat, + serializeFeedbackTraces, +} from "./feedback.js"; interface CompanyCommandOptions extends BaseClientOptions {} type CompanyDeleteSelectorMode = "auto" | "id" | "prefix"; @@ -44,6 +51,20 @@ interface CompanyExportOptions extends BaseClientOptions { expandReferencedSkills?: boolean; } +interface CompanyFeedbackOptions extends BaseClientOptions { + targetType?: string; + vote?: string; + status?: string; + projectId?: string; + issueId?: string; + from?: string; + to?: string; + sharedOnly?: boolean; + includePayload?: boolean; + out?: string; + format?: string; +} + interface CompanyImportOptions extends BaseClientOptions { include?: string; target?: CompanyImportTargetMode; @@ -765,8 +786,15 @@ export function isHttpUrl(input: string): boolean { return /^https?:\/\//i.test(input.trim()); } -export function isGithubUrl(input: string): boolean { - return /^https?:\/\/github\.com\//i.test(input.trim()); +export function looksLikeRepoUrl(input: string): boolean { + try { + const url = new URL(input.trim()); + if (url.protocol !== "https:") return false; + const segments = url.pathname.split("/").filter(Boolean); + return segments.length >= 2; + } catch { + return false; + } } function isGithubSegment(input: string): boolean { @@ -797,13 +825,15 @@ function normalizeGithubImportPath(input: string | null | undefined): string | n } function buildGithubImportUrl(input: { + hostname?: string; owner: string; repo: string; ref?: string | null; path?: string | null; companyPath?: string | null; }): string { - const url = new URL(`https://github.com/${input.owner}/${input.repo.replace(/\.git$/i, "")}`); + const host = input.hostname || "github.com"; + const url = new URL(`https://${host}/${input.owner}/${input.repo.replace(/\.git$/i, "")}`); const ref = input.ref?.trim(); if (ref) { url.searchParams.set("ref", ref); @@ -834,14 +864,15 @@ export function normalizeGithubImportSource(input: string, refOverride?: string) }); } - if (!isGithubUrl(trimmed)) { - throw new Error("GitHub source must be a github.com URL or owner/repo[/path] shorthand."); + if (!looksLikeRepoUrl(trimmed)) { + throw new Error("GitHub source must be a GitHub or GitHub Enterprise URL, or owner/repo[/path] shorthand."); } if (!ref) { return trimmed; } const url = new URL(trimmed); + const hostname = url.hostname; const parts = url.pathname.split("/").filter(Boolean); if (parts.length < 2) { throw new Error("Invalid GitHub URL."); @@ -852,18 +883,18 @@ export function normalizeGithubImportSource(input: string, refOverride?: string) const existingPath = normalizeGithubImportPath(url.searchParams.get("path")); const existingCompanyPath = normalizeGithubImportPath(url.searchParams.get("companyPath")); if (existingCompanyPath) { - return buildGithubImportUrl({ owner, repo, ref, companyPath: existingCompanyPath }); + return buildGithubImportUrl({ hostname, owner, repo, ref, companyPath: existingCompanyPath }); } if (existingPath) { - return buildGithubImportUrl({ owner, repo, ref, path: existingPath }); + return buildGithubImportUrl({ hostname, owner, repo, ref, path: existingPath }); } if (parts[2] === "tree") { - return buildGithubImportUrl({ owner, repo, ref, path: parts.slice(4).join("/") }); + return buildGithubImportUrl({ hostname, owner, repo, ref, path: parts.slice(4).join("/") }); } if (parts[2] === "blob") { - return buildGithubImportUrl({ owner, repo, ref, companyPath: parts.slice(4).join("/") }); + return buildGithubImportUrl({ hostname, owner, repo, ref, companyPath: parts.slice(4).join("/") }); } - return buildGithubImportUrl({ owner, repo, ref }); + return buildGithubImportUrl({ hostname, owner, repo, ref }); } async function pathExists(inputPath: string): Promise { @@ -1093,6 +1124,91 @@ export function registerCompanyCommands(program: Command): void { }), ); + addCommonClientOptions( + company + .command("feedback:list") + .description("List feedback traces for a company") + .requiredOption("-C, --company-id ", "Company ID") + .option("--target-type ", "Filter by target type") + .option("--vote ", "Filter by vote value") + .option("--status ", "Filter by trace status") + .option("--project-id ", "Filter by project ID") + .option("--issue-id ", "Filter by issue ID") + .option("--from ", "Only include traces created at or after this timestamp") + .option("--to ", "Only include traces created at or before this timestamp") + .option("--shared-only", "Only include traces eligible for sharing/export") + .option("--include-payload", "Include stored payload snapshots in the response") + .action(async (opts: CompanyFeedbackOptions) => { + try { + const ctx = resolveCommandContext(opts, { requireCompany: true }); + const traces = (await ctx.api.get( + `/api/companies/${ctx.companyId}/feedback-traces${buildFeedbackTraceQuery(opts)}`, + )) ?? []; + if (ctx.json) { + printOutput(traces, { json: true }); + return; + } + printOutput( + traces.map((trace) => ({ + id: trace.id, + issue: trace.issueIdentifier ?? trace.issueId, + vote: trace.vote, + status: trace.status, + targetType: trace.targetType, + target: trace.targetSummary.label, + })), + { json: false }, + ); + } catch (err) { + handleCommandError(err); + } + }), + { includeCompany: false }, + ); + + addCommonClientOptions( + company + .command("feedback:export") + .description("Export feedback traces for a company") + .requiredOption("-C, --company-id ", "Company ID") + .option("--target-type ", "Filter by target type") + .option("--vote ", "Filter by vote value") + .option("--status ", "Filter by trace status") + .option("--project-id ", "Filter by project ID") + .option("--issue-id ", "Filter by issue ID") + .option("--from ", "Only include traces created at or after this timestamp") + .option("--to ", "Only include traces created at or before this timestamp") + .option("--shared-only", "Only include traces eligible for sharing/export") + .option("--include-payload", "Include stored payload snapshots in the export") + .option("--out ", "Write export to a file path instead of stdout") + .option("--format ", "Export format: json or ndjson", "ndjson") + .action(async (opts: CompanyFeedbackOptions) => { + try { + const ctx = resolveCommandContext(opts, { requireCompany: true }); + const traces = (await ctx.api.get( + `/api/companies/${ctx.companyId}/feedback-traces${buildFeedbackTraceQuery(opts, opts.includePayload ?? true)}`, + )) ?? []; + const serialized = serializeFeedbackTraces(traces, opts.format); + if (opts.out?.trim()) { + await writeFile(opts.out, serialized, "utf8"); + if (ctx.json) { + printOutput( + { out: opts.out, count: traces.length, format: normalizeFeedbackTraceExportFormat(opts.format) }, + { json: true }, + ); + return; + } + console.log(`Wrote ${traces.length} feedback trace(s) to ${opts.out}`); + return; + } + process.stdout.write(`${serialized}${serialized.endsWith("\n") ? "" : "\n"}`); + } catch (err) { + handleCommandError(err); + } + }), + { includeCompany: false }, + ); + addCommonClientOptions( company .command("export") @@ -1208,13 +1324,13 @@ export function registerCompanyCommands(program: Command): void { | { type: "github"; url: string }; const treatAsLocalPath = !isHttpUrl(from) && await pathExists(from); - const isGithubSource = isGithubUrl(from) || (isGithubShorthand(from) && !treatAsLocalPath); + const isGithubSource = looksLikeRepoUrl(from) || (isGithubShorthand(from) && !treatAsLocalPath); if (isHttpUrl(from) || isGithubSource) { - if (!isGithubUrl(from) && !isGithubShorthand(from)) { + if (!looksLikeRepoUrl(from) && !isGithubShorthand(from)) { throw new Error( "Only GitHub URLs and local paths are supported for import. " + - "Generic HTTP URLs are not supported. Use a GitHub URL (https://github.com/...) or a local directory path.", + "Generic HTTP URLs are not supported. Use a GitHub or GitHub Enterprise URL (https://github.com/... or https://ghe.example.com/...) or a local directory path.", ); } sourcePayload = { type: "github", url: normalizeGithubImportSource(from, opts.ref) }; @@ -1325,6 +1441,12 @@ export function registerCompanyCommands(program: Command): void { if (!imported) { throw new Error("Import request returned no data."); } + const tc = getTelemetryClient(); + if (tc) { + const isPrivate = sourcePayload.type !== "github"; + const sourceRef = sourcePayload.type === "github" ? sourcePayload.url : from; + trackCompanyImported(tc, { sourceType: sourcePayload.type, sourceRef, isPrivate }); + } let companyUrl: string | undefined; if (!ctx.json) { try { diff --git a/cli/src/commands/client/feedback.ts b/cli/src/commands/client/feedback.ts new file mode 100644 index 00000000000..3ac247d9dc8 --- /dev/null +++ b/cli/src/commands/client/feedback.ts @@ -0,0 +1,645 @@ +import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises"; +import path from "node:path"; +import pc from "picocolors"; +import { Command } from "commander"; +import type { Company, FeedbackTrace, FeedbackTraceBundle } from "@paperclipai/shared"; +import { + addCommonClientOptions, + handleCommandError, + printOutput, + resolveCommandContext, + type BaseClientOptions, + type ResolvedClientContext, +} from "./common.js"; + +interface FeedbackFilterOptions extends BaseClientOptions { + targetType?: string; + vote?: string; + status?: string; + projectId?: string; + issueId?: string; + from?: string; + to?: string; + sharedOnly?: boolean; +} + +export interface FeedbackTraceQueryOptions { + targetType?: string; + vote?: string; + status?: string; + projectId?: string; + issueId?: string; + from?: string; + to?: string; + sharedOnly?: boolean; +} + +interface FeedbackReportOptions extends FeedbackFilterOptions { + payloads?: boolean; +} + +interface FeedbackExportOptions extends FeedbackFilterOptions { + out?: string; +} + +interface FeedbackSummary { + total: number; + thumbsUp: number; + thumbsDown: number; + withReason: number; + statuses: Record; +} + +interface FeedbackExportManifest { + exportedAt: string; + serverUrl: string; + companyId: string; + summary: FeedbackSummary & { + uniqueIssues: number; + issues: string[]; + }; + files: { + votes: string[]; + traces: string[]; + fullTraces: string[]; + zip: string; + }; +} + +interface FeedbackExportResult { + outputDir: string; + zipPath: string; + manifest: FeedbackExportManifest; +} + +export function registerFeedbackCommands(program: Command): void { + const feedback = program.command("feedback").description("Inspect and export local feedback traces"); + + addCommonClientOptions( + feedback + .command("report") + .description("Render a terminal report for company feedback traces") + .option("-C, --company-id ", "Company ID (overrides context default)") + .option("--target-type ", "Filter by target type") + .option("--vote ", "Filter by vote value") + .option("--status ", "Filter by trace status") + .option("--project-id ", "Filter by project ID") + .option("--issue-id ", "Filter by issue ID") + .option("--from ", "Only include traces created at or after this timestamp") + .option("--to ", "Only include traces created at or before this timestamp") + .option("--shared-only", "Only include traces eligible for sharing/export") + .option("--payloads", "Include raw payload dumps in the terminal report", false) + .action(async (opts: FeedbackReportOptions) => { + try { + const ctx = resolveCommandContext(opts); + const companyId = await resolveFeedbackCompanyId(ctx, opts.companyId); + const traces = await fetchCompanyFeedbackTraces(ctx, companyId, opts); + const summary = summarizeFeedbackTraces(traces); + if (ctx.json) { + printOutput( + { + apiBase: ctx.api.apiBase, + companyId, + summary, + traces, + }, + { json: true }, + ); + return; + } + console.log(renderFeedbackReport({ + apiBase: ctx.api.apiBase, + companyId, + traces, + summary, + includePayloads: Boolean(opts.payloads), + })); + } catch (err) { + handleCommandError(err); + } + }), + { includeCompany: false }, + ); + + addCommonClientOptions( + feedback + .command("export") + .description("Export feedback votes and raw trace bundles into a folder plus zip archive") + .option("-C, --company-id ", "Company ID (overrides context default)") + .option("--target-type ", "Filter by target type") + .option("--vote ", "Filter by vote value") + .option("--status ", "Filter by trace status") + .option("--project-id ", "Filter by project ID") + .option("--issue-id ", "Filter by issue ID") + .option("--from ", "Only include traces created at or after this timestamp") + .option("--to ", "Only include traces created at or before this timestamp") + .option("--shared-only", "Only include traces eligible for sharing/export") + .option("--out ", "Output directory (default: ./feedback-export-)") + .action(async (opts: FeedbackExportOptions) => { + try { + const ctx = resolveCommandContext(opts); + const companyId = await resolveFeedbackCompanyId(ctx, opts.companyId); + const traces = await fetchCompanyFeedbackTraces(ctx, companyId, opts); + const outputDir = path.resolve(opts.out?.trim() || defaultFeedbackExportDirName()); + const exported = await writeFeedbackExportBundle({ + apiBase: ctx.api.apiBase, + companyId, + traces, + outputDir, + traceBundleFetcher: (trace) => fetchFeedbackTraceBundle(ctx, trace.id), + }); + if (ctx.json) { + printOutput( + { + companyId, + outputDir: exported.outputDir, + zipPath: exported.zipPath, + summary: exported.manifest.summary, + }, + { json: true }, + ); + return; + } + console.log(renderFeedbackExportSummary(exported)); + } catch (err) { + handleCommandError(err); + } + }), + { includeCompany: false }, + ); +} + +export async function resolveFeedbackCompanyId( + ctx: ResolvedClientContext, + explicitCompanyId?: string, +): Promise { + const direct = explicitCompanyId?.trim() || ctx.companyId?.trim(); + if (direct) return direct; + const companies = (await ctx.api.get("/api/companies")) ?? []; + const companyId = companies[0]?.id?.trim(); + if (!companyId) { + throw new Error( + "Company ID is required. Pass --company-id, set PAPERCLIP_COMPANY_ID, or configure a CLI context default.", + ); + } + return companyId; +} + +export function buildFeedbackTraceQuery(opts: FeedbackTraceQueryOptions, includePayload = true): string { + const params = new URLSearchParams(); + if (opts.targetType) params.set("targetType", opts.targetType); + if (opts.vote) params.set("vote", opts.vote); + if (opts.status) params.set("status", opts.status); + if (opts.projectId) params.set("projectId", opts.projectId); + if (opts.issueId) params.set("issueId", opts.issueId); + if (opts.from) params.set("from", opts.from); + if (opts.to) params.set("to", opts.to); + if (opts.sharedOnly) params.set("sharedOnly", "true"); + if (includePayload) params.set("includePayload", "true"); + const query = params.toString(); + return query ? `?${query}` : ""; +} + +export function normalizeFeedbackTraceExportFormat(value: string | undefined): "json" | "ndjson" { + if (!value || value === "ndjson") return "ndjson"; + if (value === "json") return "json"; + throw new Error(`Unsupported export format: ${value}`); +} + +export function serializeFeedbackTraces(traces: FeedbackTrace[], format: string | undefined): string { + if (normalizeFeedbackTraceExportFormat(format) === "json") { + return JSON.stringify(traces, null, 2); + } + return traces.map((trace) => JSON.stringify(trace)).join("\n"); +} + +export async function fetchCompanyFeedbackTraces( + ctx: ResolvedClientContext, + companyId: string, + opts: FeedbackFilterOptions, +): Promise { + return ( + (await ctx.api.get( + `/api/companies/${companyId}/feedback-traces${buildFeedbackTraceQuery(opts, true)}`, + )) ?? [] + ); +} + +export async function fetchFeedbackTraceBundle( + ctx: ResolvedClientContext, + traceId: string, +): Promise { + const bundle = await ctx.api.get(`/api/feedback-traces/${traceId}/bundle`); + if (!bundle) { + throw new Error(`Feedback trace bundle ${traceId} not found`); + } + return bundle; +} + +export function summarizeFeedbackTraces(traces: FeedbackTrace[]): FeedbackSummary { + const statuses: Record = {}; + let thumbsUp = 0; + let thumbsDown = 0; + let withReason = 0; + + for (const trace of traces) { + if (trace.vote === "up") thumbsUp += 1; + if (trace.vote === "down") thumbsDown += 1; + if (readFeedbackReason(trace)) withReason += 1; + statuses[trace.status] = (statuses[trace.status] ?? 0) + 1; + } + + return { + total: traces.length, + thumbsUp, + thumbsDown, + withReason, + statuses, + }; +} + +export function renderFeedbackReport(input: { + apiBase: string; + companyId: string; + traces: FeedbackTrace[]; + summary: FeedbackSummary; + includePayloads: boolean; +}): string { + const lines: string[] = []; + lines.push(""); + lines.push(pc.bold(pc.magenta("Paperclip Feedback Report"))); + lines.push(pc.dim(new Date().toISOString())); + lines.push(horizontalRule()); + lines.push(`${pc.dim("Server:")} ${input.apiBase}`); + lines.push(`${pc.dim("Company:")} ${input.companyId}`); + lines.push(""); + + if (input.traces.length === 0) { + lines.push(pc.yellow("[!!] No feedback traces found.")); + lines.push(""); + return lines.join("\n"); + } + + lines.push(pc.bold(pc.cyan("Summary"))); + lines.push(horizontalRule()); + lines.push(` ${pc.green(pc.bold(String(input.summary.thumbsUp)))} thumbs up`); + lines.push(` ${pc.red(pc.bold(String(input.summary.thumbsDown)))} thumbs down`); + lines.push(` ${pc.yellow(pc.bold(String(input.summary.withReason)))} downvotes with a reason`); + lines.push(` ${pc.bold(String(input.summary.total))} total traces`); + lines.push(""); + lines.push(pc.dim("Export status:")); + for (const status of ["pending", "sent", "local_only", "failed"]) { + lines.push(` ${padRight(status, 10)} ${input.summary.statuses[status] ?? 0}`); + } + lines.push(""); + lines.push(pc.bold(pc.cyan("Trace Details"))); + lines.push(horizontalRule()); + + for (const trace of input.traces) { + const voteColor = trace.vote === "up" ? pc.green : pc.red; + const voteIcon = trace.vote === "up" ? "^" : "v"; + const issueRef = trace.issueIdentifier ?? trace.issueId; + const label = trace.targetSummary.label?.trim() || trace.targetType; + const excerpt = compactText(trace.targetSummary.excerpt); + const reason = readFeedbackReason(trace); + lines.push( + ` ${voteColor(voteIcon)} ${pc.bold(issueRef)} ${pc.dim(compactText(trace.issueTitle, 64))}`, + ); + lines.push( + ` ${pc.dim("Trace:")} ${trace.id.slice(0, 8)} ${pc.dim("Status:")} ${trace.status} ${pc.dim("Date:")} ${formatTimestamp(trace.createdAt)}`, + ); + lines.push(` ${pc.dim("Target:")} ${label}`); + if (excerpt) { + lines.push(` ${pc.dim("Excerpt:")} ${excerpt}`); + } + if (reason) { + lines.push(` ${pc.yellow(pc.bold("Reason:"))} ${pc.yellow(reason)}`); + } + lines.push(""); + } + + if (input.includePayloads) { + lines.push(pc.bold(pc.cyan("Raw Payloads"))); + lines.push(horizontalRule()); + for (const trace of input.traces) { + if (!trace.payloadSnapshot) continue; + const issueRef = trace.issueIdentifier ?? trace.issueId; + lines.push(` ${pc.bold(`${issueRef} (${trace.id.slice(0, 8)})`)}`); + const body = JSON.stringify(trace.payloadSnapshot, null, 2)?.split("\n") ?? []; + for (const line of body) { + lines.push(` ${pc.dim(line)}`); + } + lines.push(""); + } + } + + lines.push(horizontalRule()); + lines.push(pc.dim(`Report complete. ${input.traces.length} trace(s) displayed.`)); + lines.push(""); + return lines.join("\n"); +} + +export async function writeFeedbackExportBundle(input: { + apiBase: string; + companyId: string; + traces: FeedbackTrace[]; + outputDir: string; + traceBundleFetcher?: (trace: FeedbackTrace) => Promise; +}): Promise { + await ensureEmptyOutputDirectory(input.outputDir); + await mkdir(path.join(input.outputDir, "votes"), { recursive: true }); + await mkdir(path.join(input.outputDir, "traces"), { recursive: true }); + await mkdir(path.join(input.outputDir, "full-traces"), { recursive: true }); + + const summary = summarizeFeedbackTraces(input.traces); + const voteFiles: string[] = []; + const traceFiles: string[] = []; + const fullTraceDirs: string[] = []; + const fullTraceFiles: string[] = []; + const issueSet = new Set(); + + for (const trace of input.traces) { + const issueRef = sanitizeFileSegment(trace.issueIdentifier ?? trace.issueId); + const voteRecord = buildFeedbackVoteRecord(trace); + const voteFileName = `${issueRef}-${trace.feedbackVoteId.slice(0, 8)}.json`; + const traceFileName = `${issueRef}-${trace.id.slice(0, 8)}.json`; + voteFiles.push(voteFileName); + traceFiles.push(traceFileName); + issueSet.add(trace.issueIdentifier ?? trace.issueId); + await writeFile( + path.join(input.outputDir, "votes", voteFileName), + `${JSON.stringify(voteRecord, null, 2)}\n`, + "utf8", + ); + await writeFile( + path.join(input.outputDir, "traces", traceFileName), + `${JSON.stringify(trace, null, 2)}\n`, + "utf8", + ); + + if (input.traceBundleFetcher) { + const bundle = await input.traceBundleFetcher(trace); + const bundleDirName = `${issueRef}-${trace.id.slice(0, 8)}`; + const bundleDir = path.join(input.outputDir, "full-traces", bundleDirName); + await mkdir(bundleDir, { recursive: true }); + fullTraceDirs.push(bundleDirName); + await writeFile( + path.join(bundleDir, "bundle.json"), + `${JSON.stringify(bundle, null, 2)}\n`, + "utf8", + ); + fullTraceFiles.push(path.posix.join("full-traces", bundleDirName, "bundle.json")); + for (const file of bundle.files) { + const targetPath = path.join(bundleDir, file.path); + await mkdir(path.dirname(targetPath), { recursive: true }); + await writeFile(targetPath, file.contents, "utf8"); + fullTraceFiles.push(path.posix.join("full-traces", bundleDirName, file.path.replace(/\\/g, "/"))); + } + } + } + + const zipPath = `${input.outputDir}.zip`; + const manifest: FeedbackExportManifest = { + exportedAt: new Date().toISOString(), + serverUrl: input.apiBase, + companyId: input.companyId, + summary: { + ...summary, + uniqueIssues: issueSet.size, + issues: Array.from(issueSet).sort((left, right) => left.localeCompare(right)), + }, + files: { + votes: voteFiles.slice().sort((left, right) => left.localeCompare(right)), + traces: traceFiles.slice().sort((left, right) => left.localeCompare(right)), + fullTraces: fullTraceDirs.slice().sort((left, right) => left.localeCompare(right)), + zip: path.basename(zipPath), + }, + }; + + await writeFile( + path.join(input.outputDir, "index.json"), + `${JSON.stringify(manifest, null, 2)}\n`, + "utf8", + ); + const archiveFiles = await collectJsonFilesForArchive(input.outputDir, [ + "index.json", + ...manifest.files.votes.map((file) => path.posix.join("votes", file)), + ...manifest.files.traces.map((file) => path.posix.join("traces", file)), + ...fullTraceFiles, + ]); + await writeFile(zipPath, createStoredZipArchive(archiveFiles, path.basename(input.outputDir))); + + return { + outputDir: input.outputDir, + zipPath, + manifest, + }; +} + +export function renderFeedbackExportSummary(exported: FeedbackExportResult): string { + const lines: string[] = []; + lines.push(""); + lines.push(pc.bold(pc.magenta("Paperclip Feedback Export"))); + lines.push(pc.dim(exported.manifest.exportedAt)); + lines.push(horizontalRule()); + lines.push(`${pc.dim("Company:")} ${exported.manifest.companyId}`); + lines.push(`${pc.dim("Output:")} ${exported.outputDir}`); + lines.push(`${pc.dim("Archive:")} ${exported.zipPath}`); + lines.push(""); + lines.push(pc.bold("Export Summary")); + lines.push(horizontalRule()); + lines.push(` ${pc.green(pc.bold(String(exported.manifest.summary.thumbsUp)))} thumbs up`); + lines.push(` ${pc.red(pc.bold(String(exported.manifest.summary.thumbsDown)))} thumbs down`); + lines.push(` ${pc.yellow(pc.bold(String(exported.manifest.summary.withReason)))} with reason`); + lines.push(` ${pc.bold(String(exported.manifest.summary.uniqueIssues))} unique issues`); + lines.push(""); + lines.push(pc.dim("Files:")); + lines.push(` ${path.join(exported.outputDir, "index.json")}`); + lines.push(` ${path.join(exported.outputDir, "votes")} (${exported.manifest.files.votes.length} files)`); + lines.push(` ${path.join(exported.outputDir, "traces")} (${exported.manifest.files.traces.length} files)`); + lines.push(` ${path.join(exported.outputDir, "full-traces")} (${exported.manifest.files.fullTraces.length} bundles)`); + lines.push(` ${exported.zipPath}`); + lines.push(""); + return lines.join("\n"); +} + +function readFeedbackReason(trace: FeedbackTrace): string | null { + const payload = asRecord(trace.payloadSnapshot); + const vote = asRecord(payload?.vote); + const reason = vote?.reason; + return typeof reason === "string" && reason.trim() ? reason.trim() : null; +} + +function buildFeedbackVoteRecord(trace: FeedbackTrace) { + return { + voteId: trace.feedbackVoteId, + traceId: trace.id, + issueId: trace.issueId, + issueIdentifier: trace.issueIdentifier, + issueTitle: trace.issueTitle, + vote: trace.vote, + targetType: trace.targetType, + targetId: trace.targetId, + targetSummary: trace.targetSummary, + status: trace.status, + consentVersion: trace.consentVersion, + createdAt: trace.createdAt, + updatedAt: trace.updatedAt, + reason: readFeedbackReason(trace), + }; +} + +function asRecord(value: unknown): Record | null { + if (!value || typeof value !== "object" || Array.isArray(value)) return null; + return value as Record; +} + +function compactText(value: string | null | undefined, maxLength = 88): string | null { + if (!value) return null; + const compact = value.replace(/\s+/g, " ").trim(); + if (!compact) return null; + if (compact.length <= maxLength) return compact; + return `${compact.slice(0, maxLength - 3)}...`; +} + +function formatTimestamp(value: unknown): string { + if (value instanceof Date) return value.toISOString().slice(0, 19).replace("T", " "); + if (typeof value === "string") return value.slice(0, 19).replace("T", " "); + return "-"; +} + +function horizontalRule(): string { + return pc.dim("-".repeat(72)); +} + +function padRight(value: string, width: number): string { + return `${value}${" ".repeat(Math.max(0, width - value.length))}`; +} + +function defaultFeedbackExportDirName(): string { + const iso = new Date().toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z"); + return `feedback-export-${iso}`; +} + +async function ensureEmptyOutputDirectory(outputDir: string): Promise { + try { + const info = await stat(outputDir); + if (!info.isDirectory()) { + throw new Error(`Output path already exists and is not a directory: ${outputDir}`); + } + const entries = await readdir(outputDir); + if (entries.length > 0) { + throw new Error(`Output directory already exists and is not empty: ${outputDir}`); + } + } catch (error) { + const message = error instanceof Error ? error.message : ""; + if (/ENOENT/.test(message)) { + await mkdir(outputDir, { recursive: true }); + return; + } + throw error; + } +} + +async function collectJsonFilesForArchive( + outputDir: string, + relativePaths: string[], +): Promise> { + const files: Record = {}; + for (const relativePath of relativePaths) { + const normalized = relativePath.replace(/\\/g, "/"); + files[normalized] = await readFile(path.join(outputDir, normalized), "utf8"); + } + return files; +} + +function sanitizeFileSegment(value: string): string { + return value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "feedback"; +} + +function writeUint16(target: Uint8Array, offset: number, value: number) { + target[offset] = value & 0xff; + target[offset + 1] = (value >>> 8) & 0xff; +} + +function writeUint32(target: Uint8Array, offset: number, value: number) { + target[offset] = value & 0xff; + target[offset + 1] = (value >>> 8) & 0xff; + target[offset + 2] = (value >>> 16) & 0xff; + target[offset + 3] = (value >>> 24) & 0xff; +} + +function crc32(bytes: Uint8Array) { + let crc = 0xffffffff; + for (const byte of bytes) { + crc ^= byte; + for (let bit = 0; bit < 8; bit += 1) { + crc = (crc & 1) === 1 ? (crc >>> 1) ^ 0xedb88320 : crc >>> 1; + } + } + return (crc ^ 0xffffffff) >>> 0; +} + +function createStoredZipArchive(files: Record, rootPath: string): Uint8Array { + const encoder = new TextEncoder(); + const localChunks: Uint8Array[] = []; + const centralChunks: Uint8Array[] = []; + let localOffset = 0; + let entryCount = 0; + + for (const [relativePath, content] of Object.entries(files).sort(([left], [right]) => left.localeCompare(right))) { + const fileName = encoder.encode(`${rootPath}/${relativePath}`); + const body = encoder.encode(content); + const checksum = crc32(body); + + const localHeader = new Uint8Array(30 + fileName.length); + writeUint32(localHeader, 0, 0x04034b50); + writeUint16(localHeader, 4, 20); + writeUint16(localHeader, 6, 0x0800); + writeUint16(localHeader, 8, 0); + writeUint32(localHeader, 14, checksum); + writeUint32(localHeader, 18, body.length); + writeUint32(localHeader, 22, body.length); + writeUint16(localHeader, 26, fileName.length); + localHeader.set(fileName, 30); + + const centralHeader = new Uint8Array(46 + fileName.length); + writeUint32(centralHeader, 0, 0x02014b50); + writeUint16(centralHeader, 4, 20); + writeUint16(centralHeader, 6, 20); + writeUint16(centralHeader, 8, 0x0800); + writeUint16(centralHeader, 10, 0); + writeUint32(centralHeader, 16, checksum); + writeUint32(centralHeader, 20, body.length); + writeUint32(centralHeader, 24, body.length); + writeUint16(centralHeader, 28, fileName.length); + writeUint32(centralHeader, 42, localOffset); + centralHeader.set(fileName, 46); + + localChunks.push(localHeader, body); + centralChunks.push(centralHeader); + localOffset += localHeader.length + body.length; + entryCount += 1; + } + + const centralDirectoryLength = centralChunks.reduce((sum, chunk) => sum + chunk.length, 0); + const archive = new Uint8Array( + localChunks.reduce((sum, chunk) => sum + chunk.length, 0) + centralDirectoryLength + 22, + ); + let offset = 0; + for (const chunk of localChunks) { + archive.set(chunk, offset); + offset += chunk.length; + } + const centralDirectoryOffset = offset; + for (const chunk of centralChunks) { + archive.set(chunk, offset); + offset += chunk.length; + } + writeUint32(archive, offset, 0x06054b50); + writeUint16(archive, offset + 8, entryCount); + writeUint16(archive, offset + 10, entryCount); + writeUint32(archive, offset + 12, centralDirectoryLength); + writeUint32(archive, offset + 16, centralDirectoryOffset); + return archive; +} diff --git a/cli/src/commands/client/issue.ts b/cli/src/commands/client/issue.ts index 8db617d96d0..921c077e27e 100644 --- a/cli/src/commands/client/issue.ts +++ b/cli/src/commands/client/issue.ts @@ -1,8 +1,10 @@ import { Command } from "commander"; +import { writeFile } from "node:fs/promises"; import { addIssueCommentSchema, checkoutIssueSchema, createIssueSchema, + type FeedbackTrace, updateIssueSchema, type Issue, type IssueComment, @@ -15,6 +17,11 @@ import { resolveCommandContext, type BaseClientOptions, } from "./common.js"; +import { + buildFeedbackTraceQuery, + normalizeFeedbackTraceExportFormat, + serializeFeedbackTraces, +} from "./feedback.js"; interface IssueBaseOptions extends BaseClientOptions { status?: string; @@ -61,6 +68,18 @@ interface IssueCheckoutOptions extends BaseClientOptions { expectedStatuses?: string; } +interface IssueFeedbackOptions extends BaseClientOptions { + targetType?: string; + vote?: string; + status?: string; + from?: string; + to?: string; + sharedOnly?: boolean; + includePayload?: boolean; + out?: string; + format?: string; +} + export function registerIssueCommands(program: Command): void { const issue = program.command("issue").description("Issue operations"); @@ -237,6 +256,85 @@ export function registerIssueCommands(program: Command): void { }), ); + addCommonClientOptions( + issue + .command("feedback:list") + .description("List feedback traces for an issue") + .argument("", "Issue ID") + .option("--target-type ", "Filter by target type") + .option("--vote ", "Filter by vote value") + .option("--status ", "Filter by trace status") + .option("--from ", "Only include traces created at or after this timestamp") + .option("--to ", "Only include traces created at or before this timestamp") + .option("--shared-only", "Only include traces eligible for sharing/export") + .option("--include-payload", "Include stored payload snapshots in the response") + .action(async (issueId: string, opts: IssueFeedbackOptions) => { + try { + const ctx = resolveCommandContext(opts); + const traces = (await ctx.api.get( + `/api/issues/${issueId}/feedback-traces${buildFeedbackTraceQuery(opts)}`, + )) ?? []; + if (ctx.json) { + printOutput(traces, { json: true }); + return; + } + printOutput( + traces.map((trace) => ({ + id: trace.id, + issue: trace.issueIdentifier ?? trace.issueId, + vote: trace.vote, + status: trace.status, + targetType: trace.targetType, + target: trace.targetSummary.label, + })), + { json: false }, + ); + } catch (err) { + handleCommandError(err); + } + }), + ); + + addCommonClientOptions( + issue + .command("feedback:export") + .description("Export feedback traces for an issue") + .argument("", "Issue ID") + .option("--target-type ", "Filter by target type") + .option("--vote ", "Filter by vote value") + .option("--status ", "Filter by trace status") + .option("--from ", "Only include traces created at or after this timestamp") + .option("--to ", "Only include traces created at or before this timestamp") + .option("--shared-only", "Only include traces eligible for sharing/export") + .option("--include-payload", "Include stored payload snapshots in the export") + .option("--out ", "Write export to a file path instead of stdout") + .option("--format ", "Export format: json or ndjson", "ndjson") + .action(async (issueId: string, opts: IssueFeedbackOptions) => { + try { + const ctx = resolveCommandContext(opts); + const traces = (await ctx.api.get( + `/api/issues/${issueId}/feedback-traces${buildFeedbackTraceQuery(opts, opts.includePayload ?? true)}`, + )) ?? []; + const serialized = serializeFeedbackTraces(traces, opts.format); + if (opts.out?.trim()) { + await writeFile(opts.out, serialized, "utf8"); + if (ctx.json) { + printOutput( + { out: opts.out, count: traces.length, format: normalizeFeedbackTraceExportFormat(opts.format) }, + { json: true }, + ); + return; + } + console.log(`Wrote ${traces.length} feedback trace(s) to ${opts.out}`); + return; + } + process.stdout.write(`${serialized}${serialized.endsWith("\n") ? "" : "\n"}`); + } catch (err) { + handleCommandError(err); + } + }), + ); + addCommonClientOptions( issue .command("checkout") diff --git a/cli/src/commands/configure.ts b/cli/src/commands/configure.ts index 969ead97d48..83ff089be61 100644 --- a/cli/src/commands/configure.ts +++ b/cli/src/commands/configure.ts @@ -63,6 +63,9 @@ function defaultConfig(): PaperclipConfig { baseUrlMode: "auto", disableSignUp: false, }, + telemetry: { + enabled: true, + }, storage: defaultStorageConfig(), secrets: defaultSecretsConfig(), }; diff --git a/cli/src/commands/onboard.ts b/cli/src/commands/onboard.ts index d470354f8d1..d9b325a8c17 100644 --- a/cli/src/commands/onboard.ts +++ b/cli/src/commands/onboard.ts @@ -33,6 +33,11 @@ import { } from "../config/home.js"; import { bootstrapCeoInvite } from "./auth-bootstrap-ceo.js"; import { printPaperclipCliBanner } from "../utils/banner.js"; +import { + getTelemetryClient, + trackInstallStarted, + trackInstallCompleted, +} from "../telemetry.js"; type SetupMode = "quickstart" | "advanced"; @@ -356,6 +361,9 @@ export async function onboard(opts: OnboardOptions): Promise { setupMode = setupModeChoice as SetupMode; } + const tc = getTelemetryClient(); + if (tc) trackInstallStarted(tc); + let llm: PaperclipConfig["llm"] | undefined; const { defaults: derivedDefaults, usedEnvKeys, ignoredEnvKeys } = quickstartDefaultsFromEnv(); let { @@ -488,6 +496,9 @@ export async function onboard(opts: OnboardOptions): Promise { logging, server, auth, + telemetry: { + enabled: true, + }, storage, secrets, }; @@ -501,6 +512,10 @@ export async function onboard(opts: OnboardOptions): Promise { writeConfig(config, opts.config); + if (tc) trackInstallCompleted(tc, { + adapterType: server.deploymentMode, + }); + p.note( [ `Database: ${database.mode}`, diff --git a/cli/src/commands/routines.ts b/cli/src/commands/routines.ts new file mode 100644 index 00000000000..4c5a3ca9677 --- /dev/null +++ b/cli/src/commands/routines.ts @@ -0,0 +1,352 @@ +import fs from "node:fs"; +import net from "node:net"; +import path from "node:path"; +import { Command } from "commander"; +import pc from "picocolors"; +import { + applyPendingMigrations, + createDb, + createEmbeddedPostgresLogBuffer, + ensurePostgresDatabase, + formatEmbeddedPostgresError, + routines, +} from "@paperclipai/db"; +import { eq, inArray } from "drizzle-orm"; +import { loadPaperclipEnvFile } from "../config/env.js"; +import { readConfig, resolveConfigPath } from "../config/store.js"; + +type RoutinesDisableAllOptions = { + config?: string; + dataDir?: string; + companyId?: string; + json?: boolean; +}; + +type DisableAllRoutinesResult = { + companyId: string; + totalRoutines: number; + pausedCount: number; + alreadyPausedCount: number; + archivedCount: number; +}; + +type EmbeddedPostgresInstance = { + initialise(): Promise; + start(): Promise; + stop(): Promise; +}; + +type EmbeddedPostgresCtor = new (opts: { + databaseDir: string; + user: string; + password: string; + port: number; + persistent: boolean; + initdbFlags?: string[]; + onLog?: (message: unknown) => void; + onError?: (message: unknown) => void; +}) => EmbeddedPostgresInstance; + +type EmbeddedPostgresHandle = { + port: number; + startedByThisProcess: boolean; + stop: () => Promise; +}; + +type ClosableDb = ReturnType & { + $client?: { + end?: (options?: { timeout?: number }) => Promise; + }; +}; + +function nonEmpty(value: string | null | undefined): string | null { + return typeof value === "string" && value.trim().length > 0 ? value.trim() : null; +} + +async function isPortAvailable(port: number): Promise { + return await new Promise((resolve) => { + const server = net.createServer(); + server.unref(); + server.once("error", () => resolve(false)); + server.listen(port, "127.0.0.1", () => { + server.close(() => resolve(true)); + }); + }); +} + +async function findAvailablePort(preferredPort: number): Promise { + let port = Math.max(1, Math.trunc(preferredPort)); + while (!(await isPortAvailable(port))) { + port += 1; + } + return port; +} + +function readPidFilePort(postmasterPidFile: string): number | null { + if (!fs.existsSync(postmasterPidFile)) return null; + try { + const lines = fs.readFileSync(postmasterPidFile, "utf8").split("\n"); + const port = Number(lines[3]?.trim()); + return Number.isInteger(port) && port > 0 ? port : null; + } catch { + return null; + } +} + +function readRunningPostmasterPid(postmasterPidFile: string): number | null { + if (!fs.existsSync(postmasterPidFile)) return null; + try { + const pid = Number(fs.readFileSync(postmasterPidFile, "utf8").split("\n")[0]?.trim()); + if (!Number.isInteger(pid) || pid <= 0) return null; + process.kill(pid, 0); + return pid; + } catch { + return null; + } +} + +async function ensureEmbeddedPostgres(dataDir: string, preferredPort: number): Promise { + const moduleName = "embedded-postgres"; + let EmbeddedPostgres: EmbeddedPostgresCtor; + try { + const mod = await import(moduleName); + EmbeddedPostgres = mod.default as EmbeddedPostgresCtor; + } catch { + throw new Error( + "Embedded PostgreSQL support requires dependency `embedded-postgres`. Reinstall dependencies and try again.", + ); + } + + const postmasterPidFile = path.resolve(dataDir, "postmaster.pid"); + const runningPid = readRunningPostmasterPid(postmasterPidFile); + if (runningPid) { + return { + port: readPidFilePort(postmasterPidFile) ?? preferredPort, + startedByThisProcess: false, + stop: async () => {}, + }; + } + + const port = await findAvailablePort(preferredPort); + const logBuffer = createEmbeddedPostgresLogBuffer(); + const instance = new EmbeddedPostgres({ + databaseDir: dataDir, + user: "paperclip", + password: "paperclip", + port, + persistent: true, + initdbFlags: ["--encoding=UTF8", "--locale=C", "--lc-messages=C"], + onLog: logBuffer.append, + onError: logBuffer.append, + }); + + if (!fs.existsSync(path.resolve(dataDir, "PG_VERSION"))) { + try { + await instance.initialise(); + } catch (error) { + throw formatEmbeddedPostgresError(error, { + fallbackMessage: `Failed to initialize embedded PostgreSQL cluster in ${dataDir} on port ${port}`, + recentLogs: logBuffer.getRecentLogs(), + }); + } + } + + if (fs.existsSync(postmasterPidFile)) { + fs.rmSync(postmasterPidFile, { force: true }); + } + + try { + await instance.start(); + } catch (error) { + throw formatEmbeddedPostgresError(error, { + fallbackMessage: `Failed to start embedded PostgreSQL on port ${port}`, + recentLogs: logBuffer.getRecentLogs(), + }); + } + + return { + port, + startedByThisProcess: true, + stop: async () => { + await instance.stop(); + }, + }; +} + +async function closeDb(db: ClosableDb): Promise { + await db.$client?.end?.({ timeout: 5 }).catch(() => undefined); +} + +async function openConfiguredDb(configPath: string): Promise<{ + db: ClosableDb; + stop: () => Promise; +}> { + const config = readConfig(configPath); + if (!config) { + throw new Error(`Config not found at ${configPath}.`); + } + + let embeddedHandle: EmbeddedPostgresHandle | null = null; + try { + if (config.database.mode === "embedded-postgres") { + embeddedHandle = await ensureEmbeddedPostgres( + config.database.embeddedPostgresDataDir, + config.database.embeddedPostgresPort, + ); + const adminConnectionString = `postgres://paperclip:paperclip@127.0.0.1:${embeddedHandle.port}/postgres`; + await ensurePostgresDatabase(adminConnectionString, "paperclip"); + const connectionString = `postgres://paperclip:paperclip@127.0.0.1:${embeddedHandle.port}/paperclip`; + await applyPendingMigrations(connectionString); + const db = createDb(connectionString) as ClosableDb; + return { + db, + stop: async () => { + await closeDb(db); + if (embeddedHandle?.startedByThisProcess) { + await embeddedHandle.stop().catch(() => undefined); + } + }, + }; + } + + const connectionString = nonEmpty(config.database.connectionString); + if (!connectionString) { + throw new Error(`Config at ${configPath} does not define a database connection string.`); + } + + await applyPendingMigrations(connectionString); + const db = createDb(connectionString) as ClosableDb; + return { + db, + stop: async () => { + await closeDb(db); + }, + }; + } catch (error) { + if (embeddedHandle?.startedByThisProcess) { + await embeddedHandle.stop().catch(() => undefined); + } + throw error; + } +} + +export async function disableAllRoutinesInConfig( + options: Pick, +): Promise { + const configPath = resolveConfigPath(options.config); + loadPaperclipEnvFile(configPath); + const companyId = + nonEmpty(options.companyId) + ?? nonEmpty(process.env.PAPERCLIP_COMPANY_ID) + ?? null; + if (!companyId) { + throw new Error("Company ID is required. Pass --company-id or set PAPERCLIP_COMPANY_ID."); + } + + const config = readConfig(configPath); + if (!config) { + throw new Error(`Config not found at ${configPath}.`); + } + + let embeddedHandle: EmbeddedPostgresHandle | null = null; + let db: ClosableDb | null = null; + try { + if (config.database.mode === "embedded-postgres") { + embeddedHandle = await ensureEmbeddedPostgres( + config.database.embeddedPostgresDataDir, + config.database.embeddedPostgresPort, + ); + const adminConnectionString = `postgres://paperclip:paperclip@127.0.0.1:${embeddedHandle.port}/postgres`; + await ensurePostgresDatabase(adminConnectionString, "paperclip"); + const connectionString = `postgres://paperclip:paperclip@127.0.0.1:${embeddedHandle.port}/paperclip`; + await applyPendingMigrations(connectionString); + db = createDb(connectionString) as ClosableDb; + } else { + const connectionString = nonEmpty(config.database.connectionString); + if (!connectionString) { + throw new Error(`Config at ${configPath} does not define a database connection string.`); + } + await applyPendingMigrations(connectionString); + db = createDb(connectionString) as ClosableDb; + } + + const existing = await db + .select({ + id: routines.id, + status: routines.status, + }) + .from(routines) + .where(eq(routines.companyId, companyId)); + + const alreadyPausedCount = existing.filter((routine) => routine.status === "paused").length; + const archivedCount = existing.filter((routine) => routine.status === "archived").length; + const idsToPause = existing + .filter((routine) => routine.status !== "paused" && routine.status !== "archived") + .map((routine) => routine.id); + + if (idsToPause.length > 0) { + await db + .update(routines) + .set({ + status: "paused", + updatedAt: new Date(), + }) + .where(inArray(routines.id, idsToPause)); + } + + return { + companyId, + totalRoutines: existing.length, + pausedCount: idsToPause.length, + alreadyPausedCount, + archivedCount, + }; + } finally { + if (db) { + await closeDb(db); + } + if (embeddedHandle?.startedByThisProcess) { + await embeddedHandle.stop().catch(() => undefined); + } + } +} + +export async function disableAllRoutinesCommand(options: RoutinesDisableAllOptions): Promise { + const result = await disableAllRoutinesInConfig(options); + + if (options.json) { + console.log(JSON.stringify(result, null, 2)); + return; + } + + if (result.totalRoutines === 0) { + console.log(pc.dim(`No routines found for company ${result.companyId}.`)); + return; + } + + console.log( + `Paused ${result.pausedCount} routine(s) for company ${result.companyId} ` + + `(${result.alreadyPausedCount} already paused, ${result.archivedCount} archived).`, + ); +} + +export function registerRoutineCommands(program: Command): void { + const routinesCommand = program.command("routines").description("Local routine maintenance commands"); + + routinesCommand + .command("disable-all") + .description("Pause all non-archived routines in the configured local instance for one company") + .option("-c, --config ", "Path to config file") + .option("-d, --data-dir ", "Paperclip data directory root (isolates state from ~/.paperclip)") + .option("-C, --company-id ", "Company ID") + .option("--json", "Output raw JSON") + .action(async (opts: RoutinesDisableAllOptions) => { + try { + await disableAllRoutinesCommand(opts); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + console.error(pc.red(message)); + process.exit(1); + } + }); +} diff --git a/cli/src/commands/worktree-lib.ts b/cli/src/commands/worktree-lib.ts index 5249acc29ac..d2b6c5f734d 100644 --- a/cli/src/commands/worktree-lib.ts +++ b/cli/src/commands/worktree-lib.ts @@ -224,6 +224,9 @@ export function buildWorktreeConfig(input: { ...(authPublicBaseUrl ? { publicBaseUrl: authPublicBaseUrl } : {}), disableSignUp: source?.auth.disableSignUp ?? false, }, + telemetry: { + enabled: source?.telemetry?.enabled ?? true, + }, storage: { provider: source?.storage.provider ?? "local_disk", localDisk: { diff --git a/cli/src/commands/worktree.ts b/cli/src/commands/worktree.ts index 65e748497fd..963ae5e82d3 100644 --- a/cli/src/commands/worktree.ts +++ b/cli/src/commands/worktree.ts @@ -80,6 +80,7 @@ import { type WorktreeInitOptions = { name?: string; + color?: string; instance?: string; home?: string; fromConfig?: string; @@ -116,6 +117,17 @@ type WorktreeMergeHistoryOptions = { yes?: boolean; }; +type WorktreeReseedOptions = { + from?: string; + to?: string; + fromConfig?: string; + fromDataDir?: string; + fromInstance?: string; + seedMode?: string; + yes?: boolean; + allowLiveTarget?: boolean; +}; + type EmbeddedPostgresInstance = { initialise(): Promise; start(): Promise; @@ -721,6 +733,65 @@ export function resolveSourceConfigPath(opts: WorktreeInitOptions): string { return path.resolve(sourceHome, "instances", sourceInstanceId, "config.json"); } +export function resolveWorktreeReseedSource(input: WorktreeReseedOptions): ResolvedWorktreeReseedSource { + const fromSelector = nonEmpty(input.from); + const fromConfig = nonEmpty(input.fromConfig); + const fromDataDir = nonEmpty(input.fromDataDir); + const fromInstance = nonEmpty(input.fromInstance); + const hasExplicitConfigSource = Boolean(fromConfig || fromDataDir || fromInstance); + + if (fromSelector && hasExplicitConfigSource) { + throw new Error( + "Use either --from or --from-config/--from-data-dir/--from-instance, not both.", + ); + } + + if (fromSelector) { + const endpoint = resolveWorktreeEndpointFromSelector(fromSelector, { allowCurrent: true }); + return { + configPath: endpoint.configPath, + label: endpoint.label, + }; + } + + if (hasExplicitConfigSource) { + const configPath = resolveSourceConfigPath({ + fromConfig: fromConfig ?? undefined, + fromDataDir: fromDataDir ?? undefined, + fromInstance: fromInstance ?? undefined, + }); + return { + configPath, + label: configPath, + }; + } + + throw new Error( + "Pass --from or --from-config/--from-instance explicitly so the reseed source is unambiguous.", + ); +} + +export function resolveWorktreeReseedTargetPaths(input: { + configPath: string; + rootPath: string; +}): WorktreeLocalPaths { + const envEntries = readPaperclipEnvEntries(resolvePaperclipEnvFile(input.configPath)); + const homeDir = nonEmpty(envEntries.PAPERCLIP_HOME); + const instanceId = nonEmpty(envEntries.PAPERCLIP_INSTANCE_ID); + + if (!homeDir || !instanceId) { + throw new Error( + `Target config ${input.configPath} does not look like a worktree-local Paperclip instance. Expected PAPERCLIP_HOME and PAPERCLIP_INSTANCE_ID in the adjacent .env.`, + ); + } + + return resolveWorktreeLocalPaths({ + cwd: input.rootPath, + homeDir, + instanceId, + }); +} + function resolveSourceConnectionString(config: PaperclipConfig, envEntries: Record, portOverride?: number): string { if (config.database.mode === "postgres") { const connectionString = nonEmpty(envEntries.DATABASE_URL) ?? nonEmpty(config.database.connectionString); @@ -877,6 +948,8 @@ async function seedWorktreeDatabase(input: { input.sourceConfig.database.embeddedPostgresDataDir, input.sourceConfig.database.embeddedPostgresPort, ); + const sourceAdminConnectionString = `postgres://paperclip:paperclip@127.0.0.1:${sourceHandle.port}/postgres`; + await ensurePostgresDatabase(sourceAdminConnectionString, "paperclip"); } const sourceConnectionString = resolveSourceConnectionString( input.sourceConfig, @@ -942,8 +1015,8 @@ async function runWorktreeInit(opts: WorktreeInitOptions): Promise { instanceId, }); const branding = { - name: worktreeName, - color: generateWorktreeColor(), + name: opts.name ?? worktreeName, + color: opts.color ?? generateWorktreeColor(), }; const sourceConfigPath = resolveSourceConfigPath(opts); const sourceConfig = existsSync(sourceConfigPath) ? readConfig(sourceConfigPath) : null; @@ -1155,6 +1228,11 @@ type ResolvedWorktreeEndpoint = { isCurrent: boolean; }; +type ResolvedWorktreeReseedSource = { + configPath: string; + label: string; +}; + function parseGitWorktreeList(cwd: string): GitWorktreeListEntry[] { const raw = execFileSync("git", ["worktree", "list", "--porcelain"], { cwd, @@ -1648,6 +1726,13 @@ function renderMergePlan(plan: Awaited>["pla return lines.join("\n"); } +function resolveRunningEmbeddedPostgresPid(config: PaperclipConfig): number | null { + if (config.database.mode !== "embedded-postgres") { + return null; + } + return readRunningPostmasterPid(path.resolve(config.database.embeddedPostgresDataDir, "postmaster.pid")); +} + async function collectMergePlan(input: { sourceDb: ClosableDb; targetDb: ClosableDb; @@ -2589,6 +2674,89 @@ export async function worktreeMergeHistoryCommand(sourceArg: string | undefined, } } +export async function worktreeReseedCommand(opts: WorktreeReseedOptions): Promise { + printPaperclipCliBanner(); + p.intro(pc.bgCyan(pc.black(" paperclipai worktree reseed "))); + + const seedMode = opts.seedMode ?? "full"; + if (!isWorktreeSeedMode(seedMode)) { + throw new Error(`Unsupported seed mode "${seedMode}". Expected one of: minimal, full.`); + } + + const targetEndpoint = opts.to + ? resolveWorktreeEndpointFromSelector(opts.to, { allowCurrent: true }) + : resolveCurrentEndpoint(); + const source = resolveWorktreeReseedSource(opts); + + if (path.resolve(source.configPath) === path.resolve(targetEndpoint.configPath)) { + throw new Error("Source and target Paperclip configs are the same. Choose different --from/--to values."); + } + if (!existsSync(source.configPath)) { + throw new Error(`Source config not found at ${source.configPath}.`); + } + + const targetConfig = readConfig(targetEndpoint.configPath); + if (!targetConfig) { + throw new Error(`Target config not found at ${targetEndpoint.configPath}.`); + } + const sourceConfig = readConfig(source.configPath); + if (!sourceConfig) { + throw new Error(`Source config not found at ${source.configPath}.`); + } + + const targetPaths = resolveWorktreeReseedTargetPaths({ + configPath: targetEndpoint.configPath, + rootPath: targetEndpoint.rootPath, + }); + const runningTargetPid = resolveRunningEmbeddedPostgresPid(targetConfig); + if (runningTargetPid && !opts.allowLiveTarget) { + throw new Error( + `Target worktree database appears to be running (pid ${runningTargetPid}). Stop Paperclip in ${targetEndpoint.rootPath} before reseeding, or re-run with --allow-live-target if you want to override this guard.`, + ); + } + + const confirmed = opts.yes + ? true + : await p.confirm({ + message: `Overwrite the isolated Paperclip DB for ${targetEndpoint.label} from ${source.label} using ${seedMode} seed mode?`, + initialValue: false, + }); + if (p.isCancel(confirmed) || !confirmed) { + p.log.warn("Reseed cancelled."); + return; + } + + if (runningTargetPid && opts.allowLiveTarget) { + p.log.warning(`Proceeding even though the target embedded PostgreSQL appears to be running (pid ${runningTargetPid}).`); + } + + const spinner = p.spinner(); + spinner.start(`Reseeding ${targetEndpoint.label} from ${source.label} (${seedMode})...`); + try { + const seeded = await seedWorktreeDatabase({ + sourceConfigPath: source.configPath, + sourceConfig, + targetConfig, + targetPaths, + instanceId: targetPaths.instanceId, + seedMode, + }); + spinner.stop(`Reseeded ${targetEndpoint.label} (${seedMode}).`); + p.log.message(pc.dim(`Source: ${source.configPath}`)); + p.log.message(pc.dim(`Target: ${targetEndpoint.configPath}`)); + p.log.message(pc.dim(`Seed snapshot: ${seeded.backupSummary}`)); + for (const rebound of seeded.reboundWorkspaces) { + p.log.message( + pc.dim(`Rebound workspace ${rebound.name}: ${rebound.fromCwd} -> ${rebound.toCwd}`), + ); + } + p.outro(pc.green(`Reseed complete for ${targetEndpoint.label}.`)); + } catch (error) { + spinner.stop(pc.red("Failed to reseed worktree database.")); + throw error; + } +} + export function registerWorktreeCommands(program: Command): void { const worktree = program.command("worktree").description("Worktree-local Paperclip instance helpers"); @@ -2651,6 +2819,19 @@ export function registerWorktreeCommands(program: Command): void { .option("--yes", "Skip the interactive confirmation prompt when applying", false) .action(worktreeMergeHistoryCommand); + worktree + .command("reseed") + .description("Re-seed an existing worktree-local instance from another Paperclip instance or worktree") + .option("--from ", "Source worktree path, directory name, branch name, or current") + .option("--to ", "Target worktree path, directory name, branch name, or current (defaults to current)") + .option("--from-config ", "Source config.json to seed from") + .option("--from-data-dir ", "Source PAPERCLIP_HOME used when deriving the source config") + .option("--from-instance ", "Source instance id when deriving the source config") + .option("--seed-mode ", "Seed profile: minimal or full (default: full)", "full") + .option("--yes", "Skip the destructive confirmation prompt", false) + .option("--allow-live-target", "Override the guard that requires the target worktree DB to be stopped first", false) + .action(worktreeReseedCommand); + program .command("worktree:cleanup") .description("Safely remove a worktree, its branch, and its isolated instance data") diff --git a/cli/src/config/schema.ts b/cli/src/config/schema.ts index 12316faacdf..65ddeab7334 100644 --- a/cli/src/config/schema.ts +++ b/cli/src/config/schema.ts @@ -7,6 +7,7 @@ export { loggingConfigSchema, serverConfigSchema, authConfigSchema, + telemetryConfigSchema, storageConfigSchema, storageLocalDiskConfigSchema, storageS3ConfigSchema, @@ -19,10 +20,11 @@ export { type LoggingConfig, type ServerConfig, type AuthConfig, + type TelemetryConfig, type StorageConfig, type StorageLocalDiskConfig, type StorageS3Config, type SecretsConfig, type SecretsLocalEncryptedConfig, type ConfigMeta, -} from "@paperclipai/shared"; +} from "../../../packages/shared/src/config-schema.js"; diff --git a/cli/src/index.ts b/cli/src/index.ts index 828404e842d..1173945961f 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -15,11 +15,15 @@ import { registerAgentCommands } from "./commands/client/agent.js"; import { registerApprovalCommands } from "./commands/client/approval.js"; import { registerActivityCommands } from "./commands/client/activity.js"; import { registerDashboardCommands } from "./commands/client/dashboard.js"; +import { registerRoutineCommands } from "./commands/routines.js"; +import { registerFeedbackCommands } from "./commands/client/feedback.js"; import { applyDataDirOverride, type DataDirOptionLike } from "./config/data-dir.js"; import { loadPaperclipEnvFile } from "./config/env.js"; +import { initTelemetryFromConfigFile, flushTelemetry } from "./telemetry.js"; import { registerWorktreeCommands } from "./commands/worktree.js"; import { registerPluginCommands } from "./commands/client/plugin.js"; import { registerClientAuthCommands } from "./commands/client/auth.js"; +import { cliVersion } from "./version.js"; const program = new Command(); const DATA_DIR_OPTION_HELP = @@ -28,7 +32,7 @@ const DATA_DIR_OPTION_HELP = program .name("paperclipai") .description("Paperclip CLI — setup, diagnose, and configure your instance") - .version("0.2.7"); + .version(cliVersion); program.hook("preAction", (_thisCommand, actionCommand) => { const options = actionCommand.optsWithGlobals() as DataDirOptionLike; @@ -38,6 +42,7 @@ program.hook("preAction", (_thisCommand, actionCommand) => { hasContextOption: optionNames.has("context"), }); loadPaperclipEnvFile(options.config); + initTelemetryFromConfigFile(options.config); }); program @@ -137,6 +142,8 @@ registerAgentCommands(program); registerApprovalCommands(program); registerActivityCommands(program); registerDashboardCommands(program); +registerRoutineCommands(program); +registerFeedbackCommands(program); registerWorktreeCommands(program); registerPluginCommands(program); @@ -154,7 +161,20 @@ auth registerClientAuthCommands(auth); -program.parseAsync().catch((err) => { - console.error(err instanceof Error ? err.message : String(err)); - process.exit(1); -}); +async function main(): Promise { + let failed = false; + try { + await program.parseAsync(); + } catch (err) { + failed = true; + console.error(err instanceof Error ? err.message : String(err)); + } finally { + await flushTelemetry(); + } + + if (failed) { + process.exit(1); + } +} + +void main(); diff --git a/cli/src/telemetry.ts b/cli/src/telemetry.ts new file mode 100644 index 00000000000..77fa4ba42ec --- /dev/null +++ b/cli/src/telemetry.ts @@ -0,0 +1,49 @@ +import path from "node:path"; +import { + TelemetryClient, + resolveTelemetryConfig, + loadOrCreateState, + trackInstallStarted, + trackInstallCompleted, + trackCompanyImported, +} from "../../packages/shared/src/telemetry/index.js"; +import { resolvePaperclipInstanceRoot } from "./config/home.js"; +import { readConfig } from "./config/store.js"; +import { cliVersion } from "./version.js"; + +let client: TelemetryClient | null = null; + +export function initTelemetry(fileConfig?: { enabled?: boolean }): TelemetryClient | null { + if (client) return client; + + const config = resolveTelemetryConfig(fileConfig); + if (!config.enabled) return null; + + const stateDir = path.join(resolvePaperclipInstanceRoot(), "telemetry"); + client = new TelemetryClient(config, () => loadOrCreateState(stateDir, cliVersion), cliVersion); + return client; +} + +export function initTelemetryFromConfigFile(configPath?: string): TelemetryClient | null { + try { + return initTelemetry(readConfig(configPath)?.telemetry); + } catch { + return initTelemetry(); + } +} + +export function getTelemetryClient(): TelemetryClient | null { + return client; +} + +export async function flushTelemetry(): Promise { + if (client) { + await client.flush(); + } +} + +export { + trackInstallStarted, + trackInstallCompleted, + trackCompanyImported, +}; diff --git a/cli/src/version.ts b/cli/src/version.ts new file mode 100644 index 00000000000..7b94c8b35df --- /dev/null +++ b/cli/src/version.ts @@ -0,0 +1,10 @@ +import { createRequire } from "node:module"; + +type PackageJson = { + version?: string; +}; + +const require = createRequire(import.meta.url); +const pkg = require("../package.json") as PackageJson; + +export const cliVersion = pkg.version ?? "0.0.0"; diff --git a/cli/tsconfig.json b/cli/tsconfig.json index dc664efe51c..3af05f51636 100644 --- a/cli/tsconfig.json +++ b/cli/tsconfig.json @@ -2,7 +2,7 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "dist", - "rootDir": "src" + "rootDir": ".." }, - "include": ["src"] + "include": ["src", "../packages/shared/src"] } diff --git a/doc/DEVELOPING.md b/doc/DEVELOPING.md index e85de611e95..724496e9e9b 100644 --- a/doc/DEVELOPING.md +++ b/doc/DEVELOPING.md @@ -97,7 +97,7 @@ docker run --name paperclip \ Or use Compose: ```sh -docker compose -f docker-compose.quickstart.yml up --build +docker compose -f docker/docker-compose.quickstart.yml up --build ``` See `doc/DOCKER.md` for API key wiring (`OPENAI_API_KEY` / `ANTHROPIC_API_KEY`) and persistence details. @@ -145,6 +145,8 @@ For `codex_local`, Paperclip also manages a per-company Codex home under the ins - `~/.paperclip/instances/default/companies//codex-home` +If the `codex` CLI is not installed or not on `PATH`, `codex_local` agent runs fail at execution time with a clear adapter error. Quota polling uses a short-lived `codex app-server` subprocess: when `codex` cannot be spawned, that provider reports `ok: false` in aggregated quota results and the API server keeps running (it must not exit on a missing binary). + ## Worktree-local Instances When developing from multiple git worktrees, do not point two Paperclip servers at the same embedded PostgreSQL data directory. @@ -173,6 +175,8 @@ Seed modes: After `worktree init`, both the server and the CLI auto-load the repo-local `.paperclip/.env` when run inside that worktree, so normal commands like `pnpm dev`, `paperclipai doctor`, and `paperclipai db:backup` stay scoped to the worktree instance. +Provisioned git worktrees also pause all seeded routines in the isolated worktree database by default. This prevents copied daily/cron routines from firing unexpectedly inside the new workspace instance during development. + That repo-local env also sets: - `PAPERCLIP_IN_WORKTREE=true` @@ -228,6 +232,39 @@ pnpm paperclipai worktree init --force --seed-mode minimal \ That rewrites the worktree-local `.paperclip/config.json` + `.paperclip/.env`, recreates the isolated instance under `~/.paperclip-worktrees/instances//`, and preserves the git worktree contents themselves. +For an already-created worktree where you want to keep the existing repo-local config/env and only overwrite the isolated database, use `worktree reseed` instead. Stop the target worktree's Paperclip server first so the command can replace the DB safely. + +**`pnpm paperclipai worktree reseed [options]`** — Re-seed an existing worktree-local instance from another Paperclip instance or worktree while preserving the target worktree's current config, ports, and instance identity. + +| Option | Description | +|---|---| +| `--from ` | Source worktree path, directory name, branch name, or `current` | +| `--to ` | Target worktree path, directory name, branch name, or `current` (defaults to `current`) | +| `--from-config ` | Source config.json to seed from | +| `--from-data-dir ` | Source `PAPERCLIP_HOME` used when deriving the source config | +| `--from-instance ` | Source instance id when deriving the source config | +| `--seed-mode ` | Seed profile: `minimal` or `full` (default: `full`) | +| `--yes` | Skip the destructive confirmation prompt | +| `--allow-live-target` | Override the guard that requires the target worktree DB to be stopped first | + +Examples: + +```sh +# From the main repo, reseed a worktree from the current default/master instance. +cd /path/to/paperclip +pnpm paperclipai worktree reseed \ + --from current \ + --to PAP-1132-assistant-ui-pap-1131-make-issues-comments-be-like-a-chat \ + --seed-mode full \ + --yes + +# From inside a worktree, reseed it from the default instance config. +cd /path/to/paperclip/.paperclip/worktrees/PAP-1132-assistant-ui-pap-1131-make-issues-comments-be-like-a-chat +pnpm paperclipai worktree reseed \ + --from-instance default \ + --seed-mode full +``` + **`pnpm paperclipai worktree:make [options]`** — Create `~/NAME` as a git worktree, then initialize an isolated Paperclip instance inside it. This combines `git worktree add` with `worktree init` in a single step. | Option | Description | diff --git a/doc/DOCKER.md b/doc/DOCKER.md index a7055e203cf..056a7bdf043 100644 --- a/doc/DOCKER.md +++ b/doc/DOCKER.md @@ -2,6 +2,28 @@ Run Paperclip in Docker without installing Node or pnpm locally. +All commands below assume you are in the **project root** (the directory containing `package.json`), not inside `docker/`. + +## Building the image + +```sh +docker build -t paperclip-local . +``` + +The Dockerfile installs common agent tools (`git`, `gh`, `curl`, `wget`, `ripgrep`, `python3`) and the Claude, Codex, and OpenCode CLIs. + +Build arguments: + +| Arg | Default | Purpose | +|-----|---------|---------| +| `USER_UID` | `1000` | UID for the container `node` user (match your host UID to avoid permission issues on bind mounts) | +| `USER_GID` | `1000` | GID for the container `node` group | + +```sh +docker build -t paperclip-local \ + --build-arg USER_UID=$(id -u) --build-arg USER_GID=$(id -g) . +``` + ## One-liner (build + run) ```sh @@ -10,6 +32,7 @@ docker run --name paperclip \ -p 3100:3100 \ -e HOST=0.0.0.0 \ -e PAPERCLIP_HOME=/paperclip \ + -e BETTER_AUTH_SECRET=$(openssl rand -hex 32) \ -v "$(pwd)/data/docker-paperclip:/paperclip" \ paperclip-local ``` @@ -25,10 +48,15 @@ Data persistence: All persisted under your bind mount (`./data/docker-paperclip` in the example above). -## Compose Quickstart +## Docker Compose + +### Quickstart (embedded SQLite) + +Single container, no external database. Data persists via a bind mount. ```sh -docker compose -f docker-compose.quickstart.yml up --build +BETTER_AUTH_SECRET=$(openssl rand -hex 32) \ + docker compose -f docker/docker-compose.quickstart.yml up --build ``` Defaults: @@ -39,11 +67,36 @@ Defaults: Optional overrides: ```sh -PAPERCLIP_PORT=3200 PAPERCLIP_DATA_DIR=./data/pc docker compose -f docker-compose.quickstart.yml up --build +PAPERCLIP_PORT=3200 PAPERCLIP_DATA_DIR=../data/pc \ + docker compose -f docker/docker-compose.quickstart.yml up --build ``` +**Note:** `PAPERCLIP_DATA_DIR` is resolved relative to the compose file (`docker/`), so `../data/pc` maps to `data/pc` in the project root. + If you change host port or use a non-local domain, set `PAPERCLIP_PUBLIC_URL` to the external URL you will use in browser/auth flows. +Pass `OPENAI_API_KEY` and/or `ANTHROPIC_API_KEY` to enable local adapter runs. + +### Full stack (with PostgreSQL) + +Paperclip server + PostgreSQL 17. The database is health-checked before the server starts. + +```sh +BETTER_AUTH_SECRET=$(openssl rand -hex 32) \ + docker compose -f docker/docker-compose.yml up --build +``` + +PostgreSQL data persists in a named Docker volume (`pgdata`). Paperclip data persists in `paperclip-data`. + +### Untrusted PR review + +Isolated container for reviewing untrusted pull requests with Codex or Claude, without exposing your host machine. See `doc/UNTRUSTED-PR-REVIEW.md` for the full workflow. + +```sh +docker compose -f docker/docker-compose.untrusted-review.yml build +docker compose -f docker/docker-compose.untrusted-review.yml run --rm --service-ports review +``` + ## Authenticated Compose (Single Public URL) For authenticated deployments, set one canonical public URL and let Paperclip derive auth/callback defaults: @@ -93,11 +146,71 @@ Notes: - Without API keys, the app still runs normally. - Adapter environment checks in Paperclip will surface missing auth/CLI prerequisites. -## Untrusted PR Review Container +## Podman Quadlet (systemd) -If you want a separate Docker environment for reviewing untrusted pull requests with `codex` or `claude`, use the dedicated review workflow in `doc/UNTRUSTED-PR-REVIEW.md`. +The `docker/quadlet/` directory contains unit files to run Paperclip + PostgreSQL as systemd services via Podman Quadlet. -That setup keeps CLI auth state in Docker volumes instead of your host home directory and uses a separate scratch workspace for PR checkouts and preview runs. +| File | Purpose | +|------|---------| +| `docker/quadlet/paperclip.pod` | Pod definition — groups containers into a shared network namespace | +| `docker/quadlet/paperclip.container` | Paperclip server — joins the pod, connects to Postgres at `127.0.0.1` | +| `docker/quadlet/paperclip-db.container` | PostgreSQL 17 — joins the pod, health-checked | + +### Setup + +1. Build the image (see above). + +2. Copy quadlet files to your systemd directory: + + ```sh + # Rootless (recommended) + cp docker/quadlet/*.pod docker/quadlet/*.container \ + ~/.config/containers/systemd/ + + # Or rootful + sudo cp docker/quadlet/*.pod docker/quadlet/*.container \ + /etc/containers/systemd/ + ``` + +3. Create a secrets env file (keep out of version control): + + ```sh + cat > ~/.config/containers/systemd/paperclip.env < +``` + +V1 server behavior: + +- if `executionSegments` is absent, keep current single-result behavior unchanged +- if present, write one `cost_events` row per segment that has cost or token usage +- store the segment array in run usage/result metadata for later UI inspection +- keep the existing top-level `provider` / `model` fields as a summary, preferably the primary phase when present + +This avoids breaking existing adapters while giving routed adapters truthful reporting. + +## 7. Adapter Rollout Plan + +## 7.1 Phase 1: contract and server plumbing + +Work: + +1. Extend adapter result types with segmented execution metadata. +2. Update heartbeat cost recording to emit multiple cost events when segments are present. +3. Include segment summaries in run metadata for transcript/debug views. + +Success criteria: + +- existing adapters behave exactly as before +- a routed adapter can report cheap plus primary usage without collapsing them into one fake model + +## 7.2 Phase 2: `codex_local` + +Why first: + +- Codex already has rich prompt/handoff handling +- the adapter already injects Paperclip skills and workspace metadata cleanly +- the current implementation already distinguishes bootstrap, wake delta, and handoff prompt sections + +Implementation work: + +1. Add config support for `smartModelRouting`. +2. Add a cheap-preflight prompt builder. +3. Run cheap preflight only on fresh sessions. +4. Pass a compact preflight handoff note into the primary prompt. +5. Report segmented usage and model metadata. + +Important guardrail: + +- do not resume the cheap-model session as the primary session in V1 + +## 7.3 Phase 3: `claude_local` + +Implementation work is similar, but the session model-switch risk is even less attractive. + +Same rule: + +- cheap preflight is ephemeral +- primary Claude session remains canonical + +## 7.4 Phase 4: other adapters + +Candidates: + +- `cursor` +- `gemini_local` +- `opencode_local` +- external plugin adapters through `createServerAdapter()` + +These should come later because each runtime has different session and model-switch semantics. + +## 8. UI and Config Changes + +For supported built-in adapters, the agent config UI should expose: + +- `model` as the primary model +- `smart model routing` toggle +- `cheap model` +- optional cheap thinking effort +- optional `allow initial progress comment` toggle + +The run detail UI should also show when routing occurred, for example: + +- cheap preflight model +- primary model +- token/cost split + +This matters because Paperclip's board UI is supposed to make cost and behavior legible. + +## 9. Why Not Copy Hermes Exactly + +Hermes' cheap-route heuristic is useful precedent, but Paperclip should not start there. + +Reasons: + +- Hermes is optimizing free-form conversational turns +- Paperclip agents run structured, issue-scoped heartbeats with explicit task and workspace context +- Paperclip already knows whether a run is fresh vs resumed, issue-scoped vs approval follow-up, and what workspace/session exists +- those execution facts are stronger routing signals than prompt keyword matching + +If Paperclip later wants a cheap-only completion path for trivial runs, that can be a second-stage feature built on observed run data, not the first implementation. + +## 10. Risks + +## 10.1 Duplicate or noisy comments + +If the cheap phase posts an update and the primary phase posts another near-identical update, the issue thread gets worse. + +Mitigation: + +- keep cheap comments optional +- make the preflight prompt explicitly avoid repeating status if a useful comment was already posted + +## 10.2 Misleading cost reporting + +If we only record the primary model, the board loses visibility into the routing cost tradeoff. + +Mitigation: + +- add segmented execution reporting before shipping adapter behavior + +## 10.3 Session corruption + +Cross-model session reuse may fail or degrade context quality. + +Mitigation: + +- V1 does not persist or resume cheap preflight sessions + +## 10.4 Cheap model overreach + +A cheap model with full tools and permissions may do too much low-quality work. + +Mitigation: + +- hard cap preflight turns +- use an explicit orchestration-only prompt +- start with supported adapters where we can test the behavior well + +## 11. Verification Plan + +Required tests: + +- adapter unit tests for route eligibility +- adapter unit tests for "fresh session -> cheap preflight + primary" +- adapter unit tests for "resumed session -> primary only" +- heartbeat tests for segmented cost-event creation +- UI tests for config save/load of cheap-model fields + +Manual checks: + +- create a fresh issue for a routed Codex or Claude agent +- verify the run metadata shows both phases +- verify only the primary session is persisted +- verify cost rows reflect both models +- verify the issue thread does not get duplicate kickoff comments + +## 12. Recommended Sequence + +1. Add segmented execution reporting to the adapter/server contract. +2. Implement `codex_local` cheap preflight. +3. Validate cost visibility and transcript UX. +4. Implement `claude_local` cheap preflight. +5. Decide later whether any adapters need Hermes-style text heuristics in addition to phase-based routing. + +## 13. Recommendation + +Paperclip should ship smart model routing as: + +- adapter-specific +- opt-in +- phase-based +- session-safe +- cost-truthful + +The right V1 is not "choose the cheapest model for simple prompts." The right V1 is "use a cheap model for bounded orchestration work on fresh runs, then hand off to the primary model for the real task." diff --git a/doc/plans/2026-04-06-subissue-creation-on-issue-detail.md b/doc/plans/2026-04-06-subissue-creation-on-issue-detail.md new file mode 100644 index 00000000000..1d894644ab5 --- /dev/null +++ b/doc/plans/2026-04-06-subissue-creation-on-issue-detail.md @@ -0,0 +1,209 @@ +# 2026-04-06 Sub-issue Creation On Issue Detail Plan + +Status: Proposed +Date: 2026-04-06 +Audience: Product and engineering +Related: +- `ui/src/pages/IssueDetail.tsx` +- `ui/src/components/IssueProperties.tsx` +- `ui/src/components/NewIssueDialog.tsx` +- `ui/src/context/DialogContext.tsx` +- `packages/shared/src/validators/issue.ts` +- `server/src/services/issues.ts` + +## 1. Purpose + +This document defines the implementation plan for adding manual sub-issue creation from the issue detail page. + +Requested UX: + +- the `Sub-issues` tab should always show an `Add sub-issue` action, even when there are no children yet +- the properties pane should also expose a `Sub-issues` section with the same `Add sub-issue` entry point +- both entry points should open the existing new-issue dialog in a "create sub-issue" mode +- the dialog should only show sub-issue-specific UI when it was opened from one of those entry points + +This is a UI-first change. The backend already supports child issue creation with `parentId`. + +## 2. Current State + +### 2.1 Existing child issue display + +`ui/src/pages/IssueDetail.tsx` already derives `childIssues` by filtering the company issue list on `parentId === issue.id`. + +Current limitation: + +- the `Sub-issues` tab only renders the empty state or the child issue list +- there is no action to create a child issue from that tab + +### 2.2 Existing properties pane + +`ui/src/components/IssueProperties.tsx` shows `Blocked by`, `Blocking`, and `Parent`, but it has no sub-issue section or child issue affordance. + +### 2.3 Existing dialog state + +`ui/src/context/DialogContext.tsx` can open the global new-issue dialog with defaults such as status, priority, project, assignee, title, and description. + +Current limitation: + +- there is no way to pass sub-issue context like `parentId` +- `ui/src/components/NewIssueDialog.tsx` therefore cannot submit a child issue or render parent-specific context + +### 2.4 Backend contract already exists + +The create-issue validator already accepts `parentId`. + +`server/src/services/issues.ts` already uses: + +- `parentId` for parent-child issue relationships +- `parentId` as the default workspace inheritance source when `inheritExecutionWorkspaceFromIssueId` is not provided + +That means the required API and workspace inheritance behavior already exist. No server or schema change is required for the first pass. + +## 3. Proposed Implementation + +## 3.1 Extend dialog defaults for sub-issue context + +Extend `NewIssueDefaults` in `ui/src/context/DialogContext.tsx` with: + +- `parentId?: string` +- optional parent display metadata for the dialog header, for example: + - `parentIdentifier?: string` + - `parentTitle?: string` + +This keeps the dialog self-contained and avoids re-fetching parent context purely for presentation. + +## 3.2 Add issue-detail entry points + +Use `openNewIssue(...)` from `ui/src/pages/IssueDetail.tsx` in two places: + +1. `Sub-issues` tab +2. properties pane via props passed into `IssueProperties` + +Both entry points should pass: + +- `parentId: issue.id` +- `parentIdentifier: issue.identifier ?? issue.id` +- `parentTitle: issue.title` +- `projectId: issue.projectId ?? undefined` + +Using the current issue's `projectId` preserves the common expectation that sub-issues stay inside the same project unless the operator changes it in the dialog. + +No special assignee default should be forced in V1. + +## 3.3 Add a dedicated properties-pane section + +Extend `IssueProperties` to accept: + +- `childIssues: Issue[]` +- `onCreateSubissue: () => void` + +Render a new `Sub-issues` section near `Blocked by` / `Blocking`: + +- if children exist, show compact links or pills to the existing sub-issues +- always show an `Add sub-issue` button + +This keeps the child issue affordance visible in the property area without requiring a generic parent selector. + +## 3.4 Update the sub-issues tab layout + +Refactor the `Sub-issues` tab in `IssueDetail` to render: + +- a small header row with child count +- an `Add sub-issue` button +- the existing empty state or child issue list beneath it + +This satisfies the requirement that the action is visible whether or not sub-issues already exist. + +## 3.5 Add sub-issue mode to the new-issue dialog + +Update `ui/src/components/NewIssueDialog.tsx` so that when `newIssueDefaults.parentId` is present: + +- the dialog submits `parentId` +- the header/button copy can switch to `New sub-issue` / `Create sub-issue` +- a compact parent context row is shown, for example `Parent: PAP-1150 add the ability...` + +Important constraint: + +- this parent context row should only render when the dialog was opened with sub-issue defaults +- opening the dialog from global create actions should remain unchanged and should not expose a generic parent control + +That preserves the requested UX boundary: sub-issue creation is intentional, not part of the default create-issue surface. + +## 3.6 Query invalidation and refresh behavior + +No new data-fetch path is needed. + +The existing create success handler in `NewIssueDialog` already invalidates: + +- `queryKeys.issues.list(companyId)` +- issue-related list badges + +That should be enough for the parent `IssueDetail` view to recompute `childIssues` after creation because it derives children from the company issue list query. + +If the detail page ever moves away from the full company issue list, this should be revisited, but it does not require additional work for the current architecture. + +## 4. Implementation Order + +1. Extend `DialogContext` issue defaults with sub-issue fields. +2. Wire `IssueDetail` to open the dialog in sub-issue mode from the `Sub-issues` tab. +3. Extend `IssueProperties` to display child issues and the `Add sub-issue` action. +4. Update `NewIssueDialog` submission and header UI for sub-issue mode. +5. Add UI tests for the new entry points and payload behavior. + +## 5. Testing Plan + +Add focused UI tests covering: + +1. `IssueDetail` + - `Sub-issues` tab shows `Add sub-issue` when there are zero children + - clicking the action opens the dialog with parent defaults + +2. `IssueProperties` + - the properties pane renders the sub-issue section + - `Add sub-issue` remains available when there are no child issues + +3. `NewIssueDialog` + - when opened with `parentId`, submit payload includes `parentId` + - sub-issue-specific copy appears only in that mode + - when opened normally, no parent UI is shown and payload is unchanged + +No backend test expansion is required unless implementation discovers a client/server contract gap. + +## 6. Risks And Decisions + +### 6.1 Parent metadata source + +Decision: pass parent label metadata through dialog defaults instead of making `NewIssueDialog` fetch the parent issue. + +Reason: + +- less coupling +- no loading state inside the dialog +- simpler tests + +### 6.2 Project inheritance + +Decision: prefill `projectId` from the parent issue, but keep it editable. + +Reason: + +- matches expected operator behavior +- avoids silently moving a sub-issue outside the current project by default + +### 6.3 Keep parent selection out of the generic dialog + +Decision: do not add a freeform parent picker in this change. + +Reason: + +- the request explicitly wants sub-issue controls only when the flow starts from a sub-issue action +- this keeps the default issue creation surface simpler + +## 7. Success Criteria + +This plan is complete when an operator can: + +1. open any issue detail page +2. click `Add sub-issue` from either the `Sub-issues` tab or the properties pane +3. land in the existing new-issue dialog with clear parent context +4. create the child issue and see it appear under the parent without a page reload diff --git a/doc/plans/2026-04-07-issue-detail-speed-and-optimistic-inventory.md b/doc/plans/2026-04-07-issue-detail-speed-and-optimistic-inventory.md new file mode 100644 index 00000000000..b9c2f0a8729 --- /dev/null +++ b/doc/plans/2026-04-07-issue-detail-speed-and-optimistic-inventory.md @@ -0,0 +1,302 @@ +# 2026-04-07 Issue Detail Speed And Optimistic Inventory + +Status: Proposed +Date: 2026-04-07 +Audience: Product and engineering +Related: +- `ui/src/pages/IssueDetail.tsx` +- `ui/src/components/IssueProperties.tsx` +- `ui/src/api/issues.ts` +- `ui/src/lib/queryKeys.ts` +- `server/src/routes/issues.ts` +- `server/src/services/issues.ts` +- [PAP-1192](/PAP/issues/PAP-1192) +- [PAP-1191](/PAP/issues/PAP-1191) +- [PAP-1188](/PAP/issues/PAP-1188) +- [PAP-1119](/PAP/issues/PAP-1119) +- [PAP-945](/PAP/issues/PAP-945) +- [PAP-1165](/PAP/issues/PAP-1165) +- [PAP-890](/PAP/issues/PAP-890) +- [PAP-254](/PAP/issues/PAP-254) +- [PAP-138](/PAP/issues/PAP-138) + +## 1. Purpose + +This note inventories the Paperclip issues that point to the same UX class of problem: + +- pages feel slow because they over-fetch or refetch too much +- actions feel slow because the UI waits for the round trip before reflecting obvious local intent +- optimistic updates exist in some places, but not in a consistent system + +The immediate trigger is [PAP-1192](/PAP/issues/PAP-1192): the issue detail page now feels very slow. + +## 2. Short Answer + +The issue detail page is not obviously blocked by one pathological endpoint. The main problem is the shape of the page: + +- `IssueDetail` fans out into many independent queries on mount +- some of those queries fetch full company-wide collections for data that is local to one issue +- common mutations invalidate almost every issue-related query, which creates avoidable refetch storms +- the page has only a minimal top-level `Loading...` fallback and very little staged or sectional loading UX + +Measured against the current assigned issue (`PAP-1191`) on local dev, the slowest single request was the full company issues list: + +- `GET /api/issues/:id` about `18ms` +- `GET /api/issues/:id/comments|activity|approvals|attachments` about `6-8ms` +- `GET /api/companies/:companyId/agents|projects` about `9-11ms` +- `GET /api/companies/:companyId/issues` about `76ms` + +That strongly suggests the current pain is aggregate client fan-out plus over-broad invalidation, not one obviously broken endpoint. + +## 3. Similar Issue Inventory + +## 3.1 Issue-detail and issue-action siblings + +- [PAP-1192](/PAP/issues/PAP-1192): issue page feels like it loads forever +- [PAP-1188](/PAP/issues/PAP-1188): assignee changes in the issue properties pane were slow and needed optimistic UI +- [PAP-945](/PAP/issues/PAP-945): optimistic comment rendering +- [PAP-1003](/PAP/issues/PAP-1003): optimistic comments had duplicate draft/pending behavior +- [PAP-947](/PAP/issues/PAP-947): follow-up breakage from optimistic comments +- [PAP-254](/PAP/issues/PAP-254): long issue threads become sluggish when adding comments +- [PAP-189](/PAP/issues/PAP-189): comment semantics while an issue has a live run + +Pattern: the issue page already has a history of needing both optimistic behavior and bounded thread/loading behavior. `PAP-1192` is the same family, not a new category. + +## 3.2 Inbox and list-view siblings + +- [PAP-1119](/PAP/issues/PAP-1119): optimistic archive had fade-out then snap-back +- [PAP-1165](/PAP/issues/PAP-1165): issue search slow +- [PAP-890](/PAP/issues/PAP-890): issue search slow, make it very fast +- [PAP-138](/PAP/issues/PAP-138): inbox loading feels stuck +- [PAP-470](/PAP/issues/PAP-470): create-issue save state felt slow and awkward + +Pattern: Paperclip already has several places where the right fix was "show intent immediately, then reconcile," not "wait for refetch." + +## 3.3 Broader app-loading siblings + +- [PAP-472](/PAP/issues/PAP-472): dashboard charts load very slowly +- [PAP-797](/PAP/issues/PAP-797): reduce loading states through static generation/caching where possible +- [PAP-799](/PAP/issues/PAP-799): embed company data at build time to eliminate loading states +- [PAP-703](/PAP/issues/PAP-703): faster chat and better visual feedback + +Pattern: the product has recurring pressure to reduce blank/loading states across the app, so the issue-detail work should fit that broader direction. + +## 4. Current Issue Detail Findings + +## 4.1 Mount query fan-out is high + +`ui/src/pages/IssueDetail.tsx` mounts all of these data sources up front: + +- issue detail +- comments +- activity +- linked runs +- linked approvals +- attachments +- live runs +- active run +- full company issues list +- agents list +- auth session +- projects list +- feedback votes +- instance general settings +- plugin slots + +This is too much for the initial view of a single issue. + +## 4.2 The page fetches full company issue data just to derive child issues + +`IssueDetail` currently does: + +- `issuesApi.list(selectedCompanyId!)` +- then filters client-side for `parentId === issue.id` + +That is expensive relative to the need. + +Important detail: + +- the server route already supports `parentId` +- `server/src/services/issues.ts` already supports `parentId` +- but `ui/src/api/issues.ts` does not expose `parentId` in the filter type + +So the client is missing an already-supported narrow query path. + +## 4.3 Comments are still fetched as full-thread loads + +`server/src/routes/issues.ts` and `server/src/services/issues.ts` already support: + +- `after` +- `order` +- `limit` + +But `IssueDetail` still calls `issuesApi.listComments(issueId)` with no cursor or limit and then re-invalidates the full thread after common comment actions. + +That means we already have the server-side building blocks for incremental comment loading, but the page is not using them. + +## 4.4 Cache invalidation is broader than necessary + +`invalidateIssue()` in `IssueDetail` invalidates: + +- detail +- activity +- runs +- approvals +- feedback votes +- attachments +- documents +- live runs +- active run +- multiple issue collections +- sidebar badges + +That is acceptable for correctness, but it is expensive for perceived speed and makes optimistic work feel less stable because the page keeps re-painting from fresh network results. + +## 4.5 Live run state is fetched twice + +The page polls both: + +- `issues.liveRuns(issueId)` every 3s +- `issues.activeRun(issueId)` every 3s + +That is duplicate polling for closely related state. + +## 4.6 Properties panel duplicates more list fetching + +`ui/src/components/IssueProperties.tsx` fetches: + +- session +- agents list +- projects list +- labels +- and, when the blocker picker opens, the full company issues list + +The page and panel are each doing their own list work instead of sharing a narrower issue-detail data model. + +## 4.7 The perceived loading UX is too thin + +`IssueDetail` only shows: + +- plain `Loading...` while the main issue query is pending + +After that, many sub-sections can appear empty or incomplete until their own queries resolve. That makes the page feel slower than the raw request times suggest. + +## 5. Recommended Plan + +## 5.1 Phase 1: Fix perceived speed first + +Ship UX changes that make the page feel immediate before deeper backend reshaping: + +- replace the plain `Loading...` state with an issue-detail skeleton +- give comments, activity, attachments, and sub-issues their own skeleton/empty/loading states +- preserve visible stale data during refetch instead of clearing sections +- show explicit pending state for local actions that are already optimistic + +Why first: + +- it improves the user-facing feel immediately +- it reduces the chance that later data changes still feel slow because the page flashes blank + +## 5.2 Phase 2: Stop fetching the full company issues list for child issues + +Add `parentId` to the `issuesApi.list(...)` filter type and switch `IssueDetail` to: + +- fetch child issues only +- stop loading the full company issue collection on page mount + +This is the highest-confidence narrow win because the server path already exists. + +## 5.3 Phase 3: Convert comments to a bounded + incremental model + +Use the existing server support for: + +- latest comment cursor from heartbeat context or issue bootstrap +- incremental fetch with `after` +- bounded initial fetch with `limit` + +Suggested behavior: + +- first load: fetch the latest N comments +- offer `load earlier` for long threads +- after posting or on live updates: append incrementally instead of invalidating the whole thread + +This should address the same performance family as [PAP-254](/PAP/issues/PAP-254). + +## 5.4 Phase 4: Reduce duplicate polling and invalidation + +Tighten the runtime side of the page: + +- collapse `liveRuns` and `activeRun` into one client source if possible +- stop invalidating unrelated issue collections after mutations that only affect the current issue +- merge server responses into cache where we already have enough information + +Examples: + +- posting a comment should not force a broad company issue list refetch unless list-visible metadata changed +- attachment changes should not invalidate approvals or unrelated live-run queries + +## 5.5 Phase 5: Consider an issue-detail bootstrap contract + +If the page is still too chatty after the client fixes, add one tailored bootstrap surface for the issue detail page. + +Potential bootstrap payload: + +- issue core data +- child issue summaries +- latest comment cursor and recent comment page +- live run summary +- attachment summaries +- approval summaries +- any lightweight mention/selector metadata truly needed at first paint + +This should happen after the obvious client overfetch fixes, not before. + +## 6. Concrete Opportunities By Surface + +## 6.1 Issue detail page + +- narrow child issue fetch from full list to `parentId` +- stage loading by section instead of all-or-nothing perception +- bound initial comments payload +- reduce duplicate live-run polling +- replace broad invalidation with targeted cache writes + +## 6.2 Issue properties panel + +- reuse page-level agents/projects data where possible +- fetch blockers lazily and narrowly +- keep local optimistic field updates without broad page invalidation + +## 6.3 Thread/comment UX + +- append optimistic comments directly into the visible thread +- keep queued/pending comment state stable during reconciliation +- fetch only new comments after the last known cursor + +## 6.4 Cross-app optimistic consistency + +The same standards should apply to: + +- issue archive/unarchive +- issue property edits +- create issue/sub-issue flows +- comment posting +- attachment/document actions where the local result is obvious + +## 7. Suggested Execution Order + +1. `PAP-1192`: issue-detail skeletons and staged loading +2. add `parentId` support to `ui/src/api/issues.ts` and switch child-issue fetching to a narrow query +3. move comments to bounded initial load plus incremental updates +4. shrink invalidation and polling scope +5. only then decide whether a new issue-detail bootstrap endpoint is still needed + +## 8. Success Criteria + +This inventory is successful if the follow-up implementation makes the issue page behave like this: + +1. navigating to an issue shows a shaped skeleton immediately, not plain text +2. the page no longer fetches the full company issue list just to render sub-issues +3. long threads do not require full-thread fetches on every load or comment mutation +4. local actions feel immediate and do not snap back because of broad invalidation +5. the issue page feels faster even when absolute backend timings are already reasonable diff --git a/doc/plans/2026-04-07-pi-hooks-survey.md b/doc/plans/2026-04-07-pi-hooks-survey.md new file mode 100644 index 00000000000..bc67a704530 --- /dev/null +++ b/doc/plans/2026-04-07-pi-hooks-survey.md @@ -0,0 +1,248 @@ +# Pi Hook Survey + +Status: investigation note +Date: 2026-04-07 + +## Why this exists + +We were asked to find the hook surfaces exposed by `pi` and `pi-mono`, then decide which ideas transfer cleanly into Paperclip. + +This note is based on direct source inspection of: + +- `badlogic/pi` default branch and `pi2` branch +- `badlogic/pi-mono` `packages/coding-agent` +- current Paperclip plugin and adapter surfaces in this repo + +## Short answer + +- Current `pi` does not expose a comparable extension hook API. What it exposes today is a JSON event stream from `pi-agent`. +- `pi-mono` does expose a real extension hook system. It is broad, typed, and intentionally allows mutation of agent/runtime behavior. +- Paperclip should copy only the safe subset: + - typed event subscriptions + - read-only run lifecycle events + - explicit worker lifecycle hooks + - plugin-to-plugin events +- Paperclip should not copy the dangerous subset: + - arbitrary mutation hooks on core control-plane decisions + - project-local plugin loading + - built-in tool shadowing by name collision + +## What `pi` has today + +Current `badlogic/pi` is primarily a GPU pod manager plus a lightweight agent runner. It does not expose a `pi.on(...)`-style extension API like `pi-mono`. + +The closest thing to hooks is the `pi-agent --json` event stream: + +- `session_start` +- `user_message` +- `assistant_start` +- `assistant_message` +- `thinking` +- `tool_call` +- `tool_result` +- `token_usage` +- `error` +- `interrupted` + +That makes `pi` useful as an event producer, but not as a host for third-party runtime interception. + +## What `pi-mono` has + +`pi-mono` exposes a real extension API through `packages/coding-agent/src/core/extensions/types.ts`. + +### Extension event hooks + +Verified `pi.on(...)` hook names: + +- `resources_discover` +- `session_start` +- `session_before_switch` +- `session_before_fork` +- `session_before_compact` +- `session_compact` +- `session_shutdown` +- `session_before_tree` +- `session_tree` +- `context` +- `before_provider_request` +- `before_agent_start` +- `agent_start` +- `agent_end` +- `turn_start` +- `turn_end` +- `message_start` +- `message_update` +- `message_end` +- `tool_execution_start` +- `tool_execution_update` +- `tool_execution_end` +- `model_select` +- `tool_call` +- `tool_result` +- `user_bash` +- `input` + +### Other extension surfaces + +`pi-mono` extensions can also: + +- `registerTool(...)` +- `registerCommand(...)` +- `registerShortcut(...)` +- `registerFlag(...)` +- `registerMessageRenderer(...)` +- `registerProvider(...)` +- `unregisterProvider(...)` +- use an inter-extension event bus via `pi.events` + +### Important behavior + +`pi-mono` hooks are not just observers. Several can actively mutate behavior: + +- `before_agent_start` can rewrite the effective system prompt and inject messages +- `context` can replace the message set before an LLM call +- `before_provider_request` can rewrite the serialized provider payload +- `tool_call` can mutate tool inputs and block execution +- `tool_result` can rewrite tool output +- `user_bash` can replace shell execution entirely +- `input` can transform or fully handle user input before normal processing + +That is a good fit for a local coding harness. It is not automatically a good fit for a company control plane. + +## What Paperclip already has + +Paperclip already has several hook-like surfaces, but they are much narrower and safer: + +- plugin worker lifecycle hooks such as `setup()` and `onHealth()` +- declared webhook endpoints for plugins +- scheduled jobs +- a typed plugin event bus with filtering and plugin namespacing +- adapter runtime hooks for logs/status/usage in the run pipeline + +The plugin event bus is already pointed in the right direction: + +- core domain events can be subscribed to +- filters are applied server-side +- plugin-emitted events are namespaced under `plugin..*` +- plugins do not override core behavior by name collision + +## What transfers well to Paperclip + +These ideas from `pi-mono` fit Paperclip with little conceptual risk: + +### 1. Read-only run lifecycle subscriptions + +Paperclip should continue exposing run and transcript events to plugins, for example: + +- run started / finished +- tool started / finished +- usage reported +- issue comment created + +This matches Paperclip's control-plane posture: observe, react, automate. + +### 2. Plugin-to-plugin events + +Paperclip already has this. It is worth keeping and extending. + +This is the clean replacement for many ad hoc hook chains. + +### 3. Explicit worker lifecycle hooks + +Paperclip already has `setup()` and `onHealth()`. That is the right shape. + +If more lifecycle is needed, it should stay explicit and host-controlled. + +### 4. Trusted adapter-level prompt/runtime middleware + +Some `pi-mono` ideas do belong in Paperclip, but only inside trusted adapter/runtime code: + +- prompt shaping before a run starts +- provider request customization +- tool execution wrappers for local coding adapters + +This should be an adapter surface, not a general company plugin surface. + +## What should not transfer directly + +These `pi-mono` capabilities are a bad fit for Paperclip core: + +### 1. Arbitrary mutation hooks on control-plane decisions + +Paperclip should not let general plugins rewrite: + +- issue checkout semantics +- approval outcomes +- budget enforcement +- assignment rules +- company scoping + +Those are core invariants. + +### 2. Tool shadowing by name collision + +`pi-mono`'s low-friction override model is great for a personal coding harness. + +Paperclip should keep plugin tools namespaced and non-shadowing. + +### 3. Project-local plugin loading + +Paperclip is an operator-controlled control plane. Repo-local plugin auto-loading would make behavior too implicit and too hard to govern. + +### 4. UI-session-specific hooks as first-class product surface + +Hooks like: + +- `session_before_switch` +- `session_before_fork` +- `session_before_tree` +- `model_select` +- `input` +- `user_bash` + +are tied to `pi-mono` being an interactive terminal coding harness. + +They do not map directly to Paperclip's board-and-issues model. + +## Recommended Paperclip direction + +If we want a "hooks" story inspired by `pi-mono`, it should split into two layers: + +### Layer 1: safe control-plane plugins + +Allowed surfaces: + +- typed domain event subscriptions +- jobs +- webhooks +- plugin-to-plugin events +- UI slots and bridge actions +- plugin-owned tools and data endpoints + +Disallowed: + +- mutation of core issue/approval/budget invariants + +### Layer 2: trusted runtime middleware + +For adapters and other trusted runtime packages only: + +- prompt assembly hooks +- provider payload hooks +- tool execution wrappers +- transcript rendering helpers + +This is where the best `pi-mono` runtime ideas belong. + +## Bottom line + +If the question is "what hooks do `pi` and `pi-mono` have?": + +- `pi`: JSON output events, not a general extension hook system +- `pi-mono`: a broad extension hook API with 27 named event hooks plus tool/command/provider registration + +If the question is "what works for Paperclip too?": + +- yes: typed event subscriptions, worker lifecycle hooks, namespaced plugin events, read-only run lifecycle events +- maybe, but trusted-only: prompt/provider/tool middleware around adapter execution +- no: arbitrary mutation hooks on control-plane invariants, project-local plugin loading, tool shadowing diff --git a/doc/plans/2026-04-08-agent-browser-process-cleanup-plan.md b/doc/plans/2026-04-08-agent-browser-process-cleanup-plan.md new file mode 100644 index 00000000000..42024730fdd --- /dev/null +++ b/doc/plans/2026-04-08-agent-browser-process-cleanup-plan.md @@ -0,0 +1,238 @@ +# PAP-1231 Agent Browser Process Cleanup Plan + +Status: Proposed +Date: 2026-04-08 +Related issue: `PAP-1231` +Audience: Engineering + +## Goal + +Explain why browser processes accumulate during local agent runs and define a cleanup plan that fixes the general process-ownership problem rather than treating `agent-browser` as a one-off. + +## Short answer + +Yes, there is a likely root cause in Paperclip's local execution model. + +Today, heartbeat-run local adapters persist and manage only the top-level spawned PID. Their timeout/cancel path uses direct `child.kill()` semantics. That is weaker than the runtime-service path, which already tracks and terminates whole process groups. + +If Codex, Claude, Cursor, or a skill launched through them starts Chrome or Chromium helpers, Paperclip can lose ownership of those descendants even when it still believes it handled the run correctly. + +## Observed implementation facts + +### 1. Heartbeat-run local adapters track only one PID + +`packages/adapter-utils/src/server-utils.ts` + +- `runChildProcess()` spawns the adapter command and records only `child.pid` +- timeout handling sends `SIGTERM` and then `SIGKILL` to the direct child +- there is no process-group creation or process-group kill path there today + +`packages/db/src/schema/heartbeat_runs.ts` + +- `heartbeat_runs` stores `process_pid` +- there is no persisted `process_group_id` + +`server/src/services/heartbeat.ts` + +- cancellation logic uses the in-memory child handle and calls `child.kill()` +- orphaned-run recovery checks whether the recorded direct PID is alive +- the recovery model is built around one tracked process, not a descendant tree + +### 2. Workspace runtime already uses stronger ownership + +`server/src/services/workspace-runtime.ts` + +- runtime services are spawned with `detached: process.platform !== "win32"` +- the service record stores `processGroupId` +- shutdown calls `terminateLocalService()` with group-aware killing + +`server/src/services/local-service-supervisor.ts` + +- `terminateLocalService()` prefers `process.kill(-processGroupId, signal)` on POSIX +- it escalates from `SIGTERM` to `SIGKILL` + +This is the clearest internal comparison point: Paperclip already has one local-process subsystem that treats process-group ownership as the right abstraction. + +### 3. The current recovery path explains why leaks would be visible but hard to reason about + +If the direct adapter process exits, hangs, or is cancelled after launching a browser subtree: + +- Paperclip may think it cancelled the run because the parent process is gone +- descendant Chrome helpers may still be running +- orphan recovery has no persisted process-group identity to reconcile or reap later + +That makes the failure look like an `agent-browser` problem when the more general bug is "executor descendants are not owned strongly enough." + +## Why `agent-browser` makes the problem obvious + +Inference: + +- Chromium is intentionally multi-process +- browser automation often leaves a browser process plus renderer, GPU, utility, and crashpad/helper children +- skills that open browsers repeatedly amplify the symptom because each run can produce several descendant processes + +So `agent-browser` is probably not the root cause. It is the workload that exposes the weak ownership model fastest. + +## Success condition + +This work is successful when Paperclip can: + +1. start a local adapter run and own the full descendant tree it created +2. cancel, timeout, or recover that run without leaving Chrome descendants behind on POSIX +3. detect and clean up stale local descendants after server restarts +4. expose enough metadata that operators can see which run owns which spawned process tree + +## Non-goals + +Do not: + +- special-case `agent-browser` only +- depend on manual `pkill chrome` cleanup as the primary fix +- require every skill author to add bespoke browser teardown logic before Paperclip can clean up correctly +- change remote/http adapter behavior as part of the first pass + +## Proposed plan + +### Phase 0: reproduce and instrument + +Objective: + +- make the leak measurable from Paperclip's side before changing execution semantics + +Work: + +- add a reproducible local test script or fixture that launches a child process which itself launches descendants and ignores normal parent exit +- capture parent PID, descendant PIDs, and run ID in logs during local adapter execution +- document current behavior separately for: + - normal completion + - timeout + - explicit cancellation + - server restart during run + +Deliverable: + +- one short repro note attached to the implementation issue or child issue + +### Phase 1: give heartbeat-run local adapters process-group ownership + +Objective: + +- align adapter-run execution with the stronger runtime-service model + +Work: + +- update `runChildProcess()` to create a dedicated process group on POSIX +- persist both: + - direct PID + - process-group ID +- update the run cancellation and timeout paths to kill the group first, then escalate +- keep direct-PID fallback behavior for platforms where group kill is not available + +Likely touched surfaces: + +- `packages/adapter-utils/src/server-utils.ts` +- `packages/db/src/schema/heartbeat_runs.ts` +- `packages/shared/src/types/heartbeat.ts` +- `server/src/services/heartbeat.ts` + +Important design choice: + +- use the same ownership model for all local child-process adapters, not just Codex or Claude + +### Phase 2: make restart recovery group-aware + +Objective: + +- prevent stale descendants from surviving server crashes or restarts indefinitely + +Work: + +- teach orphan reconciliation to inspect the persisted process-group ID, not only the direct PID +- if the direct parent is gone but the group still exists, mark the run as detached-orphaned with clearer metadata +- decide whether restart recovery should: + - adopt the still-running group, or + - terminate it as unrecoverable + +Recommendation: + +- for heartbeat runs, prefer terminating unrecoverable orphan groups rather than adopting them unless we can prove the adapter session remains safe and observable + +Reason: + +- runtime services are long-lived and adoptable +- heartbeat runs are task executions with stricter audit and cancellation semantics + +### Phase 3: add operator-visible cleanup tools + +Objective: + +- make the system diagnosable when ownership still fails + +Work: + +- surface the tracked process metadata in run details or debug endpoints +- add a control-plane cleanup action or CLI utility for stale local run processes owned by Paperclip +- scope cleanup by run/agent/company instead of broad browser-name matching + +This should replace ad hoc scripts as the general-purpose escape hatch. + +### Phase 4: cover platform and regression cases + +Objective: + +- keep the fix from regressing and define platform behavior explicitly + +Tests to add: + +- unit tests around process-group-aware cancellation in adapter execution utilities +- heartbeat recovery tests for: + - surviving descendant tree after parent loss + - timeout cleanup + - cancellation cleanup +- platform-conditional behavior notes for Windows, where negative-PID group kill does not apply + +## Recommended first implementation slice + +The first shipping slice should be narrow: + +1. introduce process-group ownership for local heartbeat-run adapters on POSIX +2. persist group metadata on `heartbeat_runs` +3. switch timeout/cancel paths from direct-child kill to group kill +4. add one regression test that proves descendants die with the parent run + +That should address the main Chrome accumulation path without taking on the full restart-recovery design in the same patch. + +## Risks + +### 1. Over-killing unrelated processes + +If process-group boundaries are created incorrectly, cleanup could terminate more than the run owns. + +Mitigation: + +- create a fresh process group only for the spawned adapter command +- persist and target that exact group + +### 2. Cross-platform differences + +Windows does not support the POSIX negative-PID kill pattern used elsewhere in the repo. + +Mitigation: + +- ship POSIX-first +- keep direct-child fallback on Windows +- document Windows as partial until job-object or equivalent handling is designed + +### 3. Session recovery complexity + +Adopting a still-running orphaned group may look attractive but can break observability if stdout/stderr pipes are already gone. + +Mitigation: + +- default to deterministic cleanup for heartbeat runs unless adoption is explicitly proven safe + +## Recommendation + +Treat this as a Paperclip executor ownership bug, not an `agent-browser` bug. + +`agent-browser` should remain a useful repro case, but the implementation should be shared across all local child-process adapters so any descendant process tree spawned by Codex, Claude, Cursor, Gemini, Pi, or OpenCode is owned and cleaned up consistently. diff --git a/doc/plans/2026-04-08-agent-os-follow-up-plan.md b/doc/plans/2026-04-08-agent-os-follow-up-plan.md new file mode 100644 index 00000000000..52029943c5a --- /dev/null +++ b/doc/plans/2026-04-08-agent-os-follow-up-plan.md @@ -0,0 +1,261 @@ +# PAP-1229 Agent OS Follow-up Plan + +Date: 2026-04-08 +Related issue: `PAP-1229` +Companion analysis: `doc/plans/2026-04-08-agent-os-technical-report.md` + +## Goal + +Turn the `agent-os` research into a low-risk Paperclip execution plan that preserves Paperclip's control-plane model while testing the few runtime ideas that appear worth adopting. + +## Decision summary + +Paperclip should not absorb `agent-os` as a product model or orchestration layer. + +Paperclip should evaluate `agent-os` in three narrow areas: + +1. optional agent runtime for selected local adapters +2. capability-based runtime permission vocabulary +3. snapshot-backed disposable execution roots + +Everything else should stay out of scope unless those three experiments produce strong evidence. + +## Success condition + +This work is successful when Paperclip has: + +- a clear yes/no answer on whether `agent-os` is worth supporting as an execution substrate +- a concrete adapter/runtime experiment with measurable results +- a proposed runtime capability model that fits current Paperclip adapters +- a clear decision on whether snapshot-backed execution roots are worth integrating + +## Non-goals + +Do not: + +- replace Paperclip heartbeats, issues, comments, approvals, or budgets with `agent-os` primitives +- introduce Rust/sidecar requirements for all local execution paths +- migrate all adapters at once +- add runtime workflow/queue abstractions to Paperclip core + +## Existing Paperclip integration points + +The plan should stay anchored to these existing surfaces: + +- `packages/adapter-utils/src/types.ts` + - adapter contract, runtime service reporting, session metadata, and capability normalization targets +- `server/src/services/heartbeat.ts` + - execution entry point, log capture, issue comment summaries, and cost reporting +- `server/src/services/execution-workspaces.ts` + - current workspace lifecycle and git-oriented cleanup/readiness model +- `server/src/services/plugin-loader.ts` + - typed host capability boundary and extension loading patterns +- local adapter implementations in `packages/adapters/*/src/server/` + - current execution behavior to compare against an `agent-os`-backed path + +## Phase plan + +### Phase 0: constraints and experiment design + +Objective: + +- make the evaluation falsifiable before writing integration code + +Deliverables: + +- short experiment brief added to this document or a child issue +- chosen first runtime target: `pi_local` or `opencode_local` +- baseline metrics definition + +Questions to lock down: + +- what exact developer experience should improve +- what security/isolation property we expect to gain +- what failure modes are unacceptable +- whether the prototype is adapter-only or a deeper internal runtime abstraction spike + +Exit criteria: + +- a single first target chosen +- measurable comparison criteria agreed on + +Recommended metrics: + +- cold start latency +- session resume reliability across heartbeats +- transcript/log quality +- implementation complexity +- operational complexity on local dev machines + +### Phase 1: `agentos_local` spike + +Objective: + +- prove that Paperclip can drive one local agent through an `agent-os` runtime without breaking heartbeat semantics + +Suggested scope: + +- implement a new experimental adapter, `agentos_local`, or a feature-flagged runtime path under one existing adapter +- start with `pi_local` or `opencode_local` +- keep Paperclip's existing heartbeat, issue, workspace, and comment flow authoritative + +Minimum implementation shape: + +- adapter accepts model/runtime config +- `server/src/services/heartbeat.ts` still owns run lifecycle +- execution result still maps into existing `AdapterExecutionResult` +- session state still fits current `sessionParams` / `sessionDisplayId` flow + +What to verify: + +- checkout and heartbeat flow still work end to end +- resume across multiple heartbeats works +- logs/transcripts remain readable in the UI +- failure paths surface cleanly in issue comments and run logs + +Exit criteria: + +- one agent type can run reliably through the new path +- documented comparison against the existing local adapter path +- explicit recommendation: continue, pause, or abandon + +### Phase 2: capability-based runtime permissions + +Objective: + +- introduce a Paperclip-native capability vocabulary without coupling the product to `agent-os` + +Suggested scope: + +- extend adapter config schema vocabulary for runtime permissions +- prototype normalized capabilities such as: + - `fs.read` + - `fs.write` + - `network.fetch` + - `network.listen` + - `process.spawn` + - `env.read` + +Integration targets: + +- `packages/adapter-utils/src/types.ts` +- adapter config-schema support +- server-side runtime config validation +- future board-facing UI for permissions, if needed + +What to avoid: + +- building a full human policy UI before the vocabulary is proven useful +- forcing every adapter to implement capability enforcement immediately + +Exit criteria: + +- documented capability schema +- one adapter path using it meaningfully +- clear compatibility story for non-`agent-os` adapters + +### Phase 3: snapshot-backed execution root experiment + +Objective: + +- determine whether a layered/snapshotted root model improves some Paperclip workloads + +Suggested scope: + +- evaluate it only for disposable or non-repo-heavy tasks first +- keep git worktree-based repo editing as the default for codebase tasks + +Promising use cases: + +- routine-style runs +- ephemeral preview/test environments +- isolated document/artifact generation +- tasks that do not need full git history or branch semantics + +Integration targets: + +- `server/src/services/execution-workspaces.ts` +- workspace realization paths called from `server/src/services/heartbeat.ts` + +Exit criteria: + +- clear statement on which workload classes benefit +- clear statement on which workloads should stay on worktrees +- go/no-go decision for broader implementation + +### Phase 4: typed host tool evaluation + +Objective: + +- identify where Paperclip should prefer explicit typed tools over ambient shell access + +Suggested scope: + +- compare `agent-os` host-toolkit ideas with existing plugin and runtime-service surfaces +- choose 1-2 sensitive operations that should become typed tools + +Good candidates: + +- git metadata/status inspection +- runtime service inspection +- deployment/preview status retrieval +- generated artifact publishing + +Exit criteria: + +- one concrete proposal for typed-tool adoption in Paperclip +- clear statement on whether this belongs in plugins, adapters, or core services + +## Recommended sequencing + +Recommended order: + +1. Phase 0 +2. Phase 1 +3. Phase 2 +4. Phase 3 +5. Phase 4 + +Reasoning: + +- Phase 1 is the fastest way to invalidate or validate the entire `agent-os` direction +- Phase 2 is valuable even if Phase 1 is abandoned +- Phase 3 should wait until there is confidence that the runtime approach is operationally worthwhile +- Phase 4 is useful independently but should be informed by what Phase 1 and Phase 2 expose + +## Risks + +### Technical risk + +- `agent-os` introduces Rust sidecar and packaging complexity that may outweigh runtime benefits + +### Product risk + +- runtime experimentation could blur the boundary between Paperclip as control plane and Paperclip as execution platform + +### Integration risk + +- session semantics, log formatting, and failure behavior may degrade relative to current local adapters + +### Scope risk + +- a small runtime spike could expand into an adapter-system rewrite if not kept tightly bounded + +## Guardrails + +To keep this effort controlled: + +- keep all experiments behind a clearly experimental adapter or feature flag +- do not change issue/comment/approval/budget semantics to suit the runtime +- measure against current local adapters instead of judging in isolation +- stop after Phase 1 if the operational burden is already clearly too high + +## Proposed next action + +The next concrete action should be a small implementation spike issue: + +- title: `Prototype experimental agentos_local runtime for one local adapter` +- target adapter: `opencode_local` unless `pi_local` is materially easier +- expected output: code spike, short verification notes, and a continue/stop recommendation + +If leadership wants planning only and no spike yet, this document is the handoff artifact for that decision. diff --git a/doc/plans/2026-04-08-agent-os-technical-report.md b/doc/plans/2026-04-08-agent-os-technical-report.md new file mode 100644 index 00000000000..923cf7d26dd --- /dev/null +++ b/doc/plans/2026-04-08-agent-os-technical-report.md @@ -0,0 +1,397 @@ +# Agent OS Technical Report for Paperclip + +Date: 2026-04-08 +Analyzed upstream: `rivet-dev/agent-os` at commit `0063cdccd1dcb1c8e211670cd05482d70d26a5c4` (`0063cdc`), dated 2026-04-06 + +## Executive summary + +`agent-os` is not a competitor to Paperclip's core product. It is an execution substrate: an embedded, VM-like runtime for agents, tools, filesystems, and session orchestration. Paperclip is a control plane: company scoping, task hierarchy, approvals, budgets, activity logs, workspaces, and governance. + +The strongest takeaway is not "copy agent-os wholesale." The strongest takeaway is that Paperclip could selectively use its runtime ideas to improve local agent execution safety, reproducibility, and portability while keeping all company/task/governance logic in Paperclip. + +My recommendation is: + +1. Do not merge agent-os concepts into the Paperclip core product model. +2. Do evaluate an optional `agentos_local` execution adapter or internal runtime experiment. +3. Borrow a few design patterns aggressively: + - layered/snapshotted execution filesystems + - explicit capability-based runtime permissions + - a better host-tools bridge for controlled tool execution + - a normalized session capability model for agent adapters +4. Do not import its workflow/cron/queue abstractions into Paperclip core until they are reconciled with Paperclip's issue/comment/governance model. + +## What agent-os actually is + +From the repo layout and implementation, `agent-os` is a mixed TypeScript/Rust system that provides: + +- an `AgentOs` TypeScript API for creating isolated agent VMs +- a Rust kernel/sidecar that virtualizes filesystem, processes, PTYs, pipes, permissions, and networking +- an ACP-based session model for agent runtimes such as Pi, OpenCode, and Claude-style adapters +- a registry of WASM command packages and mount plugins +- optional host toolkits, cron scheduling, and filesystem mounts + +The repo is substantial already: + +- monorepo with `packages/`, `crates/`, and `registry/` +- roughly 1,200 files just across `packages/`, `crates/`, and `registry/` +- mixed implementation model: TypeScript public API plus Rust kernel/sidecar internals + +## Architecture notes + +### 1. Public runtime surface + +The main API lives in `packages/core/src/agent-os.ts` and exports an `AgentOs` class with methods such as: + +- `create()` +- `createSession()` +- `prompt()` +- `exec()` +- `spawn()` +- `snapshotRootFilesystem()` +- cron scheduling helpers + +This is an execution API, not a coordination API. + +### 2. Virtualized kernel model + +The kernel is implemented in Rust under `crates/kernel/src/`. It models: + +- virtual filesystem +- process table +- PTYs and pipes +- resource accounting +- permissioned filesystem access +- network permission checks + +That gives `agent-os` a much stronger isolation story than Paperclip's current "launch a host CLI in a workspace" local adapter approach. + +### 3. Layered filesystem and snapshots + +The filesystem design is one of the most reusable ideas. `agent-os` uses: + +- a bundled base filesystem +- a writable overlay +- optional mounted filesystems +- snapshot export/import for reusing root states + +This is cleaner than treating every execution workspace as a mutable checkout plus ad hoc cleanup. It enables reproducible starting states and cheap isolation. + +### 4. Capability-based permissions + +The kernel-level permission vocabulary is strong and concrete: + +- filesystem operations +- network operations +- child-process execution +- environment access + +The Rust kernel defaults are deny-oriented, but the high-level JS API currently serializes permissive defaults unless the caller provides a policy. That is an important nuance: the primitive is security-minded, but the product surface is still convenience-first. + +### 5. Host-tools bridge + +`agent-os` exposes host-side tools via a toolkit abstraction (`hostTool`, `toolKit`) and a local RPC bridge. This is a strong pattern because it gives the agent explicit, typed tools rather than ambient shell access to everything on the host. + +### 6. ACP session abstraction + +The session model is more uniform than most agent wrappers. It includes: + +- capabilities +- mode/config options +- permission requests +- sequenced session events +- JSON-RPC transport through ACP adapters + +This is directly relevant to Paperclip because our adapter layer still normalizes each CLI agent in a fairly bespoke way. + +## Paperclip anchor points + +The most relevant current Paperclip surfaces for any future `agent-os` integration are: + +- `packages/adapter-utils/src/types.ts` + - shared adapter contract, session metadata, runtime service reporting, environment tests, and optional `detectModel()` +- `server/src/services/heartbeat.ts` + - heartbeat execution, adapter invocation, cost capture, workspace realization, and issue-comment summaries +- `server/src/services/execution-workspaces.ts` + - execution workspace lifecycle and git readiness/cleanup logic +- `server/src/services/plugin-loader.ts` + - dynamic plugin activation, host capability boundaries, and runtime extension loading +- local adapters such as `packages/adapters/codex-local/src/server/execute.ts` and peers + - current host-CLI execution model that an `agent-os` runtime experiment would complement or replace for selected agents + +## What Paperclip can learn from it + +### 1. A safer local execution substrate + +Paperclip's local adapters currently run host CLIs in managed workspaces and rely on adapter-specific behavior plus process-level controls. That is pragmatic, but weakly isolated. + +`agent-os` shows a path toward: + +- running local agent tooling in a constrained runtime +- applying explicit network/filesystem/env policies +- reducing accidental host leakage +- making adapter behavior more portable across machines + +Best use in Paperclip: + +- as an optional runtime beneath local adapters +- or as a new adapter family for agents that can run inside ACP-compatible `agent-os` sessions + +This fits Paperclip because it improves execution safety without changing the control-plane model. + +### 2. Snapshotted execution roots instead of only mutable workspaces + +Paperclip already has strong execution-workspace concepts, but they are repo/worktree-centric. `agent-os` adds a stronger "start from known lower layers, write into a disposable upper layer" model. + +That could improve: + +- reproducible issue starts +- disposable task sandboxes +- faster reset/cleanup +- "resume from snapshot" behavior for recurring routines +- safe preview environments for risky agent operations + +This is especially interesting for tasks that do not need a full git worktree. + +### 3. A capability vocabulary for runtime governance + +Paperclip has governance at the company/task level: + +- approvals +- budgets +- activity logs +- actor permissions +- company scoping + +It has less structure at the runtime capability level. `agent-os` offers a clear vocabulary that Paperclip could adopt even without adopting the runtime itself: + +- `fs.read`, `fs.write`, `fs.mount_sensitive` +- `network.fetch`, `network.http`, `network.listen`, `network.dns` +- child process execution +- env access + +That vocabulary would improve: + +- adapter configuration schemas +- policy UIs +- execution review surfaces +- future approval gates for governed actions + +### 4. Typed host tools instead of shelling out for everything + +Paperclip's plugin system and adapters already have the beginnings of a controlled extension surface. `agent-os` reinforces the value of exposing capabilities as typed tools rather than raw shell access. + +Concrete Paperclip uses: + +- board-approved toolkits for sensitive operations +- company-scoped service tools +- plugin-defined tools with explicit schemas +- safer execution for common actions like git metadata inspection, preview lookups, deployment status checks, or document generation + +This aligns well with Paperclip's governance story. + +### 5. Better adapter normalization around sessions and capabilities + +Paperclip's adapter contract already supports execution results, session params, environment tests, skill syncing, quota windows, and optional `detectModel()`. But much of the per-agent behavior is still adapter-specific. + +`agent-os` suggests a cleaner normalization target: + +- a standard capability map +- a consistent event stream model +- explicit mode/config surfaces +- explicit permission request semantics + +Paperclip does not need ACP everywhere, but it would benefit from a more formal internal session capability model inspired by this. + +### 6. On-demand heavy sandbox escalation + +One of the best architectural choices in `agent-os` is that it does not pretend every workload fits the lightweight runtime. It has a sandbox extension for workloads that need a fuller environment. + +Paperclip can adopt that philosophy directly: + +- lightweight execution by default +- escalate to full worktree / container / remote sandbox only when needed +- keep the escalation explicit in the issue/run model + +That is better than forcing all tasks into the heaviest environment up front. + +## What does not fit Paperclip well + +### 1. Its built-in orchestration primitives overlap the wrong layer + +`agent-os` includes cron/session/workflow style primitives inside the runtime package. Paperclip already has higher-level orchestration concepts: + +- issues/comments +- heartbeat runs +- approvals +- company/org structure +- execution workspaces +- budget enforcement + +If Paperclip copied `agent-os` cron/workflow/queue ideas directly into core, we would likely duplicate orchestration across two layers. That would blur ownership and make debugging harder. + +Paperclip should keep orchestration authoritative at the control-plane layer. + +### 2. It is not company-scoped or governance-native + +`agent-os` is runtime-first, not company-first. It has no native concepts for: + +- company boundaries +- board/operator actor types +- audit logs for business actions +- issue hierarchy +- approval routing +- budget hard-stop behavior + +Those are Paperclip's differentiators. They should not be displaced by runtime abstractions. + +### 3. It introduces meaningful implementation complexity + +Adopting `agent-os` deeply would add: + +- Rust build/runtime complexity +- sidecar lifecycle management +- new failure modes across JS/Rust boundaries +- more packaging and platform compatibility work +- another abstraction layer for debugging already-complex local adapters + +This is justified only if we want stronger local isolation or portability. It is not justified as a general refactor. + +### 4. Its security model is not a drop-in governance solution + +The permission model is good, but it is low-level. Paperclip would still need to answer: + +- who can authorize a capability +- how approval decisions are logged +- how policies are scoped by company/project/issue/agent +- how runtime permissions interact with budgets and task status + +In other words, `agent-os` can supply enforcement primitives, not the control policy system itself. + +### 5. The agent compatibility story is still selective + +The repo is explicit that some runtimes are planned, partial, or still being adapted. In practice this means: + +- good ideas for ACP-native or compatible agents +- less certainty for every CLI agent we support today +- real integration work for Codex/Cursor/Gemini-style Paperclip adapters + +So the main near-term value is not universal replacement. It is selective use where compatibility is strong. + +## Concrete recommendations for Paperclip + +### Recommendation A: prototype an optional `agentos_local` adapter + +This is the highest-value experiment. + +Goal: + +- run one supported agent type inside `agent-os` +- keep Paperclip heartbeat/task/workspace/budget logic unchanged +- evaluate startup time, isolation, transcript quality, and operational complexity + +Good first target: + +- `pi_local` or `opencode_local` + +Why not start with Codex: + +- Paperclip's Codex adapter is already important and carries repo-specific behavior +- `agent-os`'s Codex story is present in the registry/docs, but the safest path is to validate the runtime on a less central adapter first + +Success criteria: + +- heartbeat can invoke the adapter reliably +- session resume works across heartbeats +- Paperclip still records logs, summaries, cost metadata, and issue comments normally +- runtime permissions can be configured without breaking common tasks + +### Recommendation B: adopt capability vocabulary into adapter configs + +Even without using `agent-os`, Paperclip should consider standardizing adapter/runtime permissions around a vocabulary like: + +- filesystem +- network +- subprocess/tool execution +- environment access + +This would improve: + +- schema-driven adapter UIs +- future approvals +- observability +- policy portability across adapters + +### Recommendation C: explore snapshot-backed execution workspaces + +Paperclip should evaluate whether some execution workspaces can be backed by: + +- a reusable lower snapshot +- a disposable upper layer +- optional mounts for project data or artifacts + +This is most valuable for: + +- non-repo tasks +- repeatable routines +- preview/test environments +- isolation-heavy local execution + +It is less urgent for full repo editing flows that already benefit from git worktrees. + +### Recommendation D: strengthen typed tool surfaces + +Paperclip plugins and adapters should continue moving toward explicit typed tools over ad hoc shell access. `agent-os` confirms that this is the right direction. + +This is a good fit for: + +- plugin tools +- workspace runtime services +- governed operations that need approval or auditability + +### Recommendation E: do not import runtime-level workflows into Paperclip core + +Paperclip should not copy `agent-os` cron/workflow/queue concepts into core orchestration yet. + +If we want them later, they must map cleanly onto: + +- issues +- comments +- heartbeats +- approvals +- budgets +- activity logs + +Without that mapping, they would create a second orchestration system inside the product. + +## A practical integration map + +### Best near-term fits + +- optional local adapter runtime +- runtime capability schema +- typed host-tool ideas for plugins/adapters +- snapshot ideas for disposable execution roots + +### Medium-term fits + +- stronger session capability normalization across adapters +- policy-aware runtime permission UI +- selective ACP-inspired event normalization + +### Poor fits right now + +- moving Paperclip orchestration into agent-os workflows +- replacing company/task/governance models with runtime constructs +- making Rust sidecars a mandatory dependency for all local execution + +## Bottom line + +`agent-os` is useful to Paperclip as an execution technology reference, not as a product model. + +Paperclip should treat it the same way it treats sandboxes or agent CLIs: + +- execution substrate underneath the control plane +- optional where the tradeoff is worth it +- never the source of truth for company/task/governance state + +If we do one thing from this report, it should be a narrowly scoped `agentos_local` experiment plus a design pass on capability-based runtime permissions. Those two ideas have the best upside and the lowest architectural risk. diff --git a/doc/spec/agent-runs.md b/doc/spec/agent-runs.md index f0d02275409..56e2eae134d 100644 --- a/doc/spec/agent-runs.md +++ b/doc/spec/agent-runs.md @@ -249,7 +249,7 @@ Runs local `claude` CLI directly. "cwd": "/absolute/or/relative/path", "promptTemplate": "You are agent {{agent.id}} ...", "model": "optional-model-id", - "maxTurnsPerRun": 300, + "maxTurnsPerRun": 1000, "dangerouslySkipPermissions": true, "env": {"KEY": "VALUE"}, "extraArgs": [], diff --git a/Dockerfile.onboard-smoke b/docker/Dockerfile.onboard-smoke similarity index 100% rename from Dockerfile.onboard-smoke rename to docker/Dockerfile.onboard-smoke diff --git a/docker-compose.quickstart.yml b/docker/docker-compose.quickstart.yml similarity index 86% rename from docker-compose.quickstart.yml rename to docker/docker-compose.quickstart.yml index 82006606b1a..65705059aab 100644 --- a/docker-compose.quickstart.yml +++ b/docker/docker-compose.quickstart.yml @@ -1,7 +1,7 @@ services: paperclip: build: - context: . + context: .. dockerfile: Dockerfile ports: - "${PAPERCLIP_PORT:-3100}:3100" @@ -15,4 +15,4 @@ services: PAPERCLIP_PUBLIC_URL: "${PAPERCLIP_PUBLIC_URL:-http://localhost:3100}" BETTER_AUTH_SECRET: "${BETTER_AUTH_SECRET:?BETTER_AUTH_SECRET must be set}" volumes: - - "${PAPERCLIP_DATA_DIR:-./data/docker-paperclip}:/paperclip" + - "${PAPERCLIP_DATA_DIR:-../data/docker-paperclip}:/paperclip" diff --git a/docker-compose.untrusted-review.yml b/docker/docker-compose.untrusted-review.yml similarity index 97% rename from docker-compose.untrusted-review.yml rename to docker/docker-compose.untrusted-review.yml index ff11148ac9f..ca1ee09805a 100644 --- a/docker-compose.untrusted-review.yml +++ b/docker/docker-compose.untrusted-review.yml @@ -1,7 +1,7 @@ services: review: build: - context: . + context: .. dockerfile: docker/untrusted-review/Dockerfile init: true tty: true diff --git a/docker-compose.yml b/docker/docker-compose.yml similarity index 94% rename from docker-compose.yml rename to docker/docker-compose.yml index 94f4291f173..b26b396ab82 100644 --- a/docker-compose.yml +++ b/docker/docker-compose.yml @@ -16,7 +16,9 @@ services: - pgdata:/var/lib/postgresql/data server: - build: . + build: + context: .. + dockerfile: Dockerfile ports: - "3100:3100" environment: diff --git a/docker/quadlet/paperclip-db.container b/docker/quadlet/paperclip-db.container new file mode 100644 index 00000000000..5cb39cc2072 --- /dev/null +++ b/docker/quadlet/paperclip-db.container @@ -0,0 +1,20 @@ +[Unit] +Description=PostgreSQL for Paperclip + +[Container] +Image=docker.io/library/postgres:17-alpine +ContainerName=paperclip-db +Pod=paperclip.pod +Volume=paperclip-pgdata:/var/lib/postgresql/data +EnvironmentFile=%h/.config/containers/systemd/paperclip.env +HealthCmd=pg_isready -U $POSTGRES_USER -d $POSTGRES_DB -h localhost || exit 1 +HealthInterval=15s +HealthTimeout=5s +HealthRetries=5 + +[Service] +Restart=on-failure +TimeoutStartSec=60 + +[Install] +WantedBy=default.target diff --git a/docker/quadlet/paperclip.container b/docker/quadlet/paperclip.container new file mode 100644 index 00000000000..2583916cd25 --- /dev/null +++ b/docker/quadlet/paperclip.container @@ -0,0 +1,23 @@ +[Unit] +Description=Paperclip AI Agent Orchestrator +Requires=paperclip-db.service +After=paperclip-db.service + +[Container] +Image=paperclip-local +ContainerName=paperclip +Pod=paperclip.pod +Volume=%h/.local/share/paperclip:/paperclip:Z +Environment=HOST=0.0.0.0 +Environment=PAPERCLIP_HOME=/paperclip +Environment=PAPERCLIP_DEPLOYMENT_MODE=authenticated +Environment=PAPERCLIP_DEPLOYMENT_EXPOSURE=private +Environment=PAPERCLIP_PUBLIC_URL=http://localhost:3100 +EnvironmentFile=%h/.config/containers/systemd/paperclip.env + +[Service] +Restart=on-failure +TimeoutStartSec=120 + +[Install] +WantedBy=default.target diff --git a/docker/quadlet/paperclip.pod b/docker/quadlet/paperclip.pod new file mode 100644 index 00000000000..45297e96334 --- /dev/null +++ b/docker/quadlet/paperclip.pod @@ -0,0 +1,3 @@ +[Pod] +PodName=paperclip +PublishPort=3100:3100 diff --git a/docs/adapters/adapter-ui-parser.md b/docs/adapters/adapter-ui-parser.md new file mode 100644 index 00000000000..a9390001f6b --- /dev/null +++ b/docs/adapters/adapter-ui-parser.md @@ -0,0 +1,287 @@ +--- +title: Adapter UI Parser Contract +summary: Ship a custom run-log parser so the Paperclip UI renders your adapter's output correctly +--- + +When Paperclip runs an agent, stdout is streamed to the UI in real time. The UI needs a **parser** to convert raw stdout lines into structured transcript entries (tool calls, tool results, assistant messages, system events). Without a custom parser, the UI falls back to a generic shell parser that treats every non-system line as `assistant` output — tool commands leak as plain text, durations are lost, and errors are invisible. + +## The Problem + +Most agent CLIs emit structured stdout with tool calls, progress indicators, and multi-line output. For example: + +``` +[hermes] Session resumed: abc123 +┊ 💬 Thinking about how to approach this... +┊ $ ls /home/user/project +┊ [done] $ ls /home/user/project — /src /README.md 0.3s +┊ 💬 I see the project structure. Let me read the README. +┊ read /home/user/project/README.md +┊ [done] read — Project Overview: A CLI tool for... 1.2s +The project is a CLI tool. Here's what I found: +- It uses TypeScript +- Tests are in /tests +``` + +Without a parser, the UI shows all of this as raw `assistant` text — the tool calls and results are indistinguishable from the agent's actual response. + +With a parser, the UI renders: + +- `Thinking about how to approach this...` as a collapsible thinking block +- `$ ls /home/user/project` as a tool call card (collapsed) +- `0.3s` duration as a tool result card +- `The project is a CLI tool...` as the assistant's response + +## How It Works + +``` +┌──────────────────┐ package.json ┌──────────────────┐ +│ Adapter Package │─── exports["./ui-parser"] ──→│ dist/ui-parser.js │ +│ (npm / local) │ │ (zero imports) │ +└──────────────────┘ └────────┬─────────┘ + │ plugin-loader reads at startup + ▼ +┌──────────────────┐ GET /api/:type/ui-parser.js ┌──────────────────┐ +│ Paperclip Server │◄────────────────────────────────│ uiParserCache │ +│ (in-memory) │ └──────────────────┘ +└────────┬─────────┘ + │ serves JS to browser + ▼ +┌──────────────────┐ fetch() + eval ┌──────────────────┐ +│ Paperclip UI │─────────────────────→│ parseStdoutLine │ +│ (dynamic loader) │ registers parser │ (per-adapter) │ +└──────────────────┘ └──────────────────┘ +``` + +1. **Build time** — You compile `src/ui-parser.ts` to `dist/ui-parser.js` (zero runtime imports) +2. **Server startup** — Plugin loader reads the file and caches it in memory +3. **UI load** — When the user opens a run, the UI fetches the parser from `GET /api/:type/ui-parser.js` +4. **Runtime** — The fetched module is eval'd and registered. All subsequent lines use the real parser + +## Contract: package.json + +### 1. `paperclip.adapterUiParser` — contract version + +```json +{ + "paperclip": { + "adapterUiParser": "1.0.0" + } +} +``` + +The Paperclip host checks this field. If the major version is unsupported, the host logs a warning and falls back to the generic parser instead of executing potentially incompatible code. + +| Host expects | Adapter declares | Result | +|---|---|---| +| `1.x` | `1.0.0` | Parser loaded | +| `1.x` | `2.0.0` | Warning logged, generic parser used | +| `1.x` | (missing) | Parser loaded (grace period — future versions may require it) | + +### 2. `exports["./ui-parser"]` — file path + +```json +{ + "exports": { + ".": "./dist/server/index.js", + "./ui-parser": "./dist/ui-parser.js" + } +} +``` + +## Contract: Module Exports + +Your `dist/ui-parser.js` must export **at least one** of: + +### `parseStdoutLine(line: string, ts: string): TranscriptEntry[]` + +Static parser. Called for each line of adapter stdout. + +```ts +export function parseStdoutLine(line: string, ts: string): TranscriptEntry[] { + if (line.startsWith("[my-agent]")) { + return [{ kind: "system", ts, text: line }]; + } + return [{ kind: "assistant", ts, text: line }]; +} +``` + +### `createStdoutParser(): { parseLine(line, ts): TranscriptEntry[]; reset(): void }` + +Stateful parser factory. Preferred if your parser needs to track multi-line continuation, command nesting, or other cross-call state. + +```ts +let counter = 0; + +export function createStdoutParser() { + let suppressContinuation = false; + + function parseLine(line: string, ts: string): TranscriptEntry[] { + const trimmed = line.trim(); + if (!trimmed) return []; + + if (suppressContinuation) { + if (/^[\d.]+s$/.test(trimmed)) { + suppressContinuation = false; + return []; + } + return []; // swallow continuation lines + } + + if (trimmed.startsWith("[tool-done]")) { + const id = `tool-${++counter}`; + suppressContinuation = true; + return [ + { kind: "tool_call", ts, name: "shell", input: {}, toolUseId: id }, + { kind: "tool_result", ts, toolUseId: id, content: trimmed, isError: false }, + ]; + } + + return [{ kind: "assistant", ts, text: trimmed }]; + } + + function reset() { + suppressContinuation = false; + } + + return { parseLine, reset }; +} +``` + +If both are exported, `createStdoutParser` takes priority. + +## Contract: TranscriptEntry + +Each entry must match one of these discriminated union shapes: + +```ts +// Assistant message +{ kind: "assistant"; ts: string; text: string; delta?: boolean } + +// Thinking / reasoning +{ kind: "thinking"; ts: string; text: string; delta?: boolean } + +// User message (rare — usually from agent-initiated prompts) +{ kind: "user"; ts: string; text: string } + +// Tool invocation +{ kind: "tool_call"; ts: string; name: string; input: unknown; toolUseId?: string } + +// Tool result +{ kind: "tool_result"; ts: string; toolUseId: string; content: string; isError: boolean } + +// System / adapter messages +{ kind: "system"; ts: string; text: string } + +// Stderr / errors +{ kind: "stderr"; ts: string; text: string } + +// Raw stdout (fallback) +{ kind: "stdout"; ts: string; text: string } +``` + +### Linking tool calls to results + +Use `toolUseId` to pair `tool_call` and `tool_result` entries. The UI renders them as collapsible cards. + +```ts +const id = `my-tool-${++counter}`; +return [ + { kind: "tool_call", ts, name: "read", input: { path: "/src/main.ts" }, toolUseId: id }, + { kind: "tool_result", ts, toolUseId: id, content: "const main = () => {...}", isError: false }, +]; +``` + +### Error handling + +Set `isError: true` on tool results to show a red indicator: + +```ts +{ kind: "tool_result", ts, toolUseId: id, content: "ENOENT: no such file", isError: true } +``` + +## Constraints + +1. **Zero runtime imports.** Your file is loaded via `URL.createObjectURL` + dynamic `import()` in the browser. No `import`, no `require`, no top-level `await`. + +2. **No DOM / Node.js APIs.** Runs in a browser sandbox. Use only vanilla JS (ES2020+). + +3. **No side effects.** Module-level code must not modify globals, access `window`, or perform I/O. Only declare and export functions. + +4. **Deterministic.** Given the same `(line, ts)` input, the same output must be produced. This matters for log replay. + +5. **Error-tolerant.** Never throw. Return `[{ kind: "stdout", ts, text: line }]` for any line you can't parse, rather than crashing the transcript. + +6. **File size.** Keep under 50 KB. This is served per-request and eval'd in the browser. + +## Lifecycle + +| Event | What happens | +|---|---| +| Server starts | Plugin loader reads `exports["./ui-parser"]`, reads the file, caches in memory | +| UI opens run | `getUIAdapter(type)` called. If no built-in parser, kicks off async `fetch(/api/:type/ui-parser.js)` | +| First lines arrive | Generic process parser handles them immediately (no blocking). Dynamic parser loads in background | +| Parser loads | `registerUIAdapter()` called. All subsequent line parsing uses the real parser | +| Parser fails (404, eval error) | Warning logged to console. Generic parser continues. Failed type is cached — no retries | +| Server restart | In-memory cache is repopulated from adapter packages | + +## Error Behavior + +| Failure | What happens | +|---|---| +| Module syntax error (import fails) | Caught, logged, falls back to generic parser. No retries. | +| Returns wrong shape | Individual entries with missing fields are silently ignored by the transcript builder. | +| Throws at runtime | Caught per-line. That line falls back to generic. Parser stays registered for future lines. | +| 404 (no ui-parser export) | Type added to failed-loads set. Generic parser from first call onward. | +| Contract version mismatch | Server logs warning, skips loading. Generic parser used. | + +## Building + +```sh +# Compile TypeScript to JavaScript +tsc src/ui-parser.ts --outDir dist --target ES2020 --module ES2020 --declaration false +``` + +Your `tsconfig.json` can handle this automatically — just make sure `ui-parser.ts` is included in the build and outputs to `dist/ui-parser.js`. + +## Testing + +Test your parser locally by running it against sample stdout: + +```ts +// test-parser.ts +import { createStdoutParser } from "./dist/ui-parser.js"; + +const parser = createStdoutParser(); +const sampleLines = [ + "[my-agent] Starting session abc123", + "Thinking about the task...", + "$ ls /home/user/project", + "[done] $ ls — /src /README.md 0.3s", + "I'll read the README now.", + "Error: file not found", +]; + +for (const line of sampleLines) { + const entries = parser.parseLine(line, new Date().toISOString()); + for (const entry of entries) { + console.log(` ${entry.kind}:`, entry.text ?? entry.name ?? entry.content); + } +} +``` + +Run with: `npx tsx test-parser.ts` + +## Skipping the UI Parser + +If your adapter's stdout is simple (no tool markers, no special formatting), you can skip the UI parser entirely. The generic `process` parser will handle it — every non-system line becomes `assistant` output. This is fine for: + +- Agents that output plain text responses +- Custom scripts that just print results +- Simple CLIs without structured output + +To skip it, simply don't include `exports["./ui-parser"]` in your `package.json`. + +## Next Steps + +- [External Adapters](/adapters/external-adapters) — full guide to building adapter packages +- [Creating an Adapter](/adapters/creating-an-adapter) — adapter internals and built-in integration diff --git a/docs/adapters/claude-local.md b/docs/adapters/claude-local.md index c6029e0c6bd..fc64fcf8d83 100644 --- a/docs/adapters/claude-local.md +++ b/docs/adapters/claude-local.md @@ -21,7 +21,7 @@ The `claude_local` adapter runs Anthropic's Claude Code CLI locally. It supports | `timeoutSec` | number | No | Process timeout (0 = no timeout) | | `graceSec` | number | No | Grace period before force-kill | | `maxTurnsPerRun` | number | No | Max agentic turns per heartbeat (defaults to `300`) | -| `dangerouslySkipPermissions` | boolean | No | Skip permission prompts (dev only) | +| `dangerouslySkipPermissions` | boolean | No | Skip permission prompts (default: `true`); required for headless runs where interactive approval is impossible | ## Prompt Templates diff --git a/docs/adapters/creating-an-adapter.md b/docs/adapters/creating-an-adapter.md index fae0e4b33be..ae5e4ccbe53 100644 --- a/docs/adapters/creating-an-adapter.md +++ b/docs/adapters/creating-an-adapter.md @@ -9,23 +9,40 @@ Build a custom adapter to connect Paperclip to any agent runtime. If you're using Claude Code, the `.agents/skills/create-agent-adapter` skill can guide you through the full adapter creation process interactively. Just ask Claude to create a new adapter and it will walk you through each step. +## Two Paths + +| | Built-in | External Plugin | +|---|---|---| +| Source | Inside `paperclip-fork` | Separate npm package | +| Distribution | Ships with Paperclip | Independent npm publish | +| UI parser | Static import | Dynamic load from API | +| Registration | Edit 3 registries | Auto-loaded at startup | +| Best for | Core adapters, contributors | Third-party adapters, internal tools | + +For most cases, **build an external adapter plugin**. It's cleaner, independently versioned, and doesn't require modifying Paperclip's source. See [External Adapters](/adapters/external-adapters) for the full guide. + +The rest of this page covers the shared internals that both paths use. + ## Package Structure ``` -packages/adapters// +packages/adapters// # built-in + ── or ── +my-adapter/ # external plugin package.json tsconfig.json src/ index.ts # Shared metadata server/ - index.ts # Server exports + index.ts # Server exports (createServerAdapter) execute.ts # Core execution logic parse.ts # Output parsing test.ts # Environment diagnostics ui/ - index.ts # UI exports - parse-stdout.ts # Transcript parser + index.ts # UI exports (built-in only) + parse-stdout.ts # Transcript parser (built-in only) build-config.ts # Config builder + ui-parser.ts # Self-contained UI parser (external — see [UI Parser Contract](/adapters/adapter-ui-parser)) cli/ index.ts # CLI exports format-event.ts # Terminal formatter @@ -46,6 +63,9 @@ Use when: ... Don't use when: ... Core fields: ... `; + +// Required for external adapters (plugin-loader convention) +export { createServerAdapter } from "./server/index.js"; ``` ## Step 2: Server Execute @@ -54,7 +74,7 @@ Core fields: ... Key responsibilities: -1. Read config using safe helpers (`asString`, `asNumber`, etc.) +1. Read config using safe helpers (`asString`, `asNumber`, etc.) from `@paperclipai/adapter-utils/server-utils` 2. Build environment with `buildPaperclipEnv(agent)` plus context vars 3. Resolve session state from `runtime.sessionParams` 4. Render prompt with `renderTemplate(template, data)` @@ -62,27 +82,102 @@ Key responsibilities: 6. Parse output for usage, costs, session state, errors 7. Handle unknown session errors (retry fresh, set `clearSession: true`) +### Available Helpers + +| Helper | Source | Purpose | +|--------|--------|---------| +| `runChildProcess(cmd, opts)` | `@paperclipai/adapter-utils/server-utils` | Spawn with timeout, grace, streaming | +| `buildPaperclipEnv(agent)` | `@paperclipai/adapter-utils/server-utils` | Inject `PAPERCLIP_*` env vars | +| `renderTemplate(tpl, data)` | `@paperclipai/adapter-utils/server-utils` | `{{variable}}` substitution | +| `asString(v)` | `@paperclipai/adapter-utils` | Safe config value extraction | +| `asNumber(v)` | `@paperclipai/adapter-utils` | Safe number extraction | + +### AdapterExecutionContext + +```ts +interface AdapterExecutionContext { + runId: string; + agent: { id: string; companyId: string; name: string; adapterConfig: unknown }; + runtime: { sessionId: string | null; sessionParams: Record | null }; + config: Record; // agent's adapterConfig + context: Record; // task, wake reason, etc. + onLog: (stream: "stdout" | "stderr", chunk: string) => Promise; + onMeta?: (meta: AdapterInvocationMeta) => Promise; + onSpawn?: (meta: { pid: number; startedAt: string }) => Promise; +} +``` + +### AdapterExecutionResult + +```ts +interface AdapterExecutionResult { + exitCode: number | null; + signal: string | null; + timedOut: boolean; + errorMessage?: string | null; + usage?: { inputTokens: number; outputTokens: number }; + sessionParams?: Record | null; // persist across heartbeats + sessionDisplayId?: string | null; + provider?: string | null; + model?: string | null; + costUsd?: number | null; + clearSession?: boolean; // set true to force fresh session on next wake +} +``` + ## Step 3: Environment Test `src/server/test.ts` validates the adapter config before running. Return structured diagnostics: -- `error` for invalid/unusable setup -- `warn` for non-blocking issues -- `info` for successful checks +| Level | Meaning | Effect | +|-------|---------|--------| +| `error` | Invalid or unusable setup | Blocks execution | +| `warn` | Non-blocking issue | Shown with yellow indicator | +| `info` | Successful check | Shown in test results | -## Step 4: UI Module +```ts +export async function testEnvironment( + ctx: AdapterEnvironmentTestContext, +): Promise { + return { + adapterType: ctx.adapterType, + status: "pass", // "pass" | "warn" | "fail" + checks: [ + { level: "info", message: "CLI v1.2.0 detected", code: "cli_detected" }, + { level: "warn", message: "No API key found", hint: "Set ANTHROPIC_API_KEY", code: "no_key" }, + ], + testedAt: new Date().toISOString(), + }; +} +``` + +## Step 4: UI Module (Built-in Only) + +For built-in adapters registered in Paperclip's source: - `parse-stdout.ts` — converts stdout lines to `TranscriptEntry[]` for the run viewer - `build-config.ts` — converts form values to `adapterConfig` JSON - Config fields React component in `ui/src/adapters//config-fields.tsx` +For external adapters, use a self-contained `ui-parser.ts` instead. See the [UI Parser Contract](/adapters/adapter-ui-parser). + ## Step 5: CLI Module `format-event.ts` — pretty-prints stdout for `paperclipai run --watch` using `picocolors`. -## Step 6: Register +```ts +export function formatStdoutEvent(line: string, debug: boolean): void { + if (line.startsWith("[tool-done]")) { + console.log(chalk.green(` ✓ ${line}`)); + } else { + console.log(` ${line}`); + } +} +``` + +## Step 6: Register (Built-in Only) Add the adapter to all three registries: @@ -90,6 +185,24 @@ Add the adapter to all three registries: 2. `ui/src/adapters/registry.ts` 3. `cli/src/adapters/registry.ts` +For external adapters, registration is automatic — the plugin loader handles it. + +## Session Persistence + +If your agent runtime supports conversation continuity across heartbeats: + +1. Return `sessionParams` from `execute()` (e.g., `{ sessionId: "abc123" }`) +2. Read `runtime.sessionParams` on the next wake to resume +3. Optionally implement a `sessionCodec` for validation and display + +```ts +export const sessionCodec: AdapterSessionCodec = { + deserialize(raw) { /* validate raw session data */ }, + serialize(params) { /* serialize for storage */ }, + getDisplayId(params) { /* human-readable session label */ }, +}; +``` + ## Skills Injection Make Paperclip skills discoverable to your agent runtime without writing to the agent's working directory: @@ -105,3 +218,10 @@ Make Paperclip skills discoverable to your agent runtime without writing to the - Inject secrets via environment variables, not prompts - Configure network access controls if the runtime supports them - Always enforce timeout and grace period +- The UI parser module runs in a browser sandbox — zero runtime imports, no side effects + +## Next Steps + +- [External Adapters](/adapters/external-adapters) — build a standalone adapter plugin +- [UI Parser Contract](/adapters/adapter-ui-parser) — ship a custom run-log parser +- [How Agents Work](/guides/agent-developer/how-agents-work) — the heartbeat lifecycle diff --git a/docs/adapters/external-adapters.md b/docs/adapters/external-adapters.md new file mode 100644 index 00000000000..3c814fc9abb --- /dev/null +++ b/docs/adapters/external-adapters.md @@ -0,0 +1,392 @@ +--- +title: External Adapters +summary: Build, package, and distribute adapters as plugins without modifying Paperclip source +--- + +Paperclip supports external adapter plugins that can be installed from npm packages or local directories. External adapters work exactly like built-in adapters — they execute agents, parse output, and render transcripts — but they live in their own package and don't require changes to Paperclip's source code. + +## Built-in vs External + +| | Built-in | External | +|---|---|---| +| Source location | Inside `paperclip-fork/packages/adapters/` | Separate npm package or local directory | +| Registration | Hardcoded in three registries | Loaded at startup via plugin system | +| UI parser | Static import at build time | Dynamically loaded from API (see [UI Parser](/adapters/adapter-ui-parser)) | +| Distribution | Ships with Paperclip | Published to npm or linked via `file:` | +| Updates | Requires Paperclip release | Independent versioning | + +## Quick Start + +### Minimal Package Structure + +``` +my-adapter/ + package.json + tsconfig.json + src/ + index.ts # Shared metadata (type, label, models) + server/ + index.ts # createServerAdapter() factory + execute.ts # Core execution logic + parse.ts # Output parsing + test.ts # Environment diagnostics + ui-parser.ts # Self-contained UI transcript parser +``` + +### package.json + +```json +{ + "name": "my-paperclip-adapter", + "version": "1.0.0", + "type": "module", + "license": "MIT", + "paperclip": { + "adapterUiParser": "1.0.0" + }, + "exports": { + ".": "./dist/index.js", + "./server": "./dist/server/index.js", + "./ui-parser": "./dist/ui-parser.js" + }, + "files": ["dist"], + "scripts": { + "build": "tsc" + }, + "dependencies": { + "@paperclipai/adapter-utils": "^2026.325.0", + "picocolors": "^1.1.0" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "typescript": "^5.7.0" + } +} +``` + +Key fields: + +| Field | Purpose | +|-------|---------| +| `exports["."]` | Entry point — must export `createServerAdapter` | +| `exports["./ui-parser"]` | Self-contained UI parser module (optional but recommended) | +| `paperclip.adapterUiParser` | Contract version for the UI parser (`"1.0.0"`) | +| `files` | Limits what gets published — only `dist/` | + +### tsconfig.json + +```json +{ + "compilerOptions": { + "target": "ES2022", + "module": "Node16", + "moduleResolution": "Node16", + "outDir": "dist", + "rootDir": "src", + "declaration": true, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true + }, + "include": ["src"] +} +``` + +## Server Module + +The plugin loader calls `createServerAdapter()` from your package root. This function must return a `ServerAdapterModule`. + +### src/index.ts + +```ts +export const type = "my_adapter"; // snake_case, globally unique +export const label = "My Agent (local)"; + +export const models = [ + { id: "model-a", label: "Model A" }, +]; + +export const agentConfigurationDoc = `# my_adapter configuration +Use when: ... +Don't use when: ... +`; + +// Required by plugin-loader convention +export { createServerAdapter } from "./server/index.js"; +``` + +### src/server/index.ts + +```ts +import type { ServerAdapterModule } from "@paperclipai/adapter-utils"; +import { type, models, agentConfigurationDoc } from "../index.js"; +import { execute } from "./execute.js"; +import { testEnvironment } from "./test.js"; + +export function createServerAdapter(): ServerAdapterModule { + return { + type, + execute, + testEnvironment, + models, + agentConfigurationDoc, + }; +} +``` + +### src/server/execute.ts + +The core execution function. Receives an `AdapterExecutionContext` and returns an `AdapterExecutionResult`. + +```ts +import type { + AdapterExecutionContext, + AdapterExecutionResult, +} from "@paperclipai/adapter-utils"; + +import { + runChildProcess, + buildPaperclipEnv, + renderTemplate, +} from "@paperclipai/adapter-utils/server-utils"; + +export async function execute( + ctx: AdapterExecutionContext, +): Promise { + const { config, agent, runtime, context, onLog, onMeta } = ctx; + + // 1. Read config with safe helpers + const cwd = String(config.cwd ?? "/tmp"); + const command = String(config.command ?? "my-agent"); + const timeoutSec = Number(config.timeoutSec ?? 300); + + // 2. Build environment with Paperclip vars injected + const env = buildPaperclipEnv(agent); + + // 3. Render prompt template + const prompt = config.promptTemplate + ? renderTemplate(String(config.promptTemplate), { + agentId: agent.id, + agentName: agent.name, + companyId: agent.companyId, + runId: ctx.runId, + taskId: context.taskId ?? "", + taskTitle: context.taskTitle ?? "", + }) + : "Continue your work."; + + // 4. Spawn process + const result = await runChildProcess(command, { + args: [prompt], + cwd, + env, + timeout: timeoutSec * 1000, + graceMs: 10_000, + onStdout: (chunk) => onLog("stdout", chunk), + onStderr: (chunk) => onLog("stderr", chunk), + }); + + // 5. Return structured result + return { + exitCode: result.exitCode, + timedOut: result.timedOut, + // Include session state for persistence + sessionParams: { /* ... */ }, + }; +} +``` + +#### Available Helpers from `@paperclipai/adapter-utils` + +| Helper | Purpose | +|--------|---------| +| `runChildProcess(command, opts)` | Spawn a child process with timeout, grace period, and streaming callbacks | +| `buildPaperclipEnv(agent)` | Inject `PAPERCLIP_*` environment variables | +| `renderTemplate(template, data)` | `{{variable}}` substitution in prompt templates | +| `asString(v)`, `asNumber(v)`, `asBoolean(v)` | Safe config value extraction | + +### src/server/test.ts + +Validates the adapter configuration before running. Returns structured diagnostics. + +```ts +import type { + AdapterEnvironmentTestContext, + AdapterEnvironmentTestResult, +} from "@paperclipai/adapter-utils"; + +export async function testEnvironment( + ctx: AdapterEnvironmentTestContext, +): Promise { + const checks = []; + + // Example: check CLI is installed + checks.push({ + level: "info", + message: "My Agent CLI v1.2.0 detected", + code: "cli_detected", + }); + + // Example: check working directory + const cwd = String(ctx.config.cwd ?? ""); + if (!cwd.startsWith("/")) { + checks.push({ + level: "error", + message: `Working directory must be absolute: "${cwd}"`, + hint: "Use /home/user/project or /workspace", + code: "invalid_cwd", + }); + } + + return { + adapterType: ctx.adapterType, + status: checks.some(c => c.level === "error") ? "fail" : "pass", + checks, + testedAt: new Date().toISOString(), + }; +} +``` + +Check levels: + +| Level | Meaning | Effect | +|-------|---------|--------| +| `info` | Informational | Shown in test results | +| `warn` | Non-blocking issue | Shown with yellow indicator | +| `error` | Blocks execution | Prevents agent from running | + +## Installation + +### From npm + +```sh +# Via the Paperclip UI +# Settings → Adapters → Install from npm → "my-paperclip-adapter" + +# Or via API +curl -X POST http://localhost:3102/api/adapters \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"packageName": "my-paperclip-adapter"}' +``` + +### From local directory + +```sh +curl -X POST http://localhost:3102/api/adapters \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"localPath": "/home/user/my-adapter"}' +``` + +Local adapters are symlinked into Paperclip's adapter directory. Changes to the source are picked up on server restart. + +### Via adapter-plugins.json + +For development, you can also edit `~/.paperclip/adapter-plugins.json` directly: + +```json +[ + { + "packageName": "my-paperclip-adapter", + "localPath": "/home/user/my-adapter", + "type": "my_adapter", + "installedAt": "2026-03-30T12:00:00.000Z" + } +] +``` + +## Optional: Session Persistence + +If your agent runtime supports sessions (conversation continuity across heartbeats), implement a session codec: + +```ts +import type { AdapterSessionCodec } from "@paperclipai/adapter-utils"; + +export const sessionCodec: AdapterSessionCodec = { + deserialize(raw) { + if (typeof raw !== "object" || raw === null) return null; + const r = raw as Record; + return r.sessionId ? { sessionId: String(r.sessionId) } : null; + }, + serialize(params) { + return params?.sessionId ? { sessionId: String(params.sessionId) } : null; + }, + getDisplayId(params) { + return params?.sessionId ? String(params.sessionId) : null; + }, +}; +``` + +Include it in `createServerAdapter()`: + +```ts +return { type, execute, testEnvironment, sessionCodec, /* ... */ }; +``` + +## Optional: Skills Sync + +If your agent runtime supports skills/plugins, implement `listSkills` and `syncSkills`: + +```ts +return { + type, + execute, + testEnvironment, + async listSkills(ctx) { + return { + adapterType: ctx.adapterType, + supported: true, + mode: "ephemeral", + desiredSkills: [], + entries: [], + warnings: [], + }; + }, + async syncSkills(ctx, desiredSkills) { + // Install desired skills into the runtime + return { /* same shape as listSkills */ }; + }, +}; +``` + +## Optional: Model Detection + +If your runtime has a local config file that specifies the default model: + +```ts +async function detectModel() { + // Read ~/.my-agent/config.yaml or similar + return { + model: "anthropic/claude-sonnet-4", + provider: "anthropic", + source: "~/.my-agent/config.yaml", + candidates: ["anthropic/claude-sonnet-4", "openai/gpt-4o"], + }; +} + +return { type, execute, testEnvironment, detectModel: () => detectModel() }; +``` + +## Publishing + +```sh +npm run build +npm publish +``` + +Other Paperclip users can then install your adapter by package name from the UI or API. + +## Security + +- Treat agent output as untrusted — parse defensively, never `eval()` agent output +- Inject secrets via environment variables, not in prompts +- Configure network access controls if the runtime supports them +- Always enforce timeout and grace period — don't let agents run forever +- The UI parser module runs in a browser sandbox — it must have zero runtime imports and no side effects + +## Next Steps + +- [UI Parser Contract](/adapters/adapter-ui-parser) — add a custom run-log parser so the UI renders your adapter's output correctly +- [Creating an Adapter](/adapters/creating-an-adapter) — full walkthrough of adapter internals +- [How Agents Work](/guides/agent-developer/how-agents-work) — understand the heartbeat lifecycle your adapter serves diff --git a/docs/adapters/overview.md b/docs/adapters/overview.md index 3216b5e5c78..dfb4b21fb3a 100644 --- a/docs/adapters/overview.md +++ b/docs/adapters/overview.md @@ -22,43 +22,67 @@ When a heartbeat fires, Paperclip: | [Codex Local](/adapters/codex-local) | `codex_local` | Runs OpenAI Codex CLI locally | | [Gemini Local](/adapters/gemini-local) | `gemini_local` | Runs Gemini CLI locally (experimental — adapter package exists, not yet in stable type enum) | | OpenCode Local | `opencode_local` | Runs OpenCode CLI locally (multi-provider `provider/model`) | -| Hermes Local | `hermes_local` | Runs Hermes CLI locally | | Cursor | `cursor` | Runs Cursor in background mode | | Pi Local | `pi_local` | Runs an embedded Pi agent locally | +| Hermes Local | `hermes_local` | Runs Hermes CLI locally (`hermes-paperclip-adapter`) | | OpenClaw Gateway | `openclaw_gateway` | Connects to an OpenClaw gateway endpoint | | [Process](/adapters/process) | `process` | Executes arbitrary shell commands | | [HTTP](/adapters/http) | `http` | Sends webhooks to external agents | +### External (plugin) adapters + +These adapters ship as standalone npm packages and are installed via the plugin system: + +| Adapter | Package | Type Key | Description | +|---------|---------|----------|-------------| +| Droid Local | `@henkey/droid-paperclip-adapter` | `droid_local` | Runs Factory Droid locally | + +## External Adapters + +You can build and distribute adapters as standalone packages — no changes to Paperclip's source code required. External adapters are loaded at startup via the plugin system. + +```sh +# Install from npm via API +curl -X POST http://localhost:3102/api/adapters \ + -d '{"packageName": "my-paperclip-adapter"}' + +# Or link from a local directory +curl -X POST http://localhost:3102/api/adapters \ + -d '{"localPath": "/home/user/my-adapter"}' +``` + +See [External Adapters](/adapters/external-adapters) for the full guide. + ## Adapter Architecture -Each adapter is a package with three modules: +Each adapter is a package with modules consumed by three registries: ``` -packages/adapters// +my-adapter/ src/ index.ts # Shared metadata (type, label, models) server/ execute.ts # Core execution logic parse.ts # Output parsing test.ts # Environment diagnostics - ui/ - parse-stdout.ts # Stdout -> transcript entries for run viewer - build-config.ts # Form values -> adapterConfig JSON + ui-parser.ts # Self-contained UI transcript parser (for external adapters) cli/ format-event.ts # Terminal output for `paperclipai run --watch` ``` -Three registries consume these modules: - -| Registry | What it does | -|----------|-------------| -| **Server** | Executes agents, captures results | -| **UI** | Renders run transcripts, provides config forms | -| **CLI** | Formats terminal output for live watching | +| Registry | What it does | Source | +|----------|-------------|--------| +| **Server** | Executes agents, captures results | `createServerAdapter()` from package root | +| **UI** | Renders run transcripts, provides config forms | `ui-parser.js` (dynamic) or static import (built-in) | +| **CLI** | Formats terminal output for live watching | Static import | ## Choosing an Adapter -- **Need a coding agent?** Use `claude_local`, `codex_local`, `opencode_local`, or `hermes_local` +- **Need a coding agent?** Use `claude_local`, `codex_local`, `opencode_local`, `hermes_local`, or install `droid_local` as an external plugin - **Need to run a script or command?** Use `process` - **Need to call an external service?** Use `http` -- **Need something custom?** [Create your own adapter](/adapters/creating-an-adapter) +- **Need something custom?** [Create your own adapter](/adapters/creating-an-adapter) or [build an external adapter plugin](/adapters/external-adapters) + +## UI Parser Contract + +External adapters can ship a self-contained UI parser that tells the Paperclip web UI how to render their stdout. Without it, the UI uses a generic shell parser. See the [UI Parser Contract](/adapters/adapter-ui-parser) for details. diff --git a/docs/agents-runtime.md b/docs/agents-runtime.md index f367272395f..cafc39e9753 100644 --- a/docs/agents-runtime.md +++ b/docs/agents-runtime.md @@ -37,14 +37,18 @@ Built-in adapters: - `claude_local`: runs your local `claude` CLI - `codex_local`: runs your local `codex` CLI - `opencode_local`: runs your local `opencode` CLI -- `hermes_local`: runs your local `hermes` CLI - `cursor`: runs Cursor in background mode - `pi_local`: runs an embedded Pi agent locally +- `hermes_local`: runs your local `hermes` CLI (`hermes-paperclip-adapter`) - `openclaw_gateway`: connects to an OpenClaw gateway endpoint - `process`: generic shell command adapter - `http`: calls an external HTTP endpoint -For local CLI adapters (`claude_local`, `codex_local`, `opencode_local`, `hermes_local`), Paperclip assumes the CLI is already installed and authenticated on the host machine. +External plugin adapters (install via the adapter manager or API): + +- `droid_local`: runs your local Factory Droid CLI (`@henkey/droid-paperclip-adapter`) + +For local CLI adapters (`claude_local`, `codex_local`, `opencode_local`, `hermes_local`, `droid_local`), Paperclip assumes the CLI is already installed and authenticated on the host machine. ## 3.2 Runtime behavior @@ -173,7 +177,7 @@ Start with least privilege where possible, and avoid exposing secrets in broad r ## 10. Minimal setup checklist -1. Choose adapter (e.g. `claude_local`, `codex_local`, `opencode_local`, `hermes_local`, `cursor`, or `openclaw_gateway`). +1. Choose adapter (e.g. `claude_local`, `codex_local`, `opencode_local`, `hermes_local`, `cursor`, or `openclaw_gateway`). External plugins like `droid_local` are also available via the adapter manager. 2. Set `cwd` to the target workspace (for local adapters). 3. Optionally add a prompt template (`promptTemplate`) or use the managed instructions bundle. 4. Configure heartbeat policy (timer and/or assignment wakeups). diff --git a/docs/api/issues.md b/docs/api/issues.md index 12fb028b09f..09738f07c31 100644 --- a/docs/api/issues.md +++ b/docs/api/issues.md @@ -73,7 +73,7 @@ POST /api/issues/{issueId}/checkout Headers: X-Paperclip-Run-Id: {runId} { "agentId": "{yourAgentId}", - "expectedStatuses": ["todo", "backlog", "blocked"] + "expectedStatuses": ["todo", "backlog", "blocked", "in_review"] } ``` diff --git a/docs/deploy/docker.md b/docs/deploy/docker.md index 0effd4c7de9..9fc2dbfe882 100644 --- a/docs/deploy/docker.md +++ b/docs/deploy/docker.md @@ -8,7 +8,7 @@ Run Paperclip in Docker without installing Node or pnpm locally. ## Compose Quickstart (Recommended) ```sh -docker compose -f docker-compose.quickstart.yml up --build +docker compose -f docker/docker-compose.quickstart.yml up --build ``` Open [http://localhost:3100](http://localhost:3100). @@ -21,10 +21,12 @@ Defaults: Override with environment variables: ```sh -PAPERCLIP_PORT=3200 PAPERCLIP_DATA_DIR=./data/pc \ - docker compose -f docker-compose.quickstart.yml up --build +PAPERCLIP_PORT=3200 PAPERCLIP_DATA_DIR=../data/pc \ + docker compose -f docker/docker-compose.quickstart.yml up --build ``` +**Note:** `PAPERCLIP_DATA_DIR` is resolved relative to the compose file (`docker/`), so `../data/pc` maps to `data/pc` in the project root. + ## Manual Docker Build ```sh diff --git a/docs/docs.json b/docs/docs.json index 7faa6c86991..54dfb003eb6 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -46,6 +46,7 @@ "guides/board-operator/managing-agents", "guides/board-operator/org-structure", "guides/board-operator/managing-tasks", + "guides/board-operator/execution-workspaces-and-runtime-services", "guides/board-operator/delegation", "guides/board-operator/execution-workspaces-and-runtime-services", "guides/board-operator/delegation", @@ -99,6 +100,8 @@ "adapters/codex-local", "adapters/process", "adapters/http", + "adapters/external-adapters", + "adapters/adapter-ui-parser", "adapters/creating-an-adapter" ] } diff --git a/docs/feedback-voting.md b/docs/feedback-voting.md new file mode 100644 index 00000000000..41b17201c87 --- /dev/null +++ b/docs/feedback-voting.md @@ -0,0 +1,193 @@ +# Feedback Voting — Local Data Guide + +When you rate an agent's response with **Helpful** (thumbs up) or **Needs work** (thumbs down), Paperclip saves your vote locally alongside your running instance. This guide covers what gets stored, how to access it, and how to export it. + +## How voting works + +1. Click **Helpful** or **Needs work** on any agent comment or document revision. +2. If you click **Needs work**, an optional text prompt appears: _"What could have been better?"_ You can type a reason or dismiss it. +3. A consent dialog asks whether to keep the vote local or share it. Your choice is remembered for future votes. + +### What gets stored + +Each vote creates two local records: + +| Record | What it contains | +|--------|-----------------| +| **Vote** | Your vote (up/down), optional reason text, sharing preference, consent version, timestamp | +| **Trace bundle** | Full context snapshot: the voted-on comment/revision text, issue title, agent info, your vote, and reason — everything needed to understand the feedback in isolation | + +All data lives in your local Paperclip database. Nothing leaves your machine unless you explicitly choose to share. + +When a vote is marked for sharing, Paperclip immediately tries to upload the trace bundle through the Telemetry Backend. The upload is compressed in transit so full trace bundles stay under gateway size limits. If that immediate push fails, the trace is left in a retriable failed state for later flush attempts. The app server never uploads raw feedback trace bundles directly to object storage. + +## Viewing your votes + +### Quick report (terminal) + +```bash +pnpm paperclipai feedback report +``` + +Shows a color-coded summary: vote counts, per-trace details with reasons, and export statuses. + +```bash +# Installed CLI +paperclipai feedback report + +# Point to a different server or company +pnpm paperclipai feedback report --api-base http://127.0.0.1:3000 --company-id + +# Include raw payload dumps in the report +pnpm paperclipai feedback report --payloads +``` + +### API endpoints + +All endpoints require board-user access (automatic in local dev). + +**List votes for an issue:** +```bash +curl http://127.0.0.1:3102/api/issues//feedback-votes +``` + +**List trace bundles for an issue (with full payloads):** +```bash +curl 'http://127.0.0.1:3102/api/issues//feedback-traces?includePayload=true' +``` + +**List all traces company-wide:** +```bash +curl 'http://127.0.0.1:3102/api/companies//feedback-traces?includePayload=true' +``` + +**Get a single trace envelope record:** +```bash +curl http://127.0.0.1:3102/api/feedback-traces/ +``` + +**Get the full export bundle for a trace:** +```bash +curl http://127.0.0.1:3102/api/feedback-traces//bundle +``` + +#### Filtering + +The trace endpoints accept query parameters: + +| Parameter | Values | Description | +|-----------|--------|-------------| +| `vote` | `up`, `down` | Filter by vote direction | +| `status` | `local_only`, `pending`, `sent`, `failed` | Filter by export status | +| `targetType` | `issue_comment`, `issue_document_revision` | Filter by what was voted on | +| `sharedOnly` | `true` | Only show votes the user chose to share | +| `includePayload` | `true` | Include the full context snapshot | +| `from` / `to` | ISO date | Date range filter | + +## Exporting your data + +### Export to files + zip + +```bash +pnpm paperclipai feedback export +``` + +Creates a timestamped directory with: + +``` +feedback-export-20260331T120000Z/ + index.json # manifest with summary stats + votes/ + PAP-123-a1b2c3d4.json # vote metadata (one per vote) + traces/ + PAP-123-e5f6g7h8.json # Paperclip feedback envelope (one per trace) + full-traces/ + PAP-123-e5f6g7h8/ + bundle.json # full export manifest for the trace + ...raw adapter files # codex / claude / opencode session artifacts when available +feedback-export-20260331T120000Z.zip +``` + +Exports are full by default. `traces/` keeps the Paperclip envelope, while `full-traces/` contains the richer per-trace bundle plus any recoverable adapter-native files. + +```bash +# Custom server and output directory +pnpm paperclipai feedback export --api-base http://127.0.0.1:3000 --company-id --out ./my-export +``` + +### Reading an exported trace + +Open any file in `traces/` to see: + +```json +{ + "id": "trace-uuid", + "vote": "down", + "issueIdentifier": "PAP-123", + "issueTitle": "Fix login timeout", + "targetType": "issue_comment", + "targetSummary": { + "label": "Comment", + "excerpt": "The first 80 chars of the comment that was voted on..." + }, + "payloadSnapshot": { + "vote": { + "value": "down", + "reason": "Did not address the root cause" + }, + "target": { + "body": "Full text of the agent comment..." + }, + "issue": { + "identifier": "PAP-123", + "title": "Fix login timeout" + } + } +} +``` + +Open `full-traces/-/bundle.json` to see the expanded export metadata, including capture notes, adapter type, integrity metadata, and the inventory of raw files written alongside it. + +Each entry in `bundle.json.files[]` includes the actual captured file payload under `contents`, not just a pathname. For text artifacts this is stored as UTF-8 text; binary artifacts use base64 plus an `encoding` marker. + +Built-in local adapters now export their native session artifacts more directly: + +- `codex_local`: `adapter/codex/session.jsonl` +- `claude_local`: `adapter/claude/session.jsonl`, plus any `adapter/claude/session/...` sidecar files and `adapter/claude/debug.txt` when present +- `opencode_local`: `adapter/opencode/session.json`, `adapter/opencode/messages/*.json`, and `adapter/opencode/parts//*.json`, with optional `project.json`, `todo.json`, and `session-diff.json` + +## Sharing preferences + +The first time you vote, a consent dialog asks: + +- **Keep local** — vote is stored locally only (`sharedWithLabs: false`) +- **Share this vote** — vote is marked for sharing (`sharedWithLabs: true`) + +Your preference is saved per-company. You can change it any time via the feedback settings. Votes marked "keep local" are never queued for export. + +## Data lifecycle + +| Status | Meaning | +|--------|---------| +| `local_only` | Vote stored locally, not marked for sharing | +| `pending` | Marked for sharing, saved locally, and waiting for the immediate upload attempt | +| `sent` | Successfully transmitted | +| `failed` | Transmission attempted but failed (for example the backend is unreachable or not configured); later flushes retry once a backend is available | + +Your local database always retains the full vote and trace data regardless of sharing status. + +## Remote sync + +Votes you choose to share are sent to the Telemetry Backend immediately from the vote request. The server also keeps a background flush worker so failed traces can retry later. The Telemetry Backend validates the request, then persists the bundle into its configured object storage. + +- App server responsibility: build the bundle, POST it to Telemetry Backend, update trace status +- Telemetry Backend responsibility: authenticate the request, validate payload shape, compress/store the bundle, return the final object key +- Retry behavior: failed uploads move to `failed` with an error message in `failureReason`, and the worker retries them on later ticks +- Default endpoint: when no feedback export backend URL is configured, Paperclip falls back to `https://telemetry.paperclip.ing` +- Important nuance: the uploaded object is a snapshot of the full bundle at vote time. If you fetch a local bundle later and the underlying adapter session file has continued to grow, the local regenerated bundle may be larger than the already-uploaded snapshot for that same trace. + +Exported objects use a deterministic key pattern so they are easy to inspect: + +```text +feedback-traces//YYYY/MM/DD/.json +``` diff --git a/docs/guides/agent-developer/heartbeat-protocol.md b/docs/guides/agent-developer/heartbeat-protocol.md index 9b6cdb3172f..7c24715eb56 100644 --- a/docs/guides/agent-developer/heartbeat-protocol.md +++ b/docs/guides/agent-developer/heartbeat-protocol.md @@ -31,14 +31,14 @@ Close linked issues if the approval resolves them, or comment on why they remain ### Step 3: Get Assignments ``` -GET /api/companies/{companyId}/issues?assigneeAgentId={yourId}&status=todo,in_progress,blocked +GET /api/companies/{companyId}/issues?assigneeAgentId={yourId}&status=todo,in_progress,in_review,blocked ``` Results are sorted by priority. This is your inbox. ### Step 4: Pick Work -- Work on `in_progress` tasks first, then `todo` +- Work on `in_progress` tasks first, then `in_review` when you were woken by a comment on it, then `todo` - Skip `blocked` unless you can unblock it - If `PAPERCLIP_TASK_ID` is set and assigned to you, prioritize it - If woken by a comment mention, read that comment thread first @@ -50,7 +50,7 @@ Before doing any work, you must checkout the task: ``` POST /api/issues/{issueId}/checkout Headers: X-Paperclip-Run-Id: {runId} -{ "agentId": "{yourId}", "expectedStatuses": ["todo", "backlog", "blocked"] } +{ "agentId": "{yourId}", "expectedStatuses": ["todo", "backlog", "blocked", "in_review"] } ``` If already checked out by you, this succeeds. If another agent owns it: `409 Conflict` — stop and pick a different task. **Never retry a 409.** diff --git a/docs/guides/agent-developer/task-workflow.md b/docs/guides/agent-developer/task-workflow.md index 3daeec0bc02..3b7e1403c0d 100644 --- a/docs/guides/agent-developer/task-workflow.md +++ b/docs/guides/agent-developer/task-workflow.md @@ -11,7 +11,7 @@ Before doing any work on a task, checkout is required: ``` POST /api/issues/{issueId}/checkout -{ "agentId": "{yourId}", "expectedStatuses": ["todo", "backlog", "blocked"] } +{ "agentId": "{yourId}", "expectedStatuses": ["todo", "backlog", "blocked", "in_review"] } ``` This is an atomic operation. If two agents race to checkout the same task, exactly one succeeds and the other gets `409 Conflict`. @@ -82,8 +82,8 @@ This releases your ownership. Leave a comment explaining why. ``` GET /api/agents/me -GET /api/companies/company-1/issues?assigneeAgentId=agent-42&status=todo,in_progress,blocked -# -> [{ id: "issue-101", status: "in_progress" }, { id: "issue-99", status: "todo" }] +GET /api/companies/company-1/issues?assigneeAgentId=agent-42&status=todo,in_progress,in_review,blocked +# -> [{ id: "issue-101", status: "in_progress" }, { id: "issue-100", status: "in_review" }, { id: "issue-99", status: "todo" }] # Continue in_progress work GET /api/issues/issue-101 @@ -96,7 +96,7 @@ PATCH /api/issues/issue-101 # Pick up next task POST /api/issues/issue-99/checkout -{ "agentId": "agent-42", "expectedStatuses": ["todo"] } +{ "agentId": "agent-42", "expectedStatuses": ["todo", "backlog", "blocked", "in_review"] } # Partial progress PATCH /api/issues/issue-99 diff --git a/docs/guides/execution-policy.md b/docs/guides/execution-policy.md new file mode 100644 index 00000000000..f12af1cb13c --- /dev/null +++ b/docs/guides/execution-policy.md @@ -0,0 +1,269 @@ +# Execution Policy: Review & Approval Workflows + +Paperclip's execution policy system ensures tasks are completed with the right level of oversight. Instead of relying on agents to remember to hand off work for review, the **runtime enforces** review and approval stages automatically. + +## Overview + +An execution policy is an optional structured object on any issue that defines what must happen after the executor finishes their work. It supports three layers of enforcement: + +| Layer | Purpose | Scope | +|---|---|---| +| **Comment required** | Every agent run must post a comment back to the issue | Runtime invariant (always on) | +| **Review stage** | A reviewer checks quality/correctness and can request changes | Per-issue, optional | +| **Approval stage** | A manager/stakeholder gives final sign-off | Per-issue, optional | + +These layers compose. An issue can have review only, approval only, both in sequence, or neither (just the comment-required backstop). + +## Data Model + +### Execution Policy (issue field: `executionPolicy`) + +```ts +interface IssueExecutionPolicy { + mode: "normal" | "auto"; + commentRequired: boolean; // always true, enforced by runtime + stages: IssueExecutionStage[]; // ordered list of review/approval stages +} + +interface IssueExecutionStage { + id: string; // auto-generated UUID + type: "review" | "approval"; // stage kind + approvalsNeeded: 1; // multi-approval is not supported yet + participants: IssueExecutionStageParticipant[]; +} + +interface IssueExecutionStageParticipant { + id: string; + type: "agent" | "user"; + agentId?: string | null; // set when type is "agent" + userId?: string | null; // set when type is "user" +} +``` + +Participants can be either agents or board users. Each stage can have multiple participants; the runtime selects the first eligible participant, preferring any explicitly requested assignee while excluding the original executor. + +### Execution State (issue field: `executionState`) + +Tracks where the issue currently sits in its policy workflow: + +```ts +interface IssueExecutionState { + status: "idle" | "pending" | "changes_requested" | "completed"; + currentStageId: string | null; + currentStageIndex: number | null; + currentStageType: "review" | "approval" | null; + currentParticipant: IssueExecutionStagePrincipal | null; + returnAssignee: IssueExecutionStagePrincipal | null; + completedStageIds: string[]; + lastDecisionId: string | null; + lastDecisionOutcome: "approved" | "changes_requested" | null; +} +``` + +### Execution Decisions (table: `issue_execution_decisions`) + +An audit trail of every review/approval action: + +```ts +interface IssueExecutionDecision { + id: string; + companyId: string; + issueId: string; + stageId: string; + stageType: "review" | "approval"; + actorAgentId: string | null; + actorUserId: string | null; + outcome: "approved" | "changes_requested"; + body: string; // required comment explaining the decision + createdByRunId: string | null; + createdAt: Date; +} +``` + +## Workflow + +### Happy Path: Review + Approval + +``` +┌──────────┐ executor ┌───────────┐ reviewer ┌───────────┐ approver ┌──────┐ +│ todo │───completes───▶│ in_review │───approves───▶│ in_review │───approves───▶│ done │ +│ (Coder) │ work │ (QA) │ │ (CTO) │ │ │ +└──────────┘ └───────────┘ └───────────┘ └──────┘ +``` + +1. **Issue created** with `executionPolicy` specifying a review stage (e.g., QA) and an approval stage (e.g., CTO). +2. **Executor works** on the issue in `in_progress` status. +3. **Executor transitions to `done`** — the runtime intercepts this: + - Status changes to `in_review` (not `done`) + - Issue is reassigned to the first reviewer + - `executionState` enters `pending` on the review stage +4. **Reviewer reviews** and transitions to `done` with a comment: + - A decision record is created: `{ outcome: "approved" }` + - Issue stays `in_review`, reassigned to the approver + - `executionState` advances to the approval stage +5. **Approver approves** and transitions to `done` with a comment: + - A decision record is created: `{ outcome: "approved" }` + - `executionState.status` becomes `completed` + - Issue reaches actual `done` status + +### Changes Requested Flow + +``` +┌───────────┐ reviewer requests ┌─────────────┐ executor ┌───────────┐ +│ in_review │───changes────────────▶│ in_progress │───resubmits──▶│ in_review │ +│ (QA) │ │ (Coder) │ │ (QA) │ +└───────────┘ └──────────────┘ └───────────┘ +``` + +1. **Reviewer requests changes** by transitioning to any status other than `done` (typically `in_progress`), with a comment explaining what needs to change. +2. Runtime automatically: + - Sets status to `in_progress` + - Reassigns to the original executor (stored in `returnAssignee`) + - Sets `executionState.status` to `changes_requested` +3. **Executor makes changes** and transitions to `done` again. +4. Runtime routes back to the **same review stage** (not the beginning), with the same reviewer. +5. This loop continues until the reviewer approves. + +### Policy Variants + +**Review only** (no approval stage): +```json +{ + "stages": [ + { "type": "review", "participants": [{ "type": "agent", "agentId": "qa-agent-id" }] } + ] +} +``` +Executor finishes → reviewer approves → done. + +**Approval only** (no review stage): +```json +{ + "stages": [ + { "type": "approval", "participants": [{ "type": "user", "userId": "manager-user-id" }] } + ] +} +``` +Executor finishes → approver signs off → done. + +**Multiple reviewers/approvers:** +Each stage supports multiple participants. The runtime selects one to act, excluding the original executor to prevent self-review. + +## Comment Required Backstop + +Independent of review stages, every issue-bound agent run must leave a comment. This is enforced at the runtime level: + +1. **Run completes** — runtime checks if the agent posted a comment for this run. +2. **If no comment**: `issueCommentStatus` is set to `retry_queued`, and the agent is woken once more with reason `missing_issue_comment`. +3. **If still no comment after retry**: `issueCommentStatus` is set to `retry_exhausted`. No further retries. The failure is recorded. +4. **If comment posted**: `issueCommentStatus` is set to `satisfied` and linked to the comment ID. + +This prevents silent completions where an agent finishes work but leaves no trace of what happened. + +### Run-level tracking fields + +| Field | Description | +|---|---| +| `issueCommentStatus` | `satisfied`, `retry_queued`, or `retry_exhausted` | +| `issueCommentSatisfiedByCommentId` | Links to the comment that fulfilled the requirement | +| `issueCommentRetryQueuedAt` | Timestamp when the retry wake was scheduled | + +## Access Control + +- Only the **active reviewer/approver** (the `currentParticipant` in execution state) can advance or reject the current stage. +- Non-participants who attempt to transition the issue receive a `422 Unprocessable Entity` error. +- Both approvals and change requests **require a comment** — empty or whitespace-only comments are rejected. + +## API Usage + +### Setting an execution policy on issue creation + +```bash +POST /api/companies/{companyId}/issues +{ + "title": "Implement feature X", + "assigneeAgentId": "coder-agent-id", + "executionPolicy": { + "mode": "normal", + "commentRequired": true, + "stages": [ + { + "type": "review", + "participants": [ + { "type": "agent", "agentId": "qa-agent-id" } + ] + }, + { + "type": "approval", + "participants": [ + { "type": "user", "userId": "cto-user-id" } + ] + } + ] + } +} +``` + +Stage IDs and participant IDs are auto-generated if omitted. Duplicate participants within a stage are automatically deduplicated. Stages with no valid participants are removed. If no valid stages remain, the policy is set to `null`. + +### Updating execution policy on an existing issue + +```bash +PATCH /api/issues/{issueId} +{ + "executionPolicy": { ... } +} +``` + +If the policy is removed (`null`) while a review is in progress, the execution state is cleared and the issue is returned to the original executor. + +### Advancing a stage (reviewer/approver approves) + +The active reviewer or approver transitions the issue to `done` with a comment: + +```bash +PATCH /api/issues/{issueId} +{ + "status": "done", + "comment": "Reviewed — implementation looks correct, tests pass." +} +``` + +The runtime determines whether this completes the workflow or advances to the next stage. + +### Requesting changes + +The active reviewer transitions to any non-`done` status with a comment: + +```bash +PATCH /api/issues/{issueId} +{ + "status": "in_progress", + "comment": "Button alignment is off on mobile. Please fix the flex container." +} +``` + +The runtime reassigns to the original executor automatically. + +## UI + +### New Issue Dialog + +When creating a new issue, **Reviewer** and **Approver** buttons appear alongside the assignee selector. Clicking either opens a participant picker with: +- "No reviewer" / "No approver" (to clear) +- "Me" (current user) +- Full list of agents and board users + +Selections build the `executionPolicy.stages` array automatically. + +### Issue Properties Pane + +For existing issues, the properties panel shows editable **Reviewer** and **Approver** fields. Multiple participants can be added per stage. Changes persist to the issue's `executionPolicy` via the API. + +## Design Principles + +1. **Runtime-enforced, not prompt-dependent.** Agents don't need to remember to hand off work. The runtime intercepts status transitions and routes accordingly. +2. **Iterative, not terminal.** Review is a loop (request changes → revise → re-review), not a one-shot gate. The system returns to the same stage on re-submission. +3. **Flexible roles.** Participants can be agents or users. Not every organization has "QA" — the reviewer/approver pattern is generic enough for peer review, manager sign-off, compliance checks, or any multi-party workflow. +4. **Auditable.** Every decision is recorded with actor, outcome, comment, and run ID. The full review history is queryable per issue. +5. **Single execution invariant preserved.** Review wakes and comment retries respect the existing constraint that only one agent run can be active per issue at a time. diff --git a/package.json b/package.json index 3c083e44b91..3fcc8d994db 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,9 @@ "pnpm": { "patchedDependencies": { "embedded-postgres@18.1.0-beta.16": "patches/embedded-postgres@18.1.0-beta.16.patch" + }, + "overrides": { + "rollup": ">=4.59.0" } } } diff --git a/packages/adapter-utils/src/index.ts b/packages/adapter-utils/src/index.ts index 943db253cfa..6770ae51c86 100644 --- a/packages/adapter-utils/src/index.ts +++ b/packages/adapter-utils/src/index.ts @@ -22,6 +22,9 @@ export type { AdapterModel, HireApprovedPayload, HireApprovedHookResult, + ConfigFieldOption, + ConfigFieldSchema, + AdapterConfigSchema, ServerAdapterModule, QuotaWindow, ProviderQuotaResult, diff --git a/packages/adapter-utils/src/log-redaction.ts b/packages/adapter-utils/src/log-redaction.ts index 6c5554e19b3..96cfba6dd0d 100644 --- a/packages/adapter-utils/src/log-redaction.ts +++ b/packages/adapter-utils/src/log-redaction.ts @@ -68,6 +68,7 @@ export function redactTranscriptEntryPaths(entry: TranscriptEntry, opts?: HomePa case "stderr": case "system": case "stdout": + case "diff": return { ...entry, text: redactHomePathUserSegments(entry.text, opts) }; case "tool_call": return { diff --git a/packages/adapter-utils/src/server-utils.test.ts b/packages/adapter-utils/src/server-utils.test.ts new file mode 100644 index 00000000000..62e395b0eb2 --- /dev/null +++ b/packages/adapter-utils/src/server-utils.test.ts @@ -0,0 +1,38 @@ +import { randomUUID } from "node:crypto"; +import { describe, expect, it } from "vitest"; +import { runChildProcess } from "./server-utils.js"; + +describe("runChildProcess", () => { + it("waits for onSpawn before sending stdin to the child", async () => { + const spawnDelayMs = 150; + const startedAt = Date.now(); + let onSpawnCompletedAt = 0; + + const result = await runChildProcess( + randomUUID(), + process.execPath, + [ + "-e", + "let data='';process.stdin.setEncoding('utf8');process.stdin.on('data',chunk=>data+=chunk);process.stdin.on('end',()=>process.stdout.write(data));", + ], + { + cwd: process.cwd(), + env: {}, + stdin: "hello from stdin", + timeoutSec: 5, + graceSec: 1, + onLog: async () => {}, + onSpawn: async () => { + await new Promise((resolve) => setTimeout(resolve, spawnDelayMs)); + onSpawnCompletedAt = Date.now(); + }, + }, + ); + const finishedAt = Date.now(); + + expect(result.exitCode).toBe(0); + expect(result.stdout).toBe("hello from stdin"); + expect(onSpawnCompletedAt).toBeGreaterThanOrEqual(startedAt + spawnDelayMs); + expect(finishedAt - startedAt).toBeGreaterThanOrEqual(spawnDelayMs); + }); +}); diff --git a/packages/adapter-utils/src/server-utils.ts b/packages/adapter-utils/src/server-utils.ts index 4a5affdff53..83dbe06f1ea 100644 --- a/packages/adapter-utils/src/server-utils.ts +++ b/packages/adapter-utils/src/server-utils.ts @@ -193,6 +193,276 @@ export function joinPromptSections( .join(separator); } +type PaperclipWakeIssue = { + id: string | null; + identifier: string | null; + title: string | null; + status: string | null; + priority: string | null; +}; + +type PaperclipWakeExecutionPrincipal = { + type: "agent" | "user" | null; + agentId: string | null; + userId: string | null; +}; + +type PaperclipWakeExecutionStage = { + wakeRole: "reviewer" | "approver" | "executor" | null; + stageId: string | null; + stageType: string | null; + currentParticipant: PaperclipWakeExecutionPrincipal | null; + returnAssignee: PaperclipWakeExecutionPrincipal | null; + lastDecisionOutcome: string | null; + allowedActions: string[]; +}; + +type PaperclipWakeComment = { + id: string | null; + issueId: string | null; + body: string; + bodyTruncated: boolean; + createdAt: string | null; + authorType: string | null; + authorId: string | null; +}; + +type PaperclipWakePayload = { + reason: string | null; + issue: PaperclipWakeIssue | null; + executionStage: PaperclipWakeExecutionStage | null; + commentIds: string[]; + latestCommentId: string | null; + comments: PaperclipWakeComment[]; + requestedCount: number; + includedCount: number; + missingCount: number; + truncated: boolean; + fallbackFetchNeeded: boolean; +}; + +function normalizePaperclipWakeIssue(value: unknown): PaperclipWakeIssue | null { + const issue = parseObject(value); + const id = asString(issue.id, "").trim() || null; + const identifier = asString(issue.identifier, "").trim() || null; + const title = asString(issue.title, "").trim() || null; + const status = asString(issue.status, "").trim() || null; + const priority = asString(issue.priority, "").trim() || null; + if (!id && !identifier && !title) return null; + return { + id, + identifier, + title, + status, + priority, + }; +} + +function normalizePaperclipWakeComment(value: unknown): PaperclipWakeComment | null { + const comment = parseObject(value); + const author = parseObject(comment.author); + const body = asString(comment.body, ""); + if (!body.trim()) return null; + return { + id: asString(comment.id, "").trim() || null, + issueId: asString(comment.issueId, "").trim() || null, + body, + bodyTruncated: asBoolean(comment.bodyTruncated, false), + createdAt: asString(comment.createdAt, "").trim() || null, + authorType: asString(author.type, "").trim() || null, + authorId: asString(author.id, "").trim() || null, + }; +} + +function normalizePaperclipWakeExecutionPrincipal(value: unknown): PaperclipWakeExecutionPrincipal | null { + const principal = parseObject(value); + const typeRaw = asString(principal.type, "").trim().toLowerCase(); + if (typeRaw !== "agent" && typeRaw !== "user") return null; + return { + type: typeRaw, + agentId: asString(principal.agentId, "").trim() || null, + userId: asString(principal.userId, "").trim() || null, + }; +} + +function normalizePaperclipWakeExecutionStage(value: unknown): PaperclipWakeExecutionStage | null { + const stage = parseObject(value); + const wakeRoleRaw = asString(stage.wakeRole, "").trim().toLowerCase(); + const wakeRole = + wakeRoleRaw === "reviewer" || wakeRoleRaw === "approver" || wakeRoleRaw === "executor" + ? wakeRoleRaw + : null; + const allowedActions = Array.isArray(stage.allowedActions) + ? stage.allowedActions + .filter((entry): entry is string => typeof entry === "string" && entry.trim().length > 0) + .map((entry) => entry.trim()) + : []; + const currentParticipant = normalizePaperclipWakeExecutionPrincipal(stage.currentParticipant); + const returnAssignee = normalizePaperclipWakeExecutionPrincipal(stage.returnAssignee); + const stageId = asString(stage.stageId, "").trim() || null; + const stageType = asString(stage.stageType, "").trim() || null; + const lastDecisionOutcome = asString(stage.lastDecisionOutcome, "").trim() || null; + + if (!wakeRole && !stageId && !stageType && !currentParticipant && !returnAssignee && !lastDecisionOutcome && allowedActions.length === 0) { + return null; + } + + return { + wakeRole, + stageId, + stageType, + currentParticipant, + returnAssignee, + lastDecisionOutcome, + allowedActions, + }; +} + +export function normalizePaperclipWakePayload(value: unknown): PaperclipWakePayload | null { + const payload = parseObject(value); + const comments = Array.isArray(payload.comments) + ? payload.comments + .map((entry) => normalizePaperclipWakeComment(entry)) + .filter((entry): entry is PaperclipWakeComment => Boolean(entry)) + : []; + const commentWindow = parseObject(payload.commentWindow); + const commentIds = Array.isArray(payload.commentIds) + ? payload.commentIds + .filter((entry): entry is string => typeof entry === "string" && entry.trim().length > 0) + .map((entry) => entry.trim()) + : []; + const executionStage = normalizePaperclipWakeExecutionStage(payload.executionStage); + + if (comments.length === 0 && commentIds.length === 0 && !executionStage && !normalizePaperclipWakeIssue(payload.issue)) { + return null; + } + + return { + reason: asString(payload.reason, "").trim() || null, + issue: normalizePaperclipWakeIssue(payload.issue), + executionStage, + commentIds, + latestCommentId: asString(payload.latestCommentId, "").trim() || null, + comments, + requestedCount: asNumber(commentWindow.requestedCount, comments.length || commentIds.length), + includedCount: asNumber(commentWindow.includedCount, comments.length), + missingCount: asNumber(commentWindow.missingCount, 0), + truncated: asBoolean(payload.truncated, false), + fallbackFetchNeeded: asBoolean(payload.fallbackFetchNeeded, false), + }; +} + +export function stringifyPaperclipWakePayload(value: unknown): string | null { + const normalized = normalizePaperclipWakePayload(value); + if (!normalized) return null; + return JSON.stringify(normalized); +} + +export function renderPaperclipWakePrompt( + value: unknown, + options: { resumedSession?: boolean } = {}, +): string { + const normalized = normalizePaperclipWakePayload(value); + if (!normalized) return ""; + const resumedSession = options.resumedSession === true; + const executionStage = normalized.executionStage; + const principalLabel = (principal: PaperclipWakeExecutionPrincipal | null) => { + if (!principal || !principal.type) return "unknown"; + if (principal.type === "agent") return principal.agentId ? `agent ${principal.agentId}` : "agent"; + return principal.userId ? `user ${principal.userId}` : "user"; + }; + + const lines = resumedSession + ? [ + "## Paperclip Resume Delta", + "", + "You are resuming an existing Paperclip session.", + "This heartbeat is scoped to the issue below. Do not switch to another issue until you have handled this wake.", + "Focus on the new wake delta below and continue the current task without restating the full heartbeat boilerplate.", + "Fetch the API thread only when `fallbackFetchNeeded` is true or you need broader history than this batch.", + "", + `- reason: ${normalized.reason ?? "unknown"}`, + `- issue: ${normalized.issue?.identifier ?? normalized.issue?.id ?? "unknown"}${normalized.issue?.title ? ` ${normalized.issue.title}` : ""}`, + `- pending comments: ${normalized.includedCount}/${normalized.requestedCount}`, + `- latest comment id: ${normalized.latestCommentId ?? "unknown"}`, + `- fallback fetch needed: ${normalized.fallbackFetchNeeded ? "yes" : "no"}`, + ] + : [ + "## Paperclip Wake Payload", + "", + "Treat this wake payload as the highest-priority change for the current heartbeat.", + "This heartbeat is scoped to the issue below. Do not switch to another issue until you have handled this wake.", + "Before generic repo exploration or boilerplate heartbeat updates, acknowledge the latest comment and explain how it changes your next action.", + "Use this inline wake data first before refetching the issue thread.", + "Only fetch the API thread when `fallbackFetchNeeded` is true or you need broader history than this batch.", + "", + `- reason: ${normalized.reason ?? "unknown"}`, + `- issue: ${normalized.issue?.identifier ?? normalized.issue?.id ?? "unknown"}${normalized.issue?.title ? ` ${normalized.issue.title}` : ""}`, + `- pending comments: ${normalized.includedCount}/${normalized.requestedCount}`, + `- latest comment id: ${normalized.latestCommentId ?? "unknown"}`, + `- fallback fetch needed: ${normalized.fallbackFetchNeeded ? "yes" : "no"}`, + ]; + + if (normalized.issue?.status) { + lines.push(`- issue status: ${normalized.issue.status}`); + } + if (normalized.issue?.priority) { + lines.push(`- issue priority: ${normalized.issue.priority}`); + } + if (normalized.missingCount > 0) { + lines.push(`- omitted comments: ${normalized.missingCount}`); + } + + if (executionStage) { + lines.push( + `- execution wake role: ${executionStage.wakeRole ?? "unknown"}`, + `- execution stage: ${executionStage.stageType ?? "unknown"}`, + `- execution participant: ${principalLabel(executionStage.currentParticipant)}`, + `- execution return assignee: ${principalLabel(executionStage.returnAssignee)}`, + `- last decision outcome: ${executionStage.lastDecisionOutcome ?? "none"}`, + ); + if (executionStage.allowedActions.length > 0) { + lines.push(`- allowed actions: ${executionStage.allowedActions.join(", ")}`); + } + lines.push(""); + if (executionStage.wakeRole === "reviewer" || executionStage.wakeRole === "approver") { + lines.push( + `You are waking as the active ${executionStage.wakeRole} for this issue.`, + "Do not execute the task itself or continue executor work.", + "Review the issue and choose one of the allowed actions above.", + "If you request changes, the workflow routes back to the stored return assignee.", + "", + ); + } else if (executionStage.wakeRole === "executor") { + lines.push( + "You are waking because changes were requested in the execution workflow.", + "Address the requested changes on this issue and resubmit when the work is ready.", + "", + ); + } + } + + if (normalized.comments.length > 0) { + lines.push("New comments in order:"); + } + + for (const [index, comment] of normalized.comments.entries()) { + const authorLabel = comment.authorId + ? `${comment.authorType ?? "unknown"} ${comment.authorId}` + : comment.authorType ?? "unknown"; + lines.push( + `${index + 1}. comment ${comment.id ?? "unknown"} at ${comment.createdAt ?? "unknown"} by ${authorLabel}`, + comment.body, + ); + if (comment.bodyTruncated) { + lines.push("[comment body truncated]"); + } + lines.push(""); + } + + return lines.join("\n").trim(); +} + export function redactEnvForLogs(env: Record): Record { const redacted: Record = {}; for (const [key, value] of Object.entries(env)) { @@ -306,6 +576,11 @@ function quoteForCmd(arg: string) { return /[\s"&<>|^()]/.test(escaped) ? `"${escaped}"` : escaped; } +function resolveWindowsCmdShell(env: NodeJS.ProcessEnv): string { + const fallbackRoot = env.SystemRoot || process.env.SystemRoot || "C:\\Windows"; + return path.join(fallbackRoot, "System32", "cmd.exe"); +} + async function resolveSpawnTarget( command: string, args: string[], @@ -320,7 +595,9 @@ async function resolveSpawnTarget( } if (/\.(cmd|bat)$/i.test(executable)) { - const shell = env.ComSpec || process.env.ComSpec || "cmd.exe"; + // Always use cmd.exe for .cmd/.bat wrappers. Some environments override + // ComSpec to PowerShell, which breaks cmd-specific flags like /d /s /c. + const shell = resolveWindowsCmdShell(env); const commandLine = [quoteForCmd(executable), ...args.map(quoteForCmd)].join(" "); return { command: shell, @@ -792,16 +1069,12 @@ export async function runChildProcess( }) as ChildProcessWithEvents; const startedAt = new Date().toISOString(); - if (opts.stdin != null && child.stdin) { - child.stdin.write(opts.stdin); - child.stdin.end(); - } - - if (typeof child.pid === "number" && child.pid > 0 && opts.onSpawn) { - void opts.onSpawn({ pid: child.pid, startedAt }).catch((err) => { - onLogError(err, runId, "failed to record child process metadata"); - }); - } + const spawnPersistPromise = + typeof child.pid === "number" && child.pid > 0 && opts.onSpawn + ? opts.onSpawn({ pid: child.pid, startedAt }).catch((err) => { + onLogError(err, runId, "failed to record child process metadata"); + }) + : Promise.resolve(); runningProcesses.set(runId, { child, graceSec: opts.graceSec }); @@ -839,6 +1112,15 @@ export async function runChildProcess( .catch((err) => onLogError(err, runId, "failed to append stderr log chunk")); }); + const stdin = child.stdin; + if (opts.stdin != null && stdin) { + void spawnPersistPromise.finally(() => { + if (child.killed || stdin.destroyed) return; + stdin.write(opts.stdin as string); + stdin.end(); + }); + } + child.on("error", (err: Error) => { if (timeout) clearTimeout(timeout); runningProcesses.delete(runId); diff --git a/packages/adapter-utils/src/session-compaction.ts b/packages/adapter-utils/src/session-compaction.ts index 308b54a3fd7..90fe544b5db 100644 --- a/packages/adapter-utils/src/session-compaction.ts +++ b/packages/adapter-utils/src/session-compaction.ts @@ -41,6 +41,7 @@ export const LEGACY_SESSIONED_ADAPTER_TYPES = new Set([ "codex_local", "cursor", "gemini_local", + "hermes_local", "opencode_local", "pi_local", ]); @@ -76,6 +77,11 @@ export const ADAPTER_SESSION_MANAGEMENT: Record { diff --git a/packages/adapter-utils/src/types.ts b/packages/adapter-utils/src/types.ts index 9337fad0b8c..429143d53d6 100644 --- a/packages/adapter-utils/src/types.ts +++ b/packages/adapter-utils/src/types.ts @@ -261,6 +261,34 @@ export interface ProviderQuotaResult { windows: QuotaWindow[]; } +// --------------------------------------------------------------------------- +// Adapter config schema — declarative UI config for external adapters +// --------------------------------------------------------------------------- + +export interface ConfigFieldOption { + label: string; + value: string; + /** Optional group key for categorizing options (e.g. provider name) */ + group?: string; +} + +export interface ConfigFieldSchema { + key: string; + label: string; + type: "text" | "select" | "toggle" | "number" | "textarea" | "combobox"; + options?: ConfigFieldOption[]; + default?: unknown; + hint?: string; + required?: boolean; + group?: string; + /** Optional metadata — not rendered, but available to custom UI logic */ + meta?: Record; +} + +export interface AdapterConfigSchema { + fields: ConfigFieldSchema[]; +} + export interface ServerAdapterModule { type: string; execute(ctx: AdapterExecutionContext): Promise; @@ -292,7 +320,14 @@ export interface ServerAdapterModule { * Returns the detected model/provider and the config source, or null if * the adapter does not support detection or no config is found. */ - detectModel?: () => Promise<{ model: string; provider: string; source: string } | null>; + detectModel?: () => Promise<{ model: string; provider: string; source: string; candidates?: string[] } | null>; + /** + * Optional: return a declarative config schema so the UI can render + * adapter-specific form fields without shipping React components. + * Dynamic options (e.g. scanning a profiles directory) should be + * resolved inside this method — the caller receives a fully hydrated schema. + */ + getConfigSchema?: () => Promise | AdapterConfigSchema; } // --------------------------------------------------------------------------- @@ -309,7 +344,8 @@ export type TranscriptEntry = | { kind: "result"; ts: string; text: string; inputTokens: number; outputTokens: number; cachedTokens: number; costUsd: number; subtype: string; isError: boolean; errors: string[] } | { kind: "stderr"; ts: string; text: string } | { kind: "system"; ts: string; text: string } - | { kind: "stdout"; ts: string; text: string }; + | { kind: "stdout"; ts: string; text: string } + | { kind: "diff"; ts: string; changeType: "add" | "remove" | "context" | "hunk" | "file_header" | "truncation"; text: string }; export type StdoutLineParser = (line: string, ts: string) => TranscriptEntry[]; @@ -353,4 +389,6 @@ export interface CreateConfigValues { maxTurnsPerRun: number; heartbeatEnabled: boolean; intervalSec: number; + /** Arbitrary key-value pairs populated by schema-driven config fields. */ + adapterSchemaValues?: Record; } diff --git a/packages/adapters/claude-local/src/index.ts b/packages/adapters/claude-local/src/index.ts index 41c0693f8d4..b2f85732fb3 100644 --- a/packages/adapters/claude-local/src/index.ts +++ b/packages/adapters/claude-local/src/index.ts @@ -21,7 +21,7 @@ Core fields: - chrome (boolean, optional): pass --chrome when running Claude - promptTemplate (string, optional): run prompt template - maxTurnsPerRun (number, optional): max turns for one run -- dangerouslySkipPermissions (boolean, optional): pass --dangerously-skip-permissions to claude +- dangerouslySkipPermissions (boolean, optional, default true): pass --dangerously-skip-permissions to claude; defaults to true because Paperclip runs Claude in headless --print mode where interactive permission prompts cannot be answered - command (string, optional): defaults to "claude" - extraArgs (string[], optional): additional CLI args - env (object, optional): KEY=VALUE environment variables diff --git a/packages/adapters/claude-local/src/server/execute.ts b/packages/adapters/claude-local/src/server/execute.ts index c7d6c6a8b22..ae5ea3abd4d 100644 --- a/packages/adapters/claude-local/src/server/execute.ts +++ b/packages/adapters/claude-local/src/server/execute.ts @@ -20,6 +20,8 @@ import { ensurePathInEnv, resolveCommandForLogs, renderTemplate, + renderPaperclipWakePrompt, + stringifyPaperclipWakePayload, runChildProcess, } from "@paperclipai/adapter-utils/server-utils"; import { @@ -30,6 +32,7 @@ import { isClaudeUnknownSessionError, } from "./parse.js"; import { resolveClaudeDesiredSkillNames } from "./skills.js"; +import { isBedrockModelId } from "./models.js"; const __moduleDir = path.dirname(fileURLToPath(import.meta.url)); @@ -100,8 +103,16 @@ function hasNonEmptyEnvValue(env: Record, key: string): boolean return typeof raw === "string" && raw.trim().length > 0; } -function resolveClaudeBillingType(env: Record): "api" | "subscription" { - // Claude uses API-key auth when ANTHROPIC_API_KEY is present; otherwise rely on local login/session auth. +function isBedrockAuth(env: Record): boolean { + return ( + env.CLAUDE_CODE_USE_BEDROCK === "1" || + env.CLAUDE_CODE_USE_BEDROCK === "true" || + hasNonEmptyEnvValue(env, "ANTHROPIC_BEDROCK_BASE_URL") + ); +} + +function resolveClaudeBillingType(env: Record): "api" | "subscription" | "metered_api" { + if (isBedrockAuth(env)) return "metered_api"; return hasNonEmptyEnvValue(env, "ANTHROPIC_API_KEY") ? "api" : "subscription"; } @@ -170,6 +181,7 @@ async function buildClaudeRuntimeConfig(input: ClaudeExecutionInput): Promise typeof value === "string" && value.trim().length > 0) : []; + const wakePayloadJson = stringifyPaperclipWakePayload(context.paperclipWake); if (wakeTaskId) { env.PAPERCLIP_TASK_ID = wakeTaskId; @@ -189,6 +201,9 @@ async function buildClaudeRuntimeConfig(input: ClaudeExecutionInput): Promise 0) { env.PAPERCLIP_LINKED_ISSUE_IDS = linkedIssueIds.join(","); } + if (wakePayloadJson) { + env.PAPERCLIP_WAKE_PAYLOAD_JSON = wakePayloadJson; + } if (effectiveWorkspaceCwd) { env.PAPERCLIP_WORKSPACE_CWD = effectiveWorkspaceCwd; } @@ -317,15 +332,9 @@ export async function execute(ctx: AdapterExecutionContext): Promise 0 && + (runtimeSessionCwd.length === 0 || path.resolve(runtimeSessionCwd) === path.resolve(cwd)); + const sessionId = canResumeSession ? runtimeSessionId : null; + if (runtimeSessionId && !canResumeSession) { + await onLog( + "stdout", + `[paperclip] Claude session "${runtimeSessionId}" was saved for cwd "${runtimeSessionCwd}" and will not be resumed in "${cwd}".\n`, + ); + } + + let effectiveInstructionsFilePath: string | undefined; + let preparedInstructionsFile = false; + + const ensureEffectiveInstructionsFilePath = async (resumeSessionId: string | null) => { + if (resumeSessionId || !instructionsFilePath) return undefined; + if (preparedInstructionsFile) return effectiveInstructionsFilePath; + + preparedInstructionsFile = true; try { const instructionsContent = await fs.readFile(instructionsFilePath, "utf-8"); const pathDirective = `\nThe above agent instructions were loaded from ${instructionsFilePath}. Resolve any relative file references from ${instructionsFileDir}.`; @@ -373,21 +399,9 @@ export async function execute(ctx: AdapterExecutionContext): Promise 0 && - (runtimeSessionCwd.length === 0 || path.resolve(runtimeSessionCwd) === path.resolve(cwd)); - const sessionId = canResumeSession ? runtimeSessionId : null; - if (runtimeSessionId && !canResumeSession) { - await onLog( - "stdout", - `[paperclip] Claude session "${runtimeSessionId}" was saved for cwd "${runtimeSessionCwd}" and will not be resumed in "${cwd}".\n`, - ); - } + return effectiveInstructionsFilePath; + }; const bootstrapPromptTemplate = asString(config.bootstrapPromptTemplate, ""); const templateData = { agentId: agent.id, @@ -398,34 +412,49 @@ export async function execute(ctx: AdapterExecutionContext): Promise 0 ? renderTemplate(bootstrapPromptTemplate, templateData).trim() : ""; + const wakePrompt = renderPaperclipWakePrompt(context.paperclipWake, { resumedSession: Boolean(sessionId) }); + const shouldUseResumeDeltaPrompt = Boolean(sessionId) && wakePrompt.length > 0; + const renderedPrompt = shouldUseResumeDeltaPrompt ? "" : renderTemplate(promptTemplate, templateData); const sessionHandoffNote = asString(context.paperclipSessionHandoffMarkdown, "").trim(); const prompt = joinPromptSections([ renderedBootstrapPrompt, + wakePrompt, sessionHandoffNote, renderedPrompt, ]); const promptMetrics = { promptChars: prompt.length, bootstrapPromptChars: renderedBootstrapPrompt.length, + wakePromptChars: wakePrompt.length, sessionHandoffChars: sessionHandoffNote.length, heartbeatPromptChars: renderedPrompt.length, }; - const buildClaudeArgs = (resumeSessionId: string | null) => { + const buildClaudeArgs = ( + resumeSessionId: string | null, + attemptInstructionsFilePath: string | undefined, + ) => { const args = ["--print", "-", "--output-format", "stream-json", "--verbose"]; if (resumeSessionId) args.push("--resume", resumeSessionId); if (dangerouslySkipPermissions) args.push("--dangerously-skip-permissions"); if (chrome) args.push("--chrome"); - if (model) args.push("--model", model); + // For Bedrock: only pass --model when the ID is a Bedrock-native identifier + // (e.g. "us.anthropic.*" or ARN). Anthropic-style IDs like "claude-opus-4-6" are invalid + // on Bedrock, so skip them and let the CLI use its own configured model. + if (model && (!isBedrockAuth(effectiveEnv) || isBedrockModelId(model))) { + args.push("--model", model); + } if (effort) args.push("--effort", effort); if (maxTurns > 0) args.push("--max-turns", String(maxTurns)); - if (effectiveInstructionsFilePath) { - args.push("--append-system-prompt-file", effectiveInstructionsFilePath); + // On resumed sessions the instructions are already in the session cache; + // re-injecting them via --append-system-prompt-file wastes 5-10K tokens + // per heartbeat and the Claude CLI may reject the combination outright. + if (attemptInstructionsFilePath && !resumeSessionId) { + args.push("--append-system-prompt-file", attemptInstructionsFilePath); } args.push("--add-dir", skillsDir); if (extraArgs.length > 0) args.push(...extraArgs); @@ -449,7 +478,14 @@ export async function execute(ctx: AdapterExecutionContext): Promise { - const args = buildClaudeArgs(resumeSessionId); + const attemptInstructionsFilePath = await ensureEffectiveInstructionsFilePath(resumeSessionId); + const args = buildClaudeArgs(resumeSessionId, attemptInstructionsFilePath); + const commandNotes = + attemptInstructionsFilePath && !resumeSessionId + ? [ + `Injected agent instructions via --append-system-prompt-file ${instructionsFilePath} (with path directive appended)`, + ] + : []; if (onMeta) { await onMeta({ adapterType: "claude_local", @@ -568,7 +604,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise 0) + ); +} + +/** + * Return the model list appropriate for the current auth mode. + * When Bedrock env vars are detected, returns Bedrock-native model IDs; + * otherwise returns standard Anthropic API model IDs. + */ +export async function listClaudeModels(): Promise { + return isBedrockEnv() ? BEDROCK_MODELS : DIRECT_MODELS; +} + +/** Check whether a model ID is a Bedrock-native identifier (not an Anthropic API short name). */ +/** Bedrock model IDs use region-qualified prefixes (e.g. us.anthropic.*, eu.anthropic.*) or ARNs. */ +export function isBedrockModelId(model: string): boolean { + return /^\w+\.anthropic\./.test(model) || model.startsWith("arn:aws:bedrock:"); +} diff --git a/packages/adapters/claude-local/src/server/quota.ts b/packages/adapters/claude-local/src/server/quota.ts index 812a58fe056..859a59c28e2 100644 --- a/packages/adapters/claude-local/src/server/quota.ts +++ b/packages/adapters/claude-local/src/server/quota.ts @@ -477,6 +477,14 @@ function formatProviderError(source: string, error: unknown): string { } export async function getQuotaWindows(): Promise { + if ( + process.env.CLAUDE_CODE_USE_BEDROCK === "1" || + process.env.CLAUDE_CODE_USE_BEDROCK === "true" || + hasNonEmptyProcessEnv("ANTHROPIC_BEDROCK_BASE_URL") + ) { + return { provider: "anthropic", source: "bedrock", ok: true, windows: [] }; + } + const authStatus = await readClaudeAuthStatus(); const authDescription = describeClaudeSubscriptionAuth(authStatus); const token = await readClaudeToken(); diff --git a/packages/adapters/claude-local/src/server/test.ts b/packages/adapters/claude-local/src/server/test.ts index 98d570c4327..be29c0e49a7 100644 --- a/packages/adapters/claude-local/src/server/test.ts +++ b/packages/adapters/claude-local/src/server/test.ts @@ -16,6 +16,7 @@ import { } from "@paperclipai/adapter-utils/server-utils"; import path from "node:path"; import { detectClaudeLoginRequired, parseClaudeStreamJson } from "./parse.js"; +import { isBedrockModelId } from "./models.js"; function summarizeStatus(checks: AdapterEnvironmentCheck[]): AdapterEnvironmentTestResult["status"] { if (checks.some((check) => check.level === "error")) return "fail"; @@ -95,9 +96,31 @@ export async function testEnvironment( }); } + const hasBedrock = + env.CLAUDE_CODE_USE_BEDROCK === "1" || + env.CLAUDE_CODE_USE_BEDROCK === "true" || + process.env.CLAUDE_CODE_USE_BEDROCK === "1" || + process.env.CLAUDE_CODE_USE_BEDROCK === "true" || + isNonEmpty(env.ANTHROPIC_BEDROCK_BASE_URL) || + isNonEmpty(process.env.ANTHROPIC_BEDROCK_BASE_URL); + const configApiKey = env.ANTHROPIC_API_KEY; const hostApiKey = process.env.ANTHROPIC_API_KEY; - if (isNonEmpty(configApiKey) || isNonEmpty(hostApiKey)) { + if (hasBedrock) { + const source = + env.CLAUDE_CODE_USE_BEDROCK === "1" || + env.CLAUDE_CODE_USE_BEDROCK === "true" || + isNonEmpty(env.ANTHROPIC_BEDROCK_BASE_URL) + ? "adapter config env" + : "server environment"; + checks.push({ + code: "claude_bedrock_auth", + level: "info", + message: "AWS Bedrock auth detected. Claude will use Bedrock for inference.", + detail: `Detected in ${source}.`, + hint: "Ensure AWS credentials (AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY or AWS_PROFILE) and AWS_REGION are configured.", + }); + } else if (isNonEmpty(configApiKey) || isNonEmpty(hostApiKey)) { const source = isNonEmpty(configApiKey) ? "adapter config env" : "server environment"; checks.push({ code: "claude_anthropic_api_key_overrides_subscription", @@ -131,7 +154,7 @@ export async function testEnvironment( const effort = asString(config.effort, "").trim(); const chrome = asBoolean(config.chrome, false); const maxTurns = asNumber(config.maxTurnsPerRun, 0); - const dangerouslySkipPermissions = asBoolean(config.dangerouslySkipPermissions, false); + const dangerouslySkipPermissions = asBoolean(config.dangerouslySkipPermissions, true); const extraArgs = (() => { const fromExtraArgs = asStringArray(config.extraArgs); if (fromExtraArgs.length > 0) return fromExtraArgs; @@ -141,7 +164,10 @@ export async function testEnvironment( const args = ["--print", "-", "--output-format", "stream-json", "--verbose"]; if (dangerouslySkipPermissions) args.push("--dangerously-skip-permissions"); if (chrome) args.push("--chrome"); - if (model) args.push("--model", model); + // For Bedrock: only pass --model when the ID is a Bedrock-native identifier. + if (model && (!hasBedrock || isBedrockModelId(model))) { + args.push("--model", model); + } if (effort) args.push("--effort", effort); if (maxTurns > 0) args.push("--max-turns", String(maxTurns)); if (extraArgs.length > 0) args.push(...extraArgs); diff --git a/packages/adapters/codex-local/src/index.ts b/packages/adapters/codex-local/src/index.ts index afb2cd9b724..ca795cb5182 100644 --- a/packages/adapters/codex-local/src/index.ts +++ b/packages/adapters/codex-local/src/index.ts @@ -24,7 +24,7 @@ Core fields: - cwd (string, optional): default absolute working directory fallback for the agent process (created if missing when possible) - instructionsFilePath (string, optional): absolute path to a markdown instructions file prepended to stdin prompt at runtime - model (string, optional): Codex model id -- modelReasoningEffort (string, optional): reasoning effort override (minimal|low|medium|high) passed via -c model_reasoning_effort=... +- modelReasoningEffort (string, optional): reasoning effort override (minimal|low|medium|high|xhigh) passed via -c model_reasoning_effort=... - promptTemplate (string, optional): run prompt template - search (boolean, optional): run codex with --search - dangerouslyBypassApprovalsAndSandbox (boolean, optional): run with bypass flag diff --git a/packages/adapters/codex-local/src/server/execute.ts b/packages/adapters/codex-local/src/server/execute.ts index 63d2dc95e93..c6ba3e9ba9f 100644 --- a/packages/adapters/codex-local/src/server/execute.ts +++ b/packages/adapters/codex-local/src/server/execute.ts @@ -18,6 +18,8 @@ import { resolveCommandForLogs, resolvePaperclipDesiredSkillNames, renderTemplate, + renderPaperclipWakePrompt, + stringifyPaperclipWakePayload, joinPromptSections, runChildProcess, } from "@paperclipai/adapter-utils/server-utils"; @@ -313,6 +315,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise typeof value === "string" && value.trim().length > 0) : []; + const wakePayloadJson = stringifyPaperclipWakePayload(context.paperclipWake); if (wakeTaskId) { env.PAPERCLIP_TASK_ID = wakeTaskId; } @@ -331,6 +334,9 @@ export async function execute(ctx: AdapterExecutionContext): Promise 0) { env.PAPERCLIP_LINKED_ISSUE_IDS = linkedIssueIds.join(","); } + if (wakePayloadJson) { + env.PAPERCLIP_WAKE_PAYLOAD_JSON = wakePayloadJson; + } if (effectiveWorkspaceCwd) { env.PAPERCLIP_WORKSPACE_CWD = effectiveWorkspaceCwd; } @@ -434,11 +440,36 @@ export async function execute(ctx: AdapterExecutionContext): Promise 0 + ? renderTemplate(bootstrapPromptTemplate, templateData).trim() + : ""; + const wakePrompt = renderPaperclipWakePrompt(context.paperclipWake, { resumedSession: Boolean(sessionId) }); + const shouldUseResumeDeltaPrompt = Boolean(sessionId) && wakePrompt.length > 0; + const promptInstructionsPrefix = shouldUseResumeDeltaPrompt ? "" : instructionsPrefix; + instructionsChars = promptInstructionsPrefix.length; const commandNotes = (() => { if (!instructionsFilePath) { return [repoAgentsNote]; } if (instructionsPrefix.length > 0) { + if (shouldUseResumeDeltaPrompt) { + return [ + `Loaded agent instructions from ${instructionsFilePath}`, + "Skipped stdin instruction reinjection because an existing Codex session is being resumed with a wake delta.", + repoAgentsNote, + ]; + } return [ `Loaded agent instructions from ${instructionsFilePath}`, `Prepended instructions + path directive to stdin prompt (relative references from ${instructionsDir}).`, @@ -450,25 +481,12 @@ export async function execute(ctx: AdapterExecutionContext): Promise 0 - ? renderTemplate(bootstrapPromptTemplate, templateData).trim() - : ""; + const renderedPrompt = shouldUseResumeDeltaPrompt ? "" : renderTemplate(promptTemplate, templateData); const sessionHandoffNote = asString(context.paperclipSessionHandoffMarkdown, "").trim(); const prompt = joinPromptSections([ - instructionsPrefix, + promptInstructionsPrefix, renderedBootstrapPrompt, + wakePrompt, sessionHandoffNote, renderedPrompt, ]); @@ -476,6 +494,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise { + it("captures session id, assistant summary, usage, and error message", () => { + const stdout = [ + JSON.stringify({ type: "thread.started", thread_id: "thread_123" }), + JSON.stringify({ + type: "item.completed", + item: { type: "agent_message", text: "Recovered response" }, + }), + JSON.stringify({ + type: "turn.completed", + usage: { input_tokens: 10, cached_input_tokens: 2, output_tokens: 4 }, + }), + JSON.stringify({ type: "turn.failed", error: { message: "resume failed" } }), + ].join("\n"); + + expect(parseCodexJsonl(stdout)).toEqual({ + sessionId: "thread_123", + summary: "Recovered response", + usage: { + inputTokens: 10, + cachedInputTokens: 2, + outputTokens: 4, + }, + errorMessage: "resume failed", + }); + }); +}); + +describe("isCodexUnknownSessionError", () => { + it("detects the current missing-rollout thread error", () => { + expect( + isCodexUnknownSessionError( + "", + "Error: thread/resume: thread/resume failed: no rollout found for thread id d448e715-7607-4bcc-91fc-7a3c0c5a9632", + ), + ).toBe(true); + }); + + it("still detects existing stale-session wordings", () => { + expect(isCodexUnknownSessionError("unknown thread id", "")).toBe(true); + expect(isCodexUnknownSessionError("", "state db missing rollout path for thread abc")).toBe(true); + }); + + it("does not classify unrelated Codex failures as stale sessions", () => { + expect(isCodexUnknownSessionError("", "model overloaded")).toBe(false); + }); +}); diff --git a/packages/adapters/codex-local/src/server/parse.ts b/packages/adapters/codex-local/src/server/parse.ts index afcf935c4b8..1a7aa6a7e16 100644 --- a/packages/adapters/codex-local/src/server/parse.ts +++ b/packages/adapters/codex-local/src/server/parse.ts @@ -67,7 +67,7 @@ export function isCodexUnknownSessionError(stdout: string, stderr: string): bool .map((line) => line.trim()) .filter(Boolean) .join("\n"); - return /unknown (session|thread)|session .* not found|thread .* not found|conversation .* not found|missing rollout path for thread|state db missing rollout path/i.test( + return /unknown (session|thread)|session .* not found|thread .* not found|conversation .* not found|missing rollout path for thread|state db missing rollout path|no rollout found for thread id/i.test( haystack, ); } diff --git a/packages/adapters/codex-local/src/server/quota-spawn-error.test.ts b/packages/adapters/codex-local/src/server/quota-spawn-error.test.ts new file mode 100644 index 00000000000..85d1e44c673 --- /dev/null +++ b/packages/adapters/codex-local/src/server/quota-spawn-error.test.ts @@ -0,0 +1,85 @@ +import { EventEmitter } from "node:events"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import type { ChildProcess } from "node:child_process"; +import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"; + +const { mockSpawn } = vi.hoisted(() => ({ + mockSpawn: vi.fn(), +})); + +vi.mock("node:child_process", async (importOriginal) => { + const cp = await importOriginal(); + return { + ...cp, + spawn: (...args: Parameters) => mockSpawn(...args) as ReturnType, + }; +}); + +import { getQuotaWindows } from "./quota.js"; + +function createChildThatErrorsOnMicrotask(err: Error): ChildProcess { + const child = new EventEmitter() as ChildProcess; + const stream = Object.assign(new EventEmitter(), { + setEncoding: () => {}, + }); + Object.assign(child, { + stdout: stream, + stderr: Object.assign(new EventEmitter(), { setEncoding: () => {} }), + stdin: { write: vi.fn(), end: vi.fn() }, + kill: vi.fn(), + }); + queueMicrotask(() => { + child.emit("error", err); + }); + return child; +} + +describe("CodexRpcClient spawn failures", () => { + let previousCodexHome: string | undefined; + let isolatedCodexHome: string | undefined; + + beforeEach(() => { + mockSpawn.mockReset(); + // After the RPC path fails, getQuotaWindows() calls readCodexToken() which + // reads $CODEX_HOME/auth.json (default ~/.codex). Point CODEX_HOME at an + // empty temp directory so we never hit real host auth or the WHAM network. + previousCodexHome = process.env.CODEX_HOME; + isolatedCodexHome = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-codex-spawn-test-")); + process.env.CODEX_HOME = isolatedCodexHome; + }); + + afterEach(() => { + if (isolatedCodexHome) { + try { + fs.rmSync(isolatedCodexHome, { recursive: true, force: true }); + } catch { + /* ignore */ + } + isolatedCodexHome = undefined; + } + if (previousCodexHome === undefined) { + delete process.env.CODEX_HOME; + } else { + process.env.CODEX_HOME = previousCodexHome; + } + }); + + it("does not crash the process when codex is missing; getQuotaWindows returns ok: false", async () => { + const enoent = Object.assign(new Error("spawn codex ENOENT"), { + code: "ENOENT", + errno: -2, + syscall: "spawn codex", + path: "codex", + }); + mockSpawn.mockImplementation(() => createChildThatErrorsOnMicrotask(enoent)); + + const result = await getQuotaWindows(); + + expect(result.ok).toBe(false); + expect(result.windows).toEqual([]); + expect(result.error).toContain("Codex app-server"); + expect(result.error).toContain("spawn codex ENOENT"); + }); +}); diff --git a/packages/adapters/codex-local/src/server/quota.ts b/packages/adapters/codex-local/src/server/quota.ts index 7bc771e46eb..3c0ac3bf733 100644 --- a/packages/adapters/codex-local/src/server/quota.ts +++ b/packages/adapters/codex-local/src/server/quota.ts @@ -432,6 +432,13 @@ class CodexRpcClient { } this.pending.clear(); }); + this.proc.on("error", (err: Error) => { + for (const request of this.pending.values()) { + clearTimeout(request.timer); + request.reject(err); + } + this.pending.clear(); + }); } private onStdout(chunk: string) { diff --git a/packages/adapters/codex-local/src/ui/parse-stdout.test.ts b/packages/adapters/codex-local/src/ui/parse-stdout.test.ts new file mode 100644 index 00000000000..976377faf3e --- /dev/null +++ b/packages/adapters/codex-local/src/ui/parse-stdout.test.ts @@ -0,0 +1,83 @@ +import { describe, expect, it } from "vitest"; +import { parseCodexStdoutLine } from "./parse-stdout.js"; + +describe("parseCodexStdoutLine", () => { + it("marks completed tool_use items as resolved tool results", () => { + const started = parseCodexStdoutLine(JSON.stringify({ + type: "item.started", + item: { + id: "tool-1", + type: "tool_use", + name: "search", + input: { query: "paperclip" }, + }, + }), "2026-04-08T12:00:00.000Z"); + + const completed = parseCodexStdoutLine(JSON.stringify({ + type: "item.completed", + item: { + id: "tool-1", + type: "tool_use", + name: "search", + status: "completed", + }, + }), "2026-04-08T12:00:01.000Z"); + + expect(started).toEqual([{ + kind: "tool_call", + ts: "2026-04-08T12:00:00.000Z", + name: "search", + toolUseId: "tool-1", + input: { query: "paperclip" }, + }]); + expect(completed).toEqual([{ + kind: "tool_result", + ts: "2026-04-08T12:00:01.000Z", + toolUseId: "tool-1", + content: "search completed", + isError: false, + }]); + }); + + it("keeps explicit tool_result payloads authoritative after tool_use completion", () => { + const completed = parseCodexStdoutLine(JSON.stringify({ + type: "item.completed", + item: { + id: "tool-2", + type: "tool_result", + tool_use_id: "tool-1", + content: "final payload", + status: "completed", + }, + }), "2026-04-08T12:00:02.000Z"); + + expect(completed).toEqual([{ + kind: "tool_result", + ts: "2026-04-08T12:00:02.000Z", + toolUseId: "tool-1", + content: "final payload", + isError: false, + }]); + }); + + it("marks failed completed tool_use items as error results", () => { + const completed = parseCodexStdoutLine(JSON.stringify({ + type: "item.completed", + item: { + id: "tool-3", + type: "tool_use", + name: "write_file", + status: "error", + error: { message: "permission denied" }, + }, + }), "2026-04-08T12:00:03.000Z"); + + expect(completed).toEqual([{ + kind: "tool_result", + ts: "2026-04-08T12:00:03.000Z", + toolUseId: "tool-3", + content: "permission denied", + isError: true, + }]); + }); +}); diff --git a/packages/adapters/codex-local/src/ui/parse-stdout.ts b/packages/adapters/codex-local/src/ui/parse-stdout.ts index 0f1786b6eb0..cb5661b9e9e 100644 --- a/packages/adapters/codex-local/src/ui/parse-stdout.ts +++ b/packages/adapters/codex-local/src/ui/parse-stdout.ts @@ -118,6 +118,52 @@ function parseFileChangeItem(item: Record, ts: string): Transcr return [{ kind: "system", ts, text: `file changes: ${preview}${more}` }]; } +function parseToolUseItem( + item: Record, + ts: string, + phase: "started" | "completed", +): TranscriptEntry[] { + const name = asString(item.name, "unknown"); + const toolUseId = asString(item.id, name || "tool_use"); + + if (phase === "started") { + return [{ + kind: "tool_call", + ts, + name, + toolUseId, + input: item.input ?? {}, + }]; + } + + const status = asString(item.status); + const isError = + item.is_error === true || + status === "failed" || + status === "errored" || + status === "error" || + status === "cancelled"; + const rawContent = + item.content ?? + item.output ?? + item.result ?? + item.error ?? + item.message; + const content = + asString(rawContent) || + errorText(rawContent) || + stringifyUnknown(rawContent) || + `${name} ${isError ? "failed" : "completed"}`; + + return [{ + kind: "tool_result", + ts, + toolUseId, + content, + isError, + }]; +} + function parseCodexItem( item: Record, ts: string, @@ -146,13 +192,7 @@ function parseCodexItem( } if (itemType === "tool_use") { - return [{ - kind: "tool_call", - ts, - name: asString(item.name, "unknown"), - toolUseId: asString(item.id), - input: item.input ?? {}, - }]; + return parseToolUseItem(item, ts, phase); } if (itemType === "tool_result" && phase === "completed") { diff --git a/packages/adapters/codex-local/vitest.config.ts b/packages/adapters/codex-local/vitest.config.ts new file mode 100644 index 00000000000..f624398e8de --- /dev/null +++ b/packages/adapters/codex-local/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + }, +}); diff --git a/packages/adapters/cursor-local/src/server/execute.ts b/packages/adapters/cursor-local/src/server/execute.ts index ed41754a9e1..648ef72cc3c 100644 --- a/packages/adapters/cursor-local/src/server/execute.ts +++ b/packages/adapters/cursor-local/src/server/execute.ts @@ -19,6 +19,8 @@ import { resolvePaperclipDesiredSkillNames, removeMaintainerOnlySkillSymlinks, renderTemplate, + renderPaperclipWakePrompt, + stringifyPaperclipWakePayload, joinPromptSections, runChildProcess, } from "@paperclipai/adapter-utils/server-utils"; @@ -219,6 +221,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise typeof value === "string" && value.trim().length > 0) : []; + const wakePayloadJson = stringifyPaperclipWakePayload(context.paperclipWake); if (wakeTaskId) { env.PAPERCLIP_TASK_ID = wakeTaskId; } @@ -237,6 +240,9 @@ export async function execute(ctx: AdapterExecutionContext): Promise 0) { env.PAPERCLIP_LINKED_ISSUE_IDS = linkedIssueIds.join(","); } + if (wakePayloadJson) { + env.PAPERCLIP_WAKE_PAYLOAD_JSON = wakePayloadJson; + } if (effectiveWorkspaceCwd) { env.PAPERCLIP_WORKSPACE_CWD = effectiveWorkspaceCwd; } @@ -352,16 +358,19 @@ export async function execute(ctx: AdapterExecutionContext): Promise 0 ? renderTemplate(bootstrapPromptTemplate, templateData).trim() : ""; + const wakePrompt = renderPaperclipWakePrompt(context.paperclipWake, { resumedSession: Boolean(sessionId) }); + const shouldUseResumeDeltaPrompt = Boolean(sessionId) && wakePrompt.length > 0; + const renderedPrompt = shouldUseResumeDeltaPrompt ? "" : renderTemplate(promptTemplate, templateData); const sessionHandoffNote = asString(context.paperclipSessionHandoffMarkdown, "").trim(); const paperclipEnvNote = renderPaperclipEnvNote(env); const prompt = joinPromptSections([ instructionsPrefix, renderedBootstrapPrompt, + wakePrompt, sessionHandoffNote, paperclipEnvNote, renderedPrompt, @@ -370,6 +379,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise typeof value === "string" && value.trim().length > 0) : []; + const wakePayloadJson = stringifyPaperclipWakePayload(context.paperclipWake); if (wakeTaskId) env.PAPERCLIP_TASK_ID = wakeTaskId; if (wakeReason) env.PAPERCLIP_WAKE_REASON = wakeReason; if (wakeCommentId) env.PAPERCLIP_WAKE_COMMENT_ID = wakeCommentId; if (approvalId) env.PAPERCLIP_APPROVAL_ID = approvalId; if (approvalStatus) env.PAPERCLIP_APPROVAL_STATUS = approvalStatus; if (linkedIssueIds.length > 0) env.PAPERCLIP_LINKED_ISSUE_IDS = linkedIssueIds.join(","); + if (wakePayloadJson) env.PAPERCLIP_WAKE_PAYLOAD_JSON = wakePayloadJson; if (effectiveWorkspaceCwd) env.PAPERCLIP_WORKSPACE_CWD = effectiveWorkspaceCwd; if (workspaceSource) env.PAPERCLIP_WORKSPACE_SOURCE = workspaceSource; if (workspaceId) env.PAPERCLIP_WORKSPACE_ID = workspaceId; @@ -295,17 +299,20 @@ export async function execute(ctx: AdapterExecutionContext): Promise 0 ? renderTemplate(bootstrapPromptTemplate, templateData).trim() : ""; + const wakePrompt = renderPaperclipWakePrompt(context.paperclipWake, { resumedSession: Boolean(sessionId) }); + const shouldUseResumeDeltaPrompt = Boolean(sessionId) && wakePrompt.length > 0; + const renderedPrompt = shouldUseResumeDeltaPrompt ? "" : renderTemplate(promptTemplate, templateData); const sessionHandoffNote = asString(context.paperclipSessionHandoffMarkdown, "").trim(); const paperclipEnvNote = renderPaperclipEnvNote(env); const apiAccessNote = renderApiAccessNote(env); const prompt = joinPromptSections([ instructionsPrefix, renderedBootstrapPrompt, + wakePrompt, sessionHandoffNote, paperclipEnvNote, apiAccessNote, @@ -315,6 +322,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise { + it("prefixes run-scoped session keys with the configured agent", () => { + expect( + resolveSessionKey({ + strategy: "run", + configuredSessionKey: null, + agentId: "meridian", + runId: "run-123", + issueId: null, + }), + ).toBe("agent:meridian:paperclip:run:run-123"); + }); + + it("prefixes issue-scoped session keys with the configured agent", () => { + expect( + resolveSessionKey({ + strategy: "issue", + configuredSessionKey: null, + agentId: "meridian", + runId: "run-123", + issueId: "issue-456", + }), + ).toBe("agent:meridian:paperclip:issue:issue-456"); + }); + + it("prefixes fixed session keys with the configured agent", () => { + expect( + resolveSessionKey({ + strategy: "fixed", + configuredSessionKey: "paperclip", + agentId: "meridian", + runId: "run-123", + issueId: null, + }), + ).toBe("agent:meridian:paperclip"); + }); + + it("does not double-prefix an already-routed session key", () => { + expect( + resolveSessionKey({ + strategy: "fixed", + configuredSessionKey: "agent:meridian:paperclip", + agentId: "meridian", + runId: "run-123", + issueId: null, + }), + ).toBe("agent:meridian:paperclip"); + }); +}); diff --git a/packages/adapters/openclaw-gateway/src/server/execute.ts b/packages/adapters/openclaw-gateway/src/server/execute.ts index f1c85c11c28..02235f39afc 100644 --- a/packages/adapters/openclaw-gateway/src/server/execute.ts +++ b/packages/adapters/openclaw-gateway/src/server/execute.ts @@ -3,7 +3,14 @@ import type { AdapterExecutionResult, AdapterRuntimeServiceReport, } from "@paperclipai/adapter-utils"; -import { asNumber, asString, buildPaperclipEnv, parseObject } from "@paperclipai/adapter-utils/server-utils"; +import { + asNumber, + asString, + buildPaperclipEnv, + parseObject, + renderPaperclipWakePrompt, + stringifyPaperclipWakePayload, +} from "@paperclipai/adapter-utils/server-utils"; import crypto, { randomUUID } from "node:crypto"; import { WebSocket } from "ws"; @@ -126,16 +133,26 @@ function normalizeSessionKeyStrategy(value: unknown): SessionKeyStrategy { return "issue"; } -function resolveSessionKey(input: { +function prefixSessionKeyForAgent(sessionKey: string, agentId: string | null): string { + if (!agentId || sessionKey.startsWith("agent:")) return sessionKey; + return `agent:${agentId}:${sessionKey}`; +} + +export function resolveSessionKey(input: { strategy: SessionKeyStrategy; configuredSessionKey: string | null; + agentId: string | null; runId: string; issueId: string | null; }): string { const fallback = input.configuredSessionKey ?? "paperclip"; - if (input.strategy === "run") return `paperclip:run:${input.runId}`; - if (input.strategy === "issue" && input.issueId) return `paperclip:issue:${input.issueId}`; - return fallback; + if (input.strategy === "run") { + return prefixSessionKeyForAgent(`paperclip:run:${input.runId}`, input.agentId); + } + if (input.strategy === "issue" && input.issueId) { + return prefixSessionKeyForAgent(`paperclip:issue:${input.issueId}`, input.agentId); + } + return prefixSessionKeyForAgent(fallback, input.agentId); } function isLoopbackHost(hostname: string): boolean { @@ -313,6 +330,12 @@ function resolvePaperclipApiUrlOverride(value: unknown): string | null { } } +const DEFAULT_CLAIMED_API_KEY_PATH = "~/.openclaw/workspace/paperclip-claimed-api-key.json"; + +function resolveClaimedApiKeyPath(value: unknown): string { + return nonEmpty(value) ?? DEFAULT_CLAIMED_API_KEY_PATH; +} + function buildPaperclipEnvForWake(ctx: AdapterExecutionContext, wakePayload: WakePayload): Record { const paperclipApiUrlOverride = resolvePaperclipApiUrlOverride(ctx.config.paperclipApiUrl); const paperclipEnv: Record = { @@ -335,7 +358,11 @@ function buildPaperclipEnvForWake(ctx: AdapterExecutionContext, wakePayload: Wak return paperclipEnv; } -function buildWakeText(payload: WakePayload, paperclipEnv: Record): string { +function buildWakeText( + payload: WakePayload, + paperclipEnv: Record, + structuredWakePrompt: string, +): string { const claimedApiKeyPath = "~/.openclaw/workspace/paperclip-claimed-api-key.json"; const orderedKeys = [ "PAPERCLIP_RUN_ID", @@ -390,20 +417,26 @@ function buildWakeText(payload: WakePayload, paperclipEnv: Record 0 ? `${trimmedBase}\n\n${wakeText}` : wakeText; } +function joinWakePayloadSections(structuredWakePrompt: string, structuredWakeJson: string): string { + const sections = [ + structuredWakePrompt.trim(), + "Structured wake payload JSON:", + "```json", + structuredWakeJson, + "```", + ].filter((entry) => entry.trim().length > 0); + return sections.join("\n"); +} + function buildStandardPaperclipPayload( ctx: AdapterExecutionContext, wakePayload: WakePayload, @@ -447,6 +491,10 @@ function buildStandardPaperclipPayload( approvalStatus: wakePayload.approvalStatus, apiUrl: paperclipEnv.PAPERCLIP_API_URL ?? null, }; + const structuredWake = parseObject(ctx.context.paperclipWake); + if (Object.keys(structuredWake).length > 0) { + standardPaperclip.wake = structuredWake; + } if (workspace) { standardPaperclip.workspace = workspace; @@ -1053,13 +1101,22 @@ export async function execute(ctx: AdapterExecutionContext): Promise = []; +export const DEFAULT_OPENCODE_LOCAL_MODEL = "openai/gpt-5.2-codex"; + +export const models: Array<{ id: string; label: string }> = [ + { id: DEFAULT_OPENCODE_LOCAL_MODEL, label: DEFAULT_OPENCODE_LOCAL_MODEL }, + { id: "openai/gpt-5.4", label: "openai/gpt-5.4" }, + { id: "openai/gpt-5.2", label: "openai/gpt-5.2" }, + { id: "openai/gpt-5.1-codex-max", label: "openai/gpt-5.1-codex-max" }, + { id: "openai/gpt-5.1-codex-mini", label: "openai/gpt-5.1-codex-mini" }, +]; export const agentConfigurationDoc = `# opencode_local agent configuration @@ -21,7 +29,7 @@ Core fields: - cwd (string, optional): default absolute working directory fallback for the agent process (created if missing when possible) - instructionsFilePath (string, optional): absolute path to a markdown instructions file prepended to the run prompt - model (string, required): OpenCode model id in provider/model format (for example anthropic/claude-sonnet-4-5) -- variant (string, optional): provider-specific model variant (for example minimal|low|medium|high|max) +- variant (string, optional): provider-specific reasoning/profile variant passed as --variant (for example minimal|low|medium|high|xhigh|max) - dangerouslySkipPermissions (boolean, optional): inject a runtime OpenCode config that allows \`external_directory\` access without interactive prompts; defaults to true for unattended Paperclip runs - promptTemplate (string, optional): run prompt template - command (string, optional): defaults to "opencode" diff --git a/packages/adapters/opencode-local/src/server/execute.ts b/packages/adapters/opencode-local/src/server/execute.ts index 7c034c692ae..2a7dae5aa08 100644 --- a/packages/adapters/opencode-local/src/server/execute.ts +++ b/packages/adapters/opencode-local/src/server/execute.ts @@ -17,6 +17,8 @@ import { ensurePathInEnv, resolveCommandForLogs, renderTemplate, + renderPaperclipWakePrompt, + stringifyPaperclipWakePayload, runChildProcess, readPaperclipRuntimeSkillEntries, resolvePaperclipDesiredSkillNames, @@ -154,12 +156,14 @@ export async function execute(ctx: AdapterExecutionContext): Promise typeof value === "string" && value.trim().length > 0) : []; + const wakePayloadJson = stringifyPaperclipWakePayload(context.paperclipWake); if (wakeTaskId) env.PAPERCLIP_TASK_ID = wakeTaskId; if (wakeReason) env.PAPERCLIP_WAKE_REASON = wakeReason; if (wakeCommentId) env.PAPERCLIP_WAKE_COMMENT_ID = wakeCommentId; if (approvalId) env.PAPERCLIP_APPROVAL_ID = approvalId; if (approvalStatus) env.PAPERCLIP_APPROVAL_STATUS = approvalStatus; if (linkedIssueIds.length > 0) env.PAPERCLIP_LINKED_ISSUE_IDS = linkedIssueIds.join(","); + if (wakePayloadJson) env.PAPERCLIP_WAKE_PAYLOAD_JSON = wakePayloadJson; if (effectiveWorkspaceCwd) env.PAPERCLIP_WORKSPACE_CWD = effectiveWorkspaceCwd; if (workspaceSource) env.PAPERCLIP_WORKSPACE_SOURCE = workspaceSource; if (workspaceId) env.PAPERCLIP_WORKSPACE_ID = workspaceId; @@ -222,7 +226,6 @@ export async function execute(ctx: AdapterExecutionContext): Promise 0 ? renderTemplate(bootstrapPromptTemplate, templateData).trim() : ""; + const wakePrompt = renderPaperclipWakePrompt(context.paperclipWake, { resumedSession: Boolean(sessionId) }); + const shouldUseResumeDeltaPrompt = Boolean(sessionId) && wakePrompt.length > 0; + const renderedPrompt = shouldUseResumeDeltaPrompt ? "" : renderTemplate(promptTemplate, templateData); const sessionHandoffNote = asString(context.paperclipSessionHandoffMarkdown, "").trim(); const prompt = joinPromptSections([ instructionsPrefix, renderedBootstrapPrompt, + wakePrompt, sessionHandoffNote, renderedPrompt, ]); @@ -287,6 +293,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise typeof value === "string" && value.trim().length > 0) : []; + const wakePayloadJson = stringifyPaperclipWakePayload(context.paperclipWake); if (wakeTaskId) env.PAPERCLIP_TASK_ID = wakeTaskId; if (wakeReason) env.PAPERCLIP_WAKE_REASON = wakeReason; @@ -184,6 +187,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise 0) env.PAPERCLIP_LINKED_ISSUE_IDS = linkedIssueIds.join(","); + if (wakePayloadJson) env.PAPERCLIP_WAKE_PAYLOAD_JSON = wakePayloadJson; if (workspaceCwd) env.PAPERCLIP_WORKSPACE_CWD = workspaceCwd; if (workspaceSource) env.PAPERCLIP_WORKSPACE_SOURCE = workspaceSource; if (workspaceId) env.PAPERCLIP_WORKSPACE_ID = workspaceId; @@ -298,14 +302,17 @@ export async function execute(ctx: AdapterExecutionContext): Promise 0 ? renderTemplate(bootstrapPromptTemplate, templateData).trim() : ""; + const wakePrompt = renderPaperclipWakePrompt(context.paperclipWake, { resumedSession: canResumeSession }); + const shouldUseResumeDeltaPrompt = canResumeSession && wakePrompt.length > 0; + const renderedHeartbeatPrompt = shouldUseResumeDeltaPrompt ? "" : renderTemplate(promptTemplate, templateData); const sessionHandoffNote = asString(context.paperclipSessionHandoffMarkdown, "").trim(); const userPrompt = joinPromptSections([ renderedBootstrapPrompt, + wakePrompt, sessionHandoffNote, renderedHeartbeatPrompt, ]); @@ -313,6 +320,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise error.trim().length > 0) ?? ""; + const effectiveExitCode = (rawExitCode ?? 0) === 0 && parsedError ? 1 : rawExitCode; + const fallbackErrorMessage = parsedError || stderrLine || `Pi exited with code ${rawExitCode ?? -1}`; return { - exitCode: rawExitCode, + exitCode: effectiveExitCode, signal: attempt.proc.signal, timedOut: false, - errorMessage: (rawExitCode ?? 0) === 0 ? null : fallbackErrorMessage, + errorMessage: (effectiveExitCode ?? 0) === 0 ? null : fallbackErrorMessage, usage: { inputTokens: attempt.parsed.usage.inputTokens, outputTokens: attempt.parsed.usage.outputTokens, diff --git a/packages/adapters/pi-local/src/server/parse.test.ts b/packages/adapters/pi-local/src/server/parse.test.ts index 6a3eef4d268..34ef8b93477 100644 --- a/packages/adapters/pi-local/src/server/parse.test.ts +++ b/packages/adapters/pi-local/src/server/parse.test.ts @@ -209,6 +209,57 @@ describe("parsePiJsonl", () => { expect(parsed.usage.cachedInputTokens).toBe(25); expect(parsed.usage.costUsd).toBe(0.003); }); + + it("surfaces failed auto-retry exhaustion as an error", () => { + const stdout = [ + JSON.stringify({ + type: "auto_retry_end", + success: false, + attempt: 3, + finalError: "Cloud Code Assist API error (429): RESOURCE_EXHAUSTED", + }), + ].join("\n"); + + const parsed = parsePiJsonl(stdout); + expect(parsed.errors).toEqual(["Cloud Code Assist API error (429): RESOURCE_EXHAUSTED"]); + }); + + it("does not treat successful auto-retry as an error", () => { + const stdout = [ + JSON.stringify({ + type: "auto_retry_end", + success: true, + attempt: 2, + }), + ].join("\n"); + + const parsed = parsePiJsonl(stdout); + expect(parsed.errors).toEqual([]); + }); + + it("surfaces standalone error events", () => { + const stdout = [ + JSON.stringify({ + type: "error", + message: "Connection to model provider lost", + }), + ].join("\n"); + + const parsed = parsePiJsonl(stdout); + expect(parsed.errors).toEqual(["Connection to model provider lost"]); + }); + + it("ignores error events with empty messages", () => { + const stdout = [ + JSON.stringify({ + type: "error", + message: "", + }), + ].join("\n"); + + const parsed = parsePiJsonl(stdout); + expect(parsed.errors).toEqual([]); + }); }); describe("isPiUnknownSessionError", () => { diff --git a/packages/adapters/pi-local/src/server/parse.ts b/packages/adapters/pi-local/src/server/parse.ts index 3ba50d8b7aa..5ab0d4cd9a8 100644 --- a/packages/adapters/pi-local/src/server/parse.ts +++ b/packages/adapters/pi-local/src/server/parse.ts @@ -76,6 +76,15 @@ export function parsePiJsonl(stdout: string): ParsedPiOutput { continue; } + if (eventType === "auto_retry_end") { + const succeeded = event.success === true; + if (!succeeded) { + const finalError = asString(event.finalError, "").trim(); + result.errors.push(finalError || "Pi exhausted automatic retries without producing a response."); + } + continue; + } + // Turn lifecycle if (eventType === "turn_start") { continue; @@ -145,6 +154,14 @@ export function parsePiJsonl(stdout: string): ParsedPiOutput { continue; } + if (eventType === "error") { + const message = asString(event.message, "").trim(); + if (message) { + result.errors.push(message); + } + continue; + } + // Tool execution if (eventType === "tool_execution_start") { const toolCallId = asString(event.toolCallId, ""); diff --git a/packages/db/package.json b/packages/db/package.json index e879d3de209..5a5b0d51d23 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -35,11 +35,12 @@ "dist" ], "scripts": { - "build": "tsc && cp -r src/migrations dist/migrations", + "check:migrations": "tsx src/check-migration-numbering.ts", + "build": "pnpm run check:migrations && tsc && cp -r src/migrations dist/migrations", "clean": "rm -rf dist", - "typecheck": "tsc --noEmit", - "generate": "tsc -p tsconfig.json && drizzle-kit generate", - "migrate": "tsx src/migrate.ts", + "typecheck": "pnpm run check:migrations && tsc --noEmit", + "generate": "pnpm run check:migrations && tsc -p tsconfig.json && drizzle-kit generate", + "migrate": "pnpm run check:migrations && tsx src/migrate.ts", "seed": "tsx src/seed.ts" }, "dependencies": { diff --git a/packages/db/src/backup-lib.test.ts b/packages/db/src/backup-lib.test.ts new file mode 100644 index 00000000000..dcdc87c50e4 --- /dev/null +++ b/packages/db/src/backup-lib.test.ts @@ -0,0 +1,224 @@ +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; +import postgres from "postgres"; +import { createBufferedTextFileWriter, runDatabaseBackup, runDatabaseRestore } from "./backup-lib.js"; +import { ensurePostgresDatabase } from "./client.js"; +import { + getEmbeddedPostgresTestSupport, + startEmbeddedPostgresTestDatabase, +} from "./test-embedded-postgres.js"; + +const cleanups: Array<() => Promise | void> = []; +const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport(); +const describeEmbeddedPostgres = embeddedPostgresSupport.supported ? describe : describe.skip; + +function createTempDir(prefix: string): string { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix)); + cleanups.push(() => { + fs.rmSync(dir, { recursive: true, force: true }); + }); + return dir; +} + +async function createTempDatabase(): Promise { + const db = await startEmbeddedPostgresTestDatabase("paperclip-db-backup-"); + cleanups.push(db.cleanup); + return db.connectionString; +} + +async function createSiblingDatabase(connectionString: string, databaseName: string): Promise { + const adminUrl = new URL(connectionString); + adminUrl.pathname = "/postgres"; + await ensurePostgresDatabase(adminUrl.toString(), databaseName); + const targetUrl = new URL(connectionString); + targetUrl.pathname = `/${databaseName}`; + return targetUrl.toString(); +} + +afterEach(async () => { + while (cleanups.length > 0) { + const cleanup = cleanups.pop(); + await cleanup?.(); + } +}); + +if (!embeddedPostgresSupport.supported) { + console.warn( + `Skipping embedded Postgres backup tests on this host: ${embeddedPostgresSupport.reason ?? "unsupported environment"}`, + ); +} + +describe("createBufferedTextFileWriter", () => { + it("preserves line boundaries across buffered flushes", async () => { + const tempDir = createTempDir("paperclip-buffered-writer-"); + const outputPath = path.join(tempDir, "backup.sql"); + const writer = createBufferedTextFileWriter(outputPath, 16); + const lines = [ + "-- header", + "BEGIN;", + "", + "INSERT INTO test VALUES (1);", + "-- footer", + ]; + + for (const line of lines) { + writer.emit(line); + } + + await writer.close(); + + expect(fs.readFileSync(outputPath, "utf8")).toBe(lines.join("\n")); + }); +}); + +describeEmbeddedPostgres("runDatabaseBackup", () => { + it( + "backs up and restores large table payloads without materializing one giant string", + async () => { + const sourceConnectionString = await createTempDatabase(); + const restoreConnectionString = await createSiblingDatabase( + sourceConnectionString, + "paperclip_restore_target", + ); + const backupDir = createTempDir("paperclip-db-backup-output-"); + const sourceSql = postgres(sourceConnectionString, { max: 1, onnotice: () => {} }); + const restoreSql = postgres(restoreConnectionString, { max: 1, onnotice: () => {} }); + + try { + await sourceSql.unsafe(` + CREATE TYPE "public"."backup_test_state" AS ENUM ('pending', 'done'); + `); + await sourceSql.unsafe(` + CREATE TABLE "public"."backup_test_records" ( + "id" serial PRIMARY KEY, + "title" text NOT NULL, + "payload" text NOT NULL, + "state" "public"."backup_test_state" NOT NULL, + "metadata" jsonb, + "created_at" timestamptz NOT NULL DEFAULT now() + ); + `); + + const payload = "x".repeat(8192); + for (let index = 0; index < 160; index += 1) { + const createdAt = new Date(Date.UTC(2026, 0, 1, 0, 0, index)); + await sourceSql` + INSERT INTO "public"."backup_test_records" ( + "title", + "payload", + "state", + "metadata", + "created_at" + ) + VALUES ( + ${`row-${index}`}, + ${payload}, + ${index % 2 === 0 ? "pending" : "done"}::"public"."backup_test_state", + ${JSON.stringify({ index, even: index % 2 === 0 })}::jsonb, + ${createdAt} + ) + `; + } + + const result = await runDatabaseBackup({ + connectionString: sourceConnectionString, + backupDir, + retentionDays: 7, + filenamePrefix: "paperclip-test", + }); + + expect(result.backupFile).toMatch(/paperclip-test-.*\.sql$/); + expect(result.sizeBytes).toBeGreaterThan(1024 * 1024); + expect(fs.existsSync(result.backupFile)).toBe(true); + + await runDatabaseRestore({ + connectionString: restoreConnectionString, + backupFile: result.backupFile, + }); + + const counts = await restoreSql.unsafe<{ count: number }[]>(` + SELECT count(*)::int AS count + FROM "public"."backup_test_records" + `); + expect(counts[0]?.count).toBe(160); + + const sampleRows = await restoreSql.unsafe<{ + title: string; + payload: string; + state: string; + metadata: { index: number; even: boolean }; + }[]>(` + SELECT "title", "payload", "state"::text AS "state", "metadata" + FROM "public"."backup_test_records" + WHERE "title" IN ('row-0', 'row-159') + ORDER BY "title" + `); + expect(sampleRows).toEqual([ + { + title: "row-0", + payload, + state: "pending", + metadata: { index: 0, even: true }, + }, + { + title: "row-159", + payload, + state: "done", + metadata: { index: 159, even: false }, + }, + ]); + } finally { + await sourceSql.end(); + await restoreSql.end(); + } + }, + 60_000, + ); + + it( + "restores statements incrementally when backup comments precede the first breakpoint", + async () => { + const restoreConnectionString = await createTempDatabase(); + const restoreSql = postgres(restoreConnectionString, { max: 1, onnotice: () => {} }); + const backupDir = createTempDir("paperclip-db-restore-manual-"); + const backupFile = path.join(backupDir, "manual.sql"); + + try { + await fs.promises.writeFile( + backupFile, + [ + "-- Paperclip database backup", + "-- Created: 2026-04-06T00:00:00.000Z", + "", + "BEGIN;", + "-- paperclip statement breakpoint 69f6f3f1-42fd-46a6-bf17-d1d85f8f3900", + "CREATE TABLE public.restore_stream_test (id integer primary key, payload text not null);", + "-- paperclip statement breakpoint 69f6f3f1-42fd-46a6-bf17-d1d85f8f3900", + "INSERT INTO public.restore_stream_test (id, payload)", + "VALUES (1, 'hello');", + "-- paperclip statement breakpoint 69f6f3f1-42fd-46a6-bf17-d1d85f8f3900", + "COMMIT;", + "-- paperclip statement breakpoint 69f6f3f1-42fd-46a6-bf17-d1d85f8f3900", + ].join("\n"), + "utf8", + ); + + await runDatabaseRestore({ + connectionString: restoreConnectionString, + backupFile, + }); + + const rows = await restoreSql.unsafe<{ payload: string }[]>(` + SELECT payload + FROM public.restore_stream_test + `); + expect(rows).toEqual([{ payload: "hello" }]); + } finally { + await restoreSql.end(); + } + }, + 20_000, + ); +}); diff --git a/packages/db/src/backup-lib.ts b/packages/db/src/backup-lib.ts index 26f918c3d79..ea76a2b6d3c 100644 --- a/packages/db/src/backup-lib.ts +++ b/packages/db/src/backup-lib.ts @@ -1,6 +1,6 @@ -import { existsSync, mkdirSync, readdirSync, statSync, unlinkSync } from "node:fs"; -import { readFile, writeFile } from "node:fs/promises"; +import { createReadStream, createWriteStream, existsSync, mkdirSync, readdirSync, statSync, unlinkSync } from "node:fs"; import { basename, resolve } from "node:path"; +import { createInterface } from "node:readline"; import postgres from "postgres"; export type RunDatabaseBackupOptions = { @@ -45,8 +45,14 @@ type TableDefinition = { tablename: string; }; +type ExtensionDefinition = { + extension_name: string; + schema_name: string; +}; + const DRIZZLE_SCHEMA = "drizzle"; const DRIZZLE_MIGRATIONS_TABLE = "__drizzle_migrations"; +const DEFAULT_BACKUP_WRITE_BUFFER_BYTES = 1024 * 1024; const STATEMENT_BREAKPOINT = "-- paperclip statement breakpoint 69f6f3f1-42fd-46a6-bf17-d1d85f8f3900"; @@ -141,6 +147,138 @@ function tableKey(schemaName: string, tableName: string): string { return `${schemaName}.${tableName}`; } +async function* readRestoreStatements(backupFile: string): AsyncGenerator { + const stream = createReadStream(backupFile, { encoding: "utf8" }); + const reader = createInterface({ + input: stream, + crlfDelay: Infinity, + }); + let statementLines: string[] = []; + + const flushStatement = () => { + const statement = statementLines.join("\n").trim(); + statementLines = []; + return statement; + }; + + try { + for await (const line of reader) { + if (line === STATEMENT_BREAKPOINT) { + const statement = flushStatement(); + if (statement.length > 0) { + yield statement; + } + continue; + } + statementLines.push(line); + } + + const trailingStatement = flushStatement(); + if (trailingStatement.length > 0) { + yield trailingStatement; + } + } finally { + reader.close(); + stream.destroy(); + } +} + +export function createBufferedTextFileWriter(filePath: string, maxBufferedBytes = DEFAULT_BACKUP_WRITE_BUFFER_BYTES) { + const stream = createWriteStream(filePath, { encoding: "utf8" }); + const flushThreshold = Math.max(1, Math.trunc(maxBufferedBytes)); + let bufferedLines: string[] = []; + let bufferedBytes = 0; + let firstChunk = true; + let closed = false; + let streamError: Error | null = null; + let pendingWrite = Promise.resolve(); + + stream.on("error", (error) => { + streamError = error; + }); + + const writeChunk = async (chunk: string): Promise => { + if (streamError) throw streamError; + const canContinue = stream.write(chunk); + if (!canContinue) { + await new Promise((resolve, reject) => { + const handleDrain = () => { + cleanup(); + resolve(); + }; + const handleError = (error: Error) => { + cleanup(); + reject(error); + }; + const cleanup = () => { + stream.off("drain", handleDrain); + stream.off("error", handleError); + }; + stream.once("drain", handleDrain); + stream.once("error", handleError); + }); + } + if (streamError) throw streamError; + }; + + const flushBufferedLines = () => { + if (bufferedLines.length === 0) return; + const linesToWrite = bufferedLines; + bufferedLines = []; + bufferedBytes = 0; + const chunkBody = linesToWrite.join("\n"); + const chunk = firstChunk ? chunkBody : `\n${chunkBody}`; + firstChunk = false; + pendingWrite = pendingWrite.then(() => writeChunk(chunk)); + }; + + return { + emit(line: string) { + if (closed) { + throw new Error(`Cannot write to closed backup file: ${filePath}`); + } + if (streamError) throw streamError; + bufferedLines.push(line); + bufferedBytes += Buffer.byteLength(line, "utf8") + 1; + if (bufferedBytes >= flushThreshold) { + flushBufferedLines(); + } + }, + async close() { + if (closed) return; + closed = true; + flushBufferedLines(); + await pendingWrite; + await new Promise((resolve, reject) => { + if (streamError) { + reject(streamError); + return; + } + stream.end((error?: Error | null) => { + if (error) reject(error); + else resolve(); + }); + }); + if (streamError) throw streamError; + }, + async abort() { + if (closed) return; + closed = true; + bufferedLines = []; + bufferedBytes = 0; + stream.destroy(); + await pendingWrite.catch(() => {}); + if (existsSync(filePath)) { + try { + unlinkSync(filePath); + } catch { + // Preserve the original backup failure if temporary file cleanup also fails. + } + } + }, + }; +} + export async function runDatabaseBackup(opts: RunDatabaseBackupOptions): Promise { const filenamePrefix = opts.filenamePrefix ?? "paperclip"; const retentionDays = Math.max(1, Math.trunc(opts.retentionDays)); @@ -149,12 +287,14 @@ export async function runDatabaseBackup(opts: RunDatabaseBackupOptions): Promise const excludedTableNames = normalizeTableNameSet(opts.excludeTables); const nullifiedColumnsByTable = normalizeNullifyColumnMap(opts.nullifyColumns); const sql = postgres(opts.connectionString, { max: 1, connect_timeout: connectTimeout }); + mkdirSync(opts.backupDir, { recursive: true }); + const backupFile = resolve(opts.backupDir, `${filenamePrefix}-${timestamp()}.sql`); + const writer = createBufferedTextFileWriter(backupFile); try { await sql`SELECT 1`; - const lines: string[] = []; - const emit = (line: string) => lines.push(line); + const emit = (line: string) => writer.emit(line); const emitStatement = (statement: string) => { emit(statement); emit(STATEMENT_BREAKPOINT); @@ -241,6 +381,25 @@ export async function runDatabaseBackup(opts: RunDatabaseBackupOptions): Promise emit(""); } + const extensions = await sql` + SELECT + e.extname AS extension_name, + n.nspname AS schema_name + FROM pg_extension e + JOIN pg_namespace n ON n.oid = e.extnamespace + WHERE e.extname <> 'plpgsql' + ORDER BY e.extname + `; + if (extensions.length > 0) { + emit("-- Extensions"); + for (const extension of extensions) { + emitStatement( + `CREATE EXTENSION IF NOT EXISTS ${quoteIdentifier(extension.extension_name)} WITH SCHEMA ${quoteIdentifier(extension.schema_name)};`, + ); + } + emit(""); + } + if (sequences.length > 0) { emit("-- Sequences"); for (const seq of sequences) { @@ -503,10 +662,7 @@ export async function runDatabaseBackup(opts: RunDatabaseBackupOptions): Promise emitStatement("COMMIT;"); emit(""); - // Write the backup file - mkdirSync(opts.backupDir, { recursive: true }); - const backupFile = resolve(opts.backupDir, `${filenamePrefix}-${timestamp()}.sql`); - await writeFile(backupFile, lines.join("\n"), "utf8"); + await writer.close(); const sizeBytes = statSync(backupFile).size; const prunedCount = pruneOldBackups(opts.backupDir, retentionDays, filenamePrefix); @@ -516,6 +672,9 @@ export async function runDatabaseBackup(opts: RunDatabaseBackupOptions): Promise sizeBytes, prunedCount, }; + } catch (error) { + await writer.abort(); + throw error; } finally { await sql.end(); } @@ -527,13 +686,7 @@ export async function runDatabaseRestore(opts: RunDatabaseRestoreOptions): Promi try { await sql`SELECT 1`; - const contents = await readFile(opts.backupFile, "utf8"); - const statements = contents - .split(STATEMENT_BREAKPOINT) - .map((statement) => statement.trim()) - .filter((statement) => statement.length > 0); - - for (const statement of statements) { + for await (const statement of readRestoreStatements(opts.backupFile)) { await sql.unsafe(statement).execute(); } } catch (error) { diff --git a/packages/db/src/check-migration-numbering.ts b/packages/db/src/check-migration-numbering.ts new file mode 100644 index 00000000000..f1799d4ef19 --- /dev/null +++ b/packages/db/src/check-migration-numbering.ts @@ -0,0 +1,89 @@ +import { readdir, readFile } from "node:fs/promises"; +import { fileURLToPath } from "node:url"; + +const migrationsDir = fileURLToPath(new URL("./migrations", import.meta.url)); +const journalPath = fileURLToPath(new URL("./migrations/meta/_journal.json", import.meta.url)); + +type JournalFile = { + entries?: Array<{ + idx?: number; + tag?: string; + }>; +}; + +function migrationNumber(value: string): string | null { + const match = value.match(/^(\d{4})_/); + return match ? match[1] : null; +} + +function ensureNoDuplicates(values: string[], label: string) { + const seen = new Map(); + + for (const value of values) { + const number = migrationNumber(value); + if (!number) { + throw new Error(`${label} entry does not start with a 4-digit migration number: ${value}`); + } + const existing = seen.get(number); + if (existing) { + throw new Error(`Duplicate migration number ${number} in ${label}: ${existing}, ${value}`); + } + seen.set(number, value); + } +} + +function ensureStrictlyOrdered(values: string[], label: string) { + const sorted = [...values].sort(); + for (let index = 0; index < values.length; index += 1) { + if (values[index] !== sorted[index]) { + throw new Error( + `${label} are out of order at position ${index}: expected ${sorted[index]}, found ${values[index]}`, + ); + } + } +} + +function ensureJournalMatchesFiles(migrationFiles: string[], journalTags: string[]) { + const journalFiles = journalTags.map((tag) => `${tag}.sql`); + + if (journalFiles.length !== migrationFiles.length) { + throw new Error( + `Migration journal/file count mismatch: journal has ${journalFiles.length}, files have ${migrationFiles.length}`, + ); + } + + for (let index = 0; index < migrationFiles.length; index += 1) { + const migrationFile = migrationFiles[index]; + const journalFile = journalFiles[index]; + if (migrationFile !== journalFile) { + throw new Error( + `Migration journal/file order mismatch at position ${index}: journal has ${journalFile}, files have ${migrationFile}`, + ); + } + } +} + +async function main() { + const migrationFiles = (await readdir(migrationsDir)) + .filter((entry) => entry.endsWith(".sql")) + .sort(); + + ensureNoDuplicates(migrationFiles, "migration files"); + ensureStrictlyOrdered(migrationFiles, "migration files"); + + const rawJournal = await readFile(journalPath, "utf8"); + const journal = JSON.parse(rawJournal) as JournalFile; + const journalTags = (journal.entries ?? []) + .map((entry, index) => { + if (typeof entry.tag !== "string" || entry.tag.length === 0) { + throw new Error(`Migration journal entry ${index} is missing a tag`); + } + return entry.tag; + }); + + ensureNoDuplicates(journalTags, "migration journal"); + ensureStrictlyOrdered(journalTags, "migration journal"); + ensureJournalMatchesFiles(migrationFiles, journalTags); +} + +await main(); diff --git a/packages/db/src/client.test.ts b/packages/db/src/client.test.ts index 622130ac977..81cc2acef9d 100644 --- a/packages/db/src/client.test.ts +++ b/packages/db/src/client.test.ts @@ -169,4 +169,302 @@ describeEmbeddedPostgres("applyPendingMigrations", () => { }, 20_000, ); + + it( + "replays migration 0046 safely when document revision columns already exist", + async () => { + const connectionString = await createTempDatabase(); + + await applyPendingMigrations(connectionString); + + const sql = postgres(connectionString, { max: 1, onnotice: () => {} }); + try { + const smoothSentinelsHash = await migrationHash("0046_smooth_sentinels.sql"); + + await sql.unsafe( + `DELETE FROM "drizzle"."__drizzle_migrations" WHERE hash = '${smoothSentinelsHash}'`, + ); + + const columns = await sql.unsafe<{ column_name: string; is_nullable: string; column_default: string | null }[]>( + ` + SELECT column_name, is_nullable, column_default + FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'document_revisions' + AND column_name IN ('title', 'format') + ORDER BY column_name + `, + ); + expect(columns).toHaveLength(2); + } finally { + await sql.end(); + } + + const pendingState = await inspectMigrations(connectionString); + expect(pendingState).toMatchObject({ + status: "needsMigrations", + pendingMigrations: ["0046_smooth_sentinels.sql"], + reason: "pending-migrations", + }); + + await applyPendingMigrations(connectionString); + + const finalState = await inspectMigrations(connectionString); + expect(finalState.status).toBe("upToDate"); + + const verifySql = postgres(connectionString, { max: 1, onnotice: () => {} }); + try { + const columns = await verifySql.unsafe<{ column_name: string; is_nullable: string; column_default: string | null }[]>( + ` + SELECT column_name, is_nullable, column_default + FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'document_revisions' + AND column_name IN ('title', 'format') + ORDER BY column_name + `, + ); + expect(columns).toEqual([ + expect.objectContaining({ + column_name: "format", + is_nullable: "NO", + }), + expect.objectContaining({ + column_name: "title", + is_nullable: "YES", + }), + ]); + expect(columns[0]?.column_default).toContain("'markdown'"); + } finally { + await verifySql.end(); + } + }, + 20_000, + ); + + it( + "replays migration 0047 safely when feedback tables and run columns already exist", + async () => { + const connectionString = await createTempDatabase(); + + await applyPendingMigrations(connectionString); + + const sql = postgres(connectionString, { max: 1, onnotice: () => {} }); + try { + const overjoyedGrootHash = await migrationHash("0047_overjoyed_groot.sql"); + + await sql.unsafe( + `DELETE FROM "drizzle"."__drizzle_migrations" WHERE hash = '${overjoyedGrootHash}'`, + ); + + const tables = await sql.unsafe<{ table_name: string }[]>( + ` + SELECT table_name + FROM information_schema.tables + WHERE table_schema = 'public' + AND table_name IN ('feedback_exports', 'feedback_votes') + ORDER BY table_name + `, + ); + expect(tables.map((row) => row.table_name)).toEqual([ + "feedback_exports", + "feedback_votes", + ]); + + const columns = await sql.unsafe<{ table_name: string; column_name: string }[]>( + ` + SELECT table_name, column_name + FROM information_schema.columns + WHERE table_schema = 'public' + AND ( + (table_name = 'companies' AND column_name IN ( + 'feedback_data_sharing_enabled', + 'feedback_data_sharing_consent_at', + 'feedback_data_sharing_consent_by_user_id', + 'feedback_data_sharing_terms_version' + )) + OR (table_name = 'document_revisions' AND column_name = 'created_by_run_id') + OR (table_name = 'issue_comments' AND column_name = 'created_by_run_id') + ) + ORDER BY table_name, column_name + `, + ); + expect(columns).toHaveLength(6); + } finally { + await sql.end(); + } + + const pendingState = await inspectMigrations(connectionString); + expect(pendingState).toMatchObject({ + status: "needsMigrations", + pendingMigrations: ["0047_overjoyed_groot.sql"], + reason: "pending-migrations", + }); + + await applyPendingMigrations(connectionString); + + const finalState = await inspectMigrations(connectionString); + expect(finalState.status).toBe("upToDate"); + + const verifySql = postgres(connectionString, { max: 1, onnotice: () => {} }); + try { + const constraints = await verifySql.unsafe<{ conname: string }[]>( + ` + SELECT conname + FROM pg_constraint + WHERE conname IN ( + 'feedback_exports_company_id_companies_id_fk', + 'feedback_exports_feedback_vote_id_feedback_votes_id_fk', + 'feedback_exports_issue_id_issues_id_fk', + 'feedback_votes_company_id_companies_id_fk', + 'feedback_votes_issue_id_issues_id_fk' + ) + ORDER BY conname + `, + ); + expect(constraints.map((row) => row.conname)).toEqual([ + "feedback_exports_company_id_companies_id_fk", + "feedback_exports_feedback_vote_id_feedback_votes_id_fk", + "feedback_exports_issue_id_issues_id_fk", + "feedback_votes_company_id_companies_id_fk", + "feedback_votes_issue_id_issues_id_fk", + ]); + } finally { + await verifySql.end(); + } + }, + 20_000, + ); + + it( + "replays migration 0048 safely when routines.variables already exists", + async () => { + const connectionString = await createTempDatabase(); + + await applyPendingMigrations(connectionString); + + const sql = postgres(connectionString, { max: 1, onnotice: () => {} }); + try { + const flashyMarrowHash = await migrationHash("0048_flashy_marrow.sql"); + + await sql.unsafe( + `DELETE FROM "drizzle"."__drizzle_migrations" WHERE hash = '${flashyMarrowHash}'`, + ); + + const columns = await sql.unsafe<{ column_name: string }[]>( + ` + SELECT column_name + FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'routines' + AND column_name = 'variables' + `, + ); + expect(columns).toHaveLength(1); + } finally { + await sql.end(); + } + + const pendingState = await inspectMigrations(connectionString); + expect(pendingState).toMatchObject({ + status: "needsMigrations", + pendingMigrations: ["0048_flashy_marrow.sql"], + reason: "pending-migrations", + }); + + await applyPendingMigrations(connectionString); + + const finalState = await inspectMigrations(connectionString); + expect(finalState.status).toBe("upToDate"); + + const verifySql = postgres(connectionString, { max: 1, onnotice: () => {} }); + try { + const columns = await verifySql.unsafe<{ column_name: string; is_nullable: string; data_type: string }[]>( + ` + SELECT column_name, is_nullable, data_type + FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'routines' + AND column_name = 'variables' + `, + ); + expect(columns).toEqual([ + expect.objectContaining({ + column_name: "variables", + is_nullable: "NO", + data_type: "jsonb", + }), + ]); + } finally { + await verifySql.end(); + } + }, + 20_000, + ); + + it( + "replays migration 0050 safely when projects.env already exists", + async () => { + const connectionString = await createTempDatabase(); + + await applyPendingMigrations(connectionString); + + const sql = postgres(connectionString, { max: 1, onnotice: () => {} }); + try { + const stiffLuckmanHash = await migrationHash("0050_stiff_luckman.sql"); + + await sql.unsafe( + `DELETE FROM "drizzle"."__drizzle_migrations" WHERE hash = '${stiffLuckmanHash}'`, + ); + + const columns = await sql.unsafe<{ column_name: string }[]>( + ` + SELECT column_name + FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'projects' + AND column_name = 'env' + `, + ); + expect(columns).toHaveLength(1); + } finally { + await sql.end(); + } + + const pendingState = await inspectMigrations(connectionString); + expect(pendingState).toMatchObject({ + status: "needsMigrations", + pendingMigrations: ["0050_stiff_luckman.sql"], + reason: "pending-migrations", + }); + + await applyPendingMigrations(connectionString); + + const finalState = await inspectMigrations(connectionString); + expect(finalState.status).toBe("upToDate"); + + const verifySql = postgres(connectionString, { max: 1, onnotice: () => {} }); + try { + const columns = await verifySql.unsafe<{ column_name: string; is_nullable: string; data_type: string }[]>( + ` + SELECT column_name, is_nullable, data_type + FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'projects' + AND column_name = 'env' + `, + ); + expect(columns).toEqual([ + expect.objectContaining({ + column_name: "env", + is_nullable: "YES", + data_type: "jsonb", + }), + ]); + } finally { + await verifySql.end(); + } + }, + 20_000, + ); }); diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts index 6c45acbc43d..cf4a26335fd 100644 --- a/packages/db/src/index.ts +++ b/packages/db/src/index.ts @@ -29,4 +29,5 @@ export { createEmbeddedPostgresLogBuffer, formatEmbeddedPostgresError, } from "./embedded-postgres-error.js"; +export { issueRelations } from "./schema/issue_relations.js"; export * from "./schema/index.js"; diff --git a/packages/db/src/migrations/0046_smooth_sentinels.sql b/packages/db/src/migrations/0046_smooth_sentinels.sql new file mode 100644 index 00000000000..8ce9da440aa --- /dev/null +++ b/packages/db/src/migrations/0046_smooth_sentinels.sql @@ -0,0 +1,11 @@ +ALTER TABLE "document_revisions" ADD COLUMN IF NOT EXISTS "title" text;--> statement-breakpoint +ALTER TABLE "document_revisions" ADD COLUMN IF NOT EXISTS "format" text;--> statement-breakpoint +ALTER TABLE "document_revisions" ALTER COLUMN "format" SET DEFAULT 'markdown'; +--> statement-breakpoint +UPDATE "document_revisions" AS "dr" +SET + "title" = COALESCE("dr"."title", "d"."title"), + "format" = COALESCE("dr"."format", "d"."format", 'markdown') +FROM "documents" AS "d" +WHERE "d"."id" = "dr"."document_id";--> statement-breakpoint +ALTER TABLE "document_revisions" ALTER COLUMN "format" SET NOT NULL; diff --git a/packages/db/src/migrations/0047_overjoyed_groot.sql b/packages/db/src/migrations/0047_overjoyed_groot.sql new file mode 100644 index 00000000000..346b0ee921f --- /dev/null +++ b/packages/db/src/migrations/0047_overjoyed_groot.sql @@ -0,0 +1,102 @@ +CREATE TABLE IF NOT EXISTS "feedback_exports" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "company_id" uuid NOT NULL, + "feedback_vote_id" uuid NOT NULL, + "issue_id" uuid NOT NULL, + "project_id" uuid, + "author_user_id" text NOT NULL, + "target_type" text NOT NULL, + "target_id" text NOT NULL, + "vote" text NOT NULL, + "status" text DEFAULT 'local_only' NOT NULL, + "destination" text, + "export_id" text, + "consent_version" text, + "schema_version" text DEFAULT 'paperclip-feedback-envelope-v2' NOT NULL, + "bundle_version" text DEFAULT 'paperclip-feedback-bundle-v2' NOT NULL, + "payload_version" text DEFAULT 'paperclip-feedback-v1' NOT NULL, + "payload_digest" text, + "payload_snapshot" jsonb, + "target_summary" jsonb NOT NULL, + "redaction_summary" jsonb, + "attempt_count" integer DEFAULT 0 NOT NULL, + "last_attempted_at" timestamp with time zone, + "exported_at" timestamp with time zone, + "failure_reason" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "feedback_votes" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "company_id" uuid NOT NULL, + "issue_id" uuid NOT NULL, + "target_type" text NOT NULL, + "target_id" text NOT NULL, + "author_user_id" text NOT NULL, + "vote" text NOT NULL, + "reason" text, + "shared_with_labs" boolean DEFAULT false NOT NULL, + "shared_at" timestamp with time zone, + "consent_version" text, + "redaction_summary" jsonb, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "companies" ADD COLUMN IF NOT EXISTS "feedback_data_sharing_enabled" boolean DEFAULT false NOT NULL;--> statement-breakpoint +ALTER TABLE "companies" ADD COLUMN IF NOT EXISTS "feedback_data_sharing_consent_at" timestamp with time zone;--> statement-breakpoint +ALTER TABLE "companies" ADD COLUMN IF NOT EXISTS "feedback_data_sharing_consent_by_user_id" text;--> statement-breakpoint +ALTER TABLE "companies" ADD COLUMN IF NOT EXISTS "feedback_data_sharing_terms_version" text;--> statement-breakpoint +ALTER TABLE "document_revisions" ADD COLUMN IF NOT EXISTS "created_by_run_id" uuid;--> statement-breakpoint +ALTER TABLE "issue_comments" ADD COLUMN IF NOT EXISTS "created_by_run_id" uuid;--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'feedback_exports_company_id_companies_id_fk') THEN + ALTER TABLE "feedback_exports" ADD CONSTRAINT "feedback_exports_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE no action ON UPDATE no action; + END IF; +END $$;--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'feedback_exports_feedback_vote_id_feedback_votes_id_fk') THEN + ALTER TABLE "feedback_exports" ADD CONSTRAINT "feedback_exports_feedback_vote_id_feedback_votes_id_fk" FOREIGN KEY ("feedback_vote_id") REFERENCES "public"."feedback_votes"("id") ON DELETE cascade ON UPDATE no action; + END IF; +END $$;--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'feedback_exports_issue_id_issues_id_fk') THEN + ALTER TABLE "feedback_exports" ADD CONSTRAINT "feedback_exports_issue_id_issues_id_fk" FOREIGN KEY ("issue_id") REFERENCES "public"."issues"("id") ON DELETE cascade ON UPDATE no action; + END IF; +END $$;--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'feedback_exports_project_id_projects_id_fk') THEN + ALTER TABLE "feedback_exports" ADD CONSTRAINT "feedback_exports_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE set null ON UPDATE no action; + END IF; +END $$;--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'feedback_votes_company_id_companies_id_fk') THEN + ALTER TABLE "feedback_votes" ADD CONSTRAINT "feedback_votes_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE no action ON UPDATE no action; + END IF; +END $$;--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'feedback_votes_issue_id_issues_id_fk') THEN + ALTER TABLE "feedback_votes" ADD CONSTRAINT "feedback_votes_issue_id_issues_id_fk" FOREIGN KEY ("issue_id") REFERENCES "public"."issues"("id") ON DELETE no action ON UPDATE no action; + END IF; +END $$;--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS "feedback_exports_feedback_vote_idx" ON "feedback_exports" USING btree ("feedback_vote_id");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "feedback_exports_company_created_idx" ON "feedback_exports" USING btree ("company_id","created_at");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "feedback_exports_company_status_idx" ON "feedback_exports" USING btree ("company_id","status","created_at");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "feedback_exports_company_issue_idx" ON "feedback_exports" USING btree ("company_id","issue_id","created_at");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "feedback_exports_company_project_idx" ON "feedback_exports" USING btree ("company_id","project_id","created_at");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "feedback_exports_company_author_idx" ON "feedback_exports" USING btree ("company_id","author_user_id","created_at");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "feedback_votes_company_issue_idx" ON "feedback_votes" USING btree ("company_id","issue_id");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "feedback_votes_issue_target_idx" ON "feedback_votes" USING btree ("issue_id","target_type","target_id");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "feedback_votes_author_idx" ON "feedback_votes" USING btree ("author_user_id","created_at");--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS "feedback_votes_company_target_author_idx" ON "feedback_votes" USING btree ("company_id","target_type","target_id","author_user_id");--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'document_revisions_created_by_run_id_heartbeat_runs_id_fk') THEN + ALTER TABLE "document_revisions" ADD CONSTRAINT "document_revisions_created_by_run_id_heartbeat_runs_id_fk" FOREIGN KEY ("created_by_run_id") REFERENCES "public"."heartbeat_runs"("id") ON DELETE set null ON UPDATE no action; + END IF; +END $$;--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'issue_comments_created_by_run_id_heartbeat_runs_id_fk') THEN + ALTER TABLE "issue_comments" ADD CONSTRAINT "issue_comments_created_by_run_id_heartbeat_runs_id_fk" FOREIGN KEY ("created_by_run_id") REFERENCES "public"."heartbeat_runs"("id") ON DELETE set null ON UPDATE no action; + END IF; +END $$; diff --git a/packages/db/src/migrations/0048_flashy_marrow.sql b/packages/db/src/migrations/0048_flashy_marrow.sql new file mode 100644 index 00000000000..be81d8a35e3 --- /dev/null +++ b/packages/db/src/migrations/0048_flashy_marrow.sql @@ -0,0 +1 @@ +ALTER TABLE "routines" ADD COLUMN IF NOT EXISTS "variables" jsonb DEFAULT '[]'::jsonb NOT NULL; diff --git a/packages/db/src/migrations/0049_flawless_abomination.sql b/packages/db/src/migrations/0049_flawless_abomination.sql new file mode 100644 index 00000000000..a48c468e3ef --- /dev/null +++ b/packages/db/src/migrations/0049_flawless_abomination.sql @@ -0,0 +1,21 @@ +CREATE TABLE "issue_relations" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "company_id" uuid NOT NULL, + "issue_id" uuid NOT NULL, + "related_issue_id" uuid NOT NULL, + "type" text NOT NULL, + "created_by_agent_id" uuid, + "created_by_user_id" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "issue_relations" ADD CONSTRAINT "issue_relations_type_check" CHECK ("type" IN ('blocks'));--> statement-breakpoint +ALTER TABLE "issue_relations" ADD CONSTRAINT "issue_relations_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "issue_relations" ADD CONSTRAINT "issue_relations_issue_id_issues_id_fk" FOREIGN KEY ("issue_id") REFERENCES "public"."issues"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "issue_relations" ADD CONSTRAINT "issue_relations_related_issue_id_issues_id_fk" FOREIGN KEY ("related_issue_id") REFERENCES "public"."issues"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "issue_relations" ADD CONSTRAINT "issue_relations_created_by_agent_id_agents_id_fk" FOREIGN KEY ("created_by_agent_id") REFERENCES "public"."agents"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "issue_relations_company_issue_idx" ON "issue_relations" USING btree ("company_id","issue_id");--> statement-breakpoint +CREATE INDEX "issue_relations_company_related_issue_idx" ON "issue_relations" USING btree ("company_id","related_issue_id");--> statement-breakpoint +CREATE INDEX "issue_relations_company_type_idx" ON "issue_relations" USING btree ("company_id","type");--> statement-breakpoint +CREATE UNIQUE INDEX "issue_relations_company_edge_uq" ON "issue_relations" USING btree ("company_id","issue_id","related_issue_id","type"); diff --git a/packages/db/src/migrations/0050_stiff_luckman.sql b/packages/db/src/migrations/0050_stiff_luckman.sql new file mode 100644 index 00000000000..25b0741f4ea --- /dev/null +++ b/packages/db/src/migrations/0050_stiff_luckman.sql @@ -0,0 +1 @@ +ALTER TABLE "projects" ADD COLUMN IF NOT EXISTS "env" jsonb; diff --git a/packages/db/src/migrations/0051_young_korg.sql b/packages/db/src/migrations/0051_young_korg.sql new file mode 100644 index 00000000000..decc5b3f32a --- /dev/null +++ b/packages/db/src/migrations/0051_young_korg.sql @@ -0,0 +1,5 @@ +CREATE EXTENSION IF NOT EXISTS pg_trgm;--> statement-breakpoint +CREATE INDEX "issue_comments_body_search_idx" ON "issue_comments" USING gin ("body" gin_trgm_ops);--> statement-breakpoint +CREATE INDEX "issues_title_search_idx" ON "issues" USING gin ("title" gin_trgm_ops);--> statement-breakpoint +CREATE INDEX "issues_identifier_search_idx" ON "issues" USING gin ("identifier" gin_trgm_ops);--> statement-breakpoint +CREATE INDEX "issues_description_search_idx" ON "issues" USING gin ("description" gin_trgm_ops); diff --git a/packages/db/src/migrations/0052_mushy_trauma.sql b/packages/db/src/migrations/0052_mushy_trauma.sql new file mode 100644 index 00000000000..95426dbd200 --- /dev/null +++ b/packages/db/src/migrations/0052_mushy_trauma.sql @@ -0,0 +1,26 @@ +CREATE TABLE "issue_execution_decisions" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "company_id" uuid NOT NULL, + "issue_id" uuid NOT NULL, + "stage_id" uuid NOT NULL, + "stage_type" text NOT NULL, + "actor_agent_id" uuid, + "actor_user_id" text, + "outcome" text NOT NULL, + "body" text NOT NULL, + "created_by_run_id" uuid, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "heartbeat_runs" ADD COLUMN "issue_comment_status" text DEFAULT 'not_applicable' NOT NULL;--> statement-breakpoint +ALTER TABLE "heartbeat_runs" ADD COLUMN "issue_comment_satisfied_by_comment_id" uuid;--> statement-breakpoint +ALTER TABLE "heartbeat_runs" ADD COLUMN "issue_comment_retry_queued_at" timestamp with time zone;--> statement-breakpoint +ALTER TABLE "issues" ADD COLUMN "execution_policy" jsonb;--> statement-breakpoint +ALTER TABLE "issues" ADD COLUMN "execution_state" jsonb;--> statement-breakpoint +ALTER TABLE "issue_execution_decisions" ADD CONSTRAINT "issue_execution_decisions_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "issue_execution_decisions" ADD CONSTRAINT "issue_execution_decisions_issue_id_issues_id_fk" FOREIGN KEY ("issue_id") REFERENCES "public"."issues"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "issue_execution_decisions" ADD CONSTRAINT "issue_execution_decisions_actor_agent_id_agents_id_fk" FOREIGN KEY ("actor_agent_id") REFERENCES "public"."agents"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "issue_execution_decisions" ADD CONSTRAINT "issue_execution_decisions_created_by_run_id_heartbeat_runs_id_fk" FOREIGN KEY ("created_by_run_id") REFERENCES "public"."heartbeat_runs"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "issue_execution_decisions_company_issue_idx" ON "issue_execution_decisions" USING btree ("company_id","issue_id");--> statement-breakpoint +CREATE INDEX "issue_execution_decisions_stage_idx" ON "issue_execution_decisions" USING btree ("issue_id","stage_id","created_at"); \ No newline at end of file diff --git a/packages/db/src/migrations/0053_sharp_wild_child.sql b/packages/db/src/migrations/0053_sharp_wild_child.sql new file mode 100644 index 00000000000..85fdbd9c86e --- /dev/null +++ b/packages/db/src/migrations/0053_sharp_wild_child.sql @@ -0,0 +1,18 @@ +CREATE TABLE IF NOT EXISTS "inbox_dismissals" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "company_id" uuid NOT NULL, + "user_id" text NOT NULL, + "item_key" text NOT NULL, + "dismissed_at" timestamp with time zone DEFAULT now() NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "inbox_dismissals" ADD CONSTRAINT "inbox_dismissals_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$;--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "inbox_dismissals_company_user_idx" ON "inbox_dismissals" USING btree ("company_id","user_id");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "inbox_dismissals_company_item_idx" ON "inbox_dismissals" USING btree ("company_id","item_key");--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS "inbox_dismissals_company_user_item_idx" ON "inbox_dismissals" USING btree ("company_id","user_id","item_key"); diff --git a/packages/db/src/migrations/0054_draft_routines.sql b/packages/db/src/migrations/0054_draft_routines.sql new file mode 100644 index 00000000000..3169f86a222 --- /dev/null +++ b/packages/db/src/migrations/0054_draft_routines.sql @@ -0,0 +1,2 @@ +ALTER TABLE "routines" ALTER COLUMN "project_id" DROP NOT NULL; +ALTER TABLE "routines" ALTER COLUMN "assignee_agent_id" DROP NOT NULL; diff --git a/packages/db/src/migrations/meta/0046_snapshot.json b/packages/db/src/migrations/meta/0046_snapshot.json new file mode 100644 index 00000000000..f8c1a504886 --- /dev/null +++ b/packages/db/src/migrations/meta/0046_snapshot.json @@ -0,0 +1,11870 @@ +{ + "id": "4ae31a44-1b98-4437-88ef-92b76d014107", + "prevId": "869b0102-2cb8-48e8-a6d8-cab88f0fa7a8", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.activity_log": { + "name": "activity_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "activity_log_company_created_idx": { + "name": "activity_log_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_run_id_idx": { + "name": "activity_log_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_entity_type_id_idx": { + "name": "activity_log_entity_type_id_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "activity_log_company_id_companies_id_fk": { + "name": "activity_log_company_id_companies_id_fk", + "tableFrom": "activity_log", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_agent_id_agents_id_fk": { + "name": "activity_log_agent_id_agents_id_fk", + "tableFrom": "activity_log", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_run_id_heartbeat_runs_id_fk": { + "name": "activity_log_run_id_heartbeat_runs_id_fk", + "tableFrom": "activity_log", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_api_keys": { + "name": "agent_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_api_keys_key_hash_idx": { + "name": "agent_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_api_keys_company_agent_idx": { + "name": "agent_api_keys_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_api_keys_agent_id_agents_id_fk": { + "name": "agent_api_keys_agent_id_agents_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_api_keys_company_id_companies_id_fk": { + "name": "agent_api_keys_company_id_companies_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_config_revisions": { + "name": "agent_config_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'patch'" + }, + "rolled_back_from_revision_id": { + "name": "rolled_back_from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "changed_keys": { + "name": "changed_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "before_config": { + "name": "before_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "after_config": { + "name": "after_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_config_revisions_company_agent_created_idx": { + "name": "agent_config_revisions_company_agent_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_config_revisions_agent_created_idx": { + "name": "agent_config_revisions_agent_created_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_config_revisions_company_id_companies_id_fk": { + "name": "agent_config_revisions_company_id_companies_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_config_revisions_agent_id_agents_id_fk": { + "name": "agent_config_revisions_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_config_revisions_created_by_agent_id_agents_id_fk": { + "name": "agent_config_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_runtime_state": { + "name": "agent_runtime_state", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_json": { + "name": "state_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_run_status": { + "name": "last_run_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total_input_tokens": { + "name": "total_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_output_tokens": { + "name": "total_output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cached_input_tokens": { + "name": "total_cached_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost_cents": { + "name": "total_cost_cents", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_runtime_state_company_agent_idx": { + "name": "agent_runtime_state_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_runtime_state_company_updated_idx": { + "name": "agent_runtime_state_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_runtime_state_agent_id_agents_id_fk": { + "name": "agent_runtime_state_agent_id_agents_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_runtime_state_company_id_companies_id_fk": { + "name": "agent_runtime_state_company_id_companies_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_task_sessions": { + "name": "agent_task_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "task_key": { + "name": "task_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_params_json": { + "name": "session_params_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_display_id": { + "name": "session_display_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_task_sessions_company_agent_adapter_task_uniq": { + "name": "agent_task_sessions_company_agent_adapter_task_uniq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "adapter_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_agent_updated_idx": { + "name": "agent_task_sessions_company_agent_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_task_updated_idx": { + "name": "agent_task_sessions_company_task_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_task_sessions_company_id_companies_id_fk": { + "name": "agent_task_sessions_company_id_companies_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_agent_id_agents_id_fk": { + "name": "agent_task_sessions_agent_id_agents_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_last_run_id_heartbeat_runs_id_fk": { + "name": "agent_task_sessions_last_run_id_heartbeat_runs_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "last_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_wakeup_requests": { + "name": "agent_wakeup_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "coalesced_count": { + "name": "coalesced_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "requested_by_actor_type": { + "name": "requested_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by_actor_id": { + "name": "requested_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_wakeup_requests_company_agent_status_idx": { + "name": "agent_wakeup_requests_company_agent_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_company_requested_idx": { + "name": "agent_wakeup_requests_company_requested_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_agent_requested_idx": { + "name": "agent_wakeup_requests_agent_requested_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_wakeup_requests_company_id_companies_id_fk": { + "name": "agent_wakeup_requests_company_id_companies_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_wakeup_requests_agent_id_agents_id_fk": { + "name": "agent_wakeup_requests_agent_id_agents_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "reports_to": { + "name": "reports_to", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'process'" + }, + "adapter_config": { + "name": "adapter_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "runtime_config": { + "name": "runtime_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_heartbeat_at": { + "name": "last_heartbeat_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agents_company_status_idx": { + "name": "agents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_company_reports_to_idx": { + "name": "agents_company_reports_to_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reports_to", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agents_company_id_companies_id_fk": { + "name": "agents_company_id_companies_id_fk", + "tableFrom": "agents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agents_reports_to_agents_id_fk": { + "name": "agents_reports_to_agents_id_fk", + "tableFrom": "agents", + "tableTo": "agents", + "columnsFrom": [ + "reports_to" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approval_comments": { + "name": "approval_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approval_comments_company_idx": { + "name": "approval_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_idx": { + "name": "approval_comments_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_created_idx": { + "name": "approval_comments_approval_created_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approval_comments_company_id_companies_id_fk": { + "name": "approval_comments_company_id_companies_id_fk", + "tableFrom": "approval_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_approval_id_approvals_id_fk": { + "name": "approval_comments_approval_id_approvals_id_fk", + "tableFrom": "approval_comments", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_author_agent_id_agents_id_fk": { + "name": "approval_comments_author_agent_id_agents_id_fk", + "tableFrom": "approval_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approvals": { + "name": "approvals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_by_agent_id": { + "name": "requested_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_by_user_id": { + "name": "requested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "decision_note": { + "name": "decision_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approvals_company_status_type_idx": { + "name": "approvals_company_status_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approvals_company_id_companies_id_fk": { + "name": "approvals_company_id_companies_id_fk", + "tableFrom": "approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approvals_requested_by_agent_id_agents_id_fk": { + "name": "approvals_requested_by_agent_id_agents_id_fk", + "tableFrom": "approvals", + "tableTo": "agents", + "columnsFrom": [ + "requested_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.assets": { + "name": "assets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "object_key": { + "name": "object_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "original_filename": { + "name": "original_filename", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "assets_company_created_idx": { + "name": "assets_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_provider_idx": { + "name": "assets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_object_key_uq": { + "name": "assets_company_object_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "assets_company_id_companies_id_fk": { + "name": "assets_company_id_companies_id_fk", + "tableFrom": "assets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "assets_created_by_agent_id_agents_id_fk": { + "name": "assets_created_by_agent_id_agents_id_fk", + "tableFrom": "assets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.board_api_keys": { + "name": "board_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "board_api_keys_key_hash_idx": { + "name": "board_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "board_api_keys_user_idx": { + "name": "board_api_keys_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "board_api_keys_user_id_user_id_fk": { + "name": "board_api_keys_user_id_user_id_fk", + "tableFrom": "board_api_keys", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_incidents": { + "name": "budget_incidents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "policy_id": { + "name": "policy_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_end": { + "name": "window_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "threshold_type": { + "name": "threshold_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_limit": { + "name": "amount_limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "amount_observed": { + "name": "amount_observed", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_incidents_company_status_idx": { + "name": "budget_incidents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_company_scope_idx": { + "name": "budget_incidents_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_policy_window_threshold_idx": { + "name": "budget_incidents_policy_window_threshold_idx", + "columns": [ + { + "expression": "policy_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "threshold_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"budget_incidents\".\"status\" <> 'dismissed'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_incidents_company_id_companies_id_fk": { + "name": "budget_incidents_company_id_companies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_policy_id_budget_policies_id_fk": { + "name": "budget_incidents_policy_id_budget_policies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "budget_policies", + "columnsFrom": [ + "policy_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_approval_id_approvals_id_fk": { + "name": "budget_incidents_approval_id_approvals_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_policies": { + "name": "budget_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'billed_cents'" + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "warn_percent": { + "name": "warn_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 80 + }, + "hard_stop_enabled": { + "name": "hard_stop_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "notify_enabled": { + "name": "notify_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_policies_company_scope_active_idx": { + "name": "budget_policies_company_scope_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_window_idx": { + "name": "budget_policies_company_window_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_scope_metric_unique_idx": { + "name": "budget_policies_company_scope_metric_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_policies_company_id_companies_id_fk": { + "name": "budget_policies_company_id_companies_id_fk", + "tableFrom": "budget_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cli_auth_challenges": { + "name": "cli_auth_challenges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_hash": { + "name": "secret_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_access": { + "name": "requested_access", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'board'" + }, + "requested_company_id": { + "name": "requested_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "pending_key_hash": { + "name": "pending_key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pending_key_name": { + "name": "pending_key_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "board_api_key_id": { + "name": "board_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cli_auth_challenges_secret_hash_idx": { + "name": "cli_auth_challenges_secret_hash_idx", + "columns": [ + { + "expression": "secret_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_approved_by_idx": { + "name": "cli_auth_challenges_approved_by_idx", + "columns": [ + { + "expression": "approved_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_requested_company_idx": { + "name": "cli_auth_challenges_requested_company_idx", + "columns": [ + { + "expression": "requested_company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cli_auth_challenges_requested_company_id_companies_id_fk": { + "name": "cli_auth_challenges_requested_company_id_companies_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "companies", + "columnsFrom": [ + "requested_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_approved_by_user_id_user_id_fk": { + "name": "cli_auth_challenges_approved_by_user_id_user_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "user", + "columnsFrom": [ + "approved_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk": { + "name": "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "board_api_keys", + "columnsFrom": [ + "board_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.companies": { + "name": "companies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "issue_prefix": { + "name": "issue_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'PAP'" + }, + "issue_counter": { + "name": "issue_counter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "require_board_approval_for_new_agents": { + "name": "require_board_approval_for_new_agents", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "brand_color": { + "name": "brand_color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "companies_issue_prefix_idx": { + "name": "companies_issue_prefix_idx", + "columns": [ + { + "expression": "issue_prefix", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_logos": { + "name": "company_logos", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_logos_company_uq": { + "name": "company_logos_company_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_logos_asset_uq": { + "name": "company_logos_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_logos_company_id_companies_id_fk": { + "name": "company_logos_company_id_companies_id_fk", + "tableFrom": "company_logos", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_logos_asset_id_assets_id_fk": { + "name": "company_logos_asset_id_assets_id_fk", + "tableFrom": "company_logos", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_memberships": { + "name": "company_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "membership_role": { + "name": "membership_role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_memberships_company_principal_unique_idx": { + "name": "company_memberships_company_principal_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_principal_status_idx": { + "name": "company_memberships_principal_status_idx", + "columns": [ + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_company_status_idx": { + "name": "company_memberships_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_memberships_company_id_companies_id_fk": { + "name": "company_memberships_company_id_companies_id_fk", + "tableFrom": "company_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_versions": { + "name": "company_secret_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "material": { + "name": "material", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "value_sha256": { + "name": "value_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "company_secret_versions_secret_idx": { + "name": "company_secret_versions_secret_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_value_sha256_idx": { + "name": "company_secret_versions_value_sha256_idx", + "columns": [ + { + "expression": "value_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_secret_version_uq": { + "name": "company_secret_versions_secret_version_uq", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_versions_secret_id_company_secrets_id_fk": { + "name": "company_secret_versions_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_versions_created_by_agent_id_agents_id_fk": { + "name": "company_secret_versions_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secrets": { + "name": "company_secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_encrypted'" + }, + "external_ref": { + "name": "external_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "latest_version": { + "name": "latest_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secrets_company_idx": { + "name": "company_secrets_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_provider_idx": { + "name": "company_secrets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_name_uq": { + "name": "company_secrets_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secrets_company_id_companies_id_fk": { + "name": "company_secrets_company_id_companies_id_fk", + "tableFrom": "company_secrets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secrets_created_by_agent_id_agents_id_fk": { + "name": "company_secrets_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secrets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skills": { + "name": "company_skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "markdown": { + "name": "markdown", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "source_locator": { + "name": "source_locator", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_ref": { + "name": "source_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trust_level": { + "name": "trust_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown_only'" + }, + "compatibility": { + "name": "compatibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'compatible'" + }, + "file_inventory": { + "name": "file_inventory", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skills_company_key_idx": { + "name": "company_skills_company_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_name_idx": { + "name": "company_skills_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skills_company_id_companies_id_fk": { + "name": "company_skills_company_id_companies_id_fk", + "tableFrom": "company_skills", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cost_events": { + "name": "cost_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "billing_type": { + "name": "billing_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cached_input_tokens": { + "name": "cached_input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cost_cents": { + "name": "cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cost_events_company_occurred_idx": { + "name": "cost_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_agent_occurred_idx": { + "name": "cost_events_company_agent_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_provider_occurred_idx": { + "name": "cost_events_company_provider_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_biller_occurred_idx": { + "name": "cost_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_heartbeat_run_idx": { + "name": "cost_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cost_events_company_id_companies_id_fk": { + "name": "cost_events_company_id_companies_id_fk", + "tableFrom": "cost_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_agent_id_agents_id_fk": { + "name": "cost_events_agent_id_agents_id_fk", + "tableFrom": "cost_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_issue_id_issues_id_fk": { + "name": "cost_events_issue_id_issues_id_fk", + "tableFrom": "cost_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_project_id_projects_id_fk": { + "name": "cost_events_project_id_projects_id_fk", + "tableFrom": "cost_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_goal_id_goals_id_fk": { + "name": "cost_events_goal_id_goals_id_fk", + "tableFrom": "cost_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "cost_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "cost_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_revisions": { + "name": "document_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_revisions_document_revision_uq": { + "name": "document_revisions_document_revision_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_revisions_company_document_created_idx": { + "name": "document_revisions_company_document_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_revisions_company_id_companies_id_fk": { + "name": "document_revisions_company_id_companies_id_fk", + "tableFrom": "document_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_revisions_document_id_documents_id_fk": { + "name": "document_revisions_document_id_documents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_revisions_created_by_agent_id_agents_id_fk": { + "name": "document_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.documents": { + "name": "documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "latest_body": { + "name": "latest_body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "latest_revision_id": { + "name": "latest_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "latest_revision_number": { + "name": "latest_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "documents_company_updated_idx": { + "name": "documents_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_company_created_idx": { + "name": "documents_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "documents_company_id_companies_id_fk": { + "name": "documents_company_id_companies_id_fk", + "tableFrom": "documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "documents_created_by_agent_id_agents_id_fk": { + "name": "documents_created_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "documents_updated_by_agent_id_agents_id_fk": { + "name": "documents_updated_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_workspaces": { + "name": "execution_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "strategy_type": { + "name": "strategy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_ref": { + "name": "base_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch_name": { + "name": "branch_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_fs'" + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "derived_from_execution_workspace_id": { + "name": "derived_from_execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "opened_at": { + "name": "opened_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_eligible_at": { + "name": "cleanup_eligible_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_reason": { + "name": "cleanup_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_workspaces_company_project_status_idx": { + "name": "execution_workspaces_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_project_workspace_status_idx": { + "name": "execution_workspaces_company_project_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_source_issue_idx": { + "name": "execution_workspaces_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_last_used_idx": { + "name": "execution_workspaces_company_last_used_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_branch_idx": { + "name": "execution_workspaces_company_branch_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspaces_company_id_companies_id_fk": { + "name": "execution_workspaces_company_id_companies_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "execution_workspaces_project_id_projects_id_fk": { + "name": "execution_workspaces_project_id_projects_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspaces_project_workspace_id_project_workspaces_id_fk": { + "name": "execution_workspaces_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_source_issue_id_issues_id_fk": { + "name": "execution_workspaces_source_issue_id_issues_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk": { + "name": "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "derived_from_execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.finance_events": { + "name": "finance_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cost_event_id": { + "name": "cost_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "event_kind": { + "name": "event_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'debit'" + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_adapter_type": { + "name": "execution_adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pricing_tier": { + "name": "pricing_tier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "estimated": { + "name": "estimated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "external_invoice_id": { + "name": "external_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata_json": { + "name": "metadata_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "finance_events_company_occurred_idx": { + "name": "finance_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_biller_occurred_idx": { + "name": "finance_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_kind_occurred_idx": { + "name": "finance_events_company_kind_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_direction_occurred_idx": { + "name": "finance_events_company_direction_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "direction", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_heartbeat_run_idx": { + "name": "finance_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_cost_event_idx": { + "name": "finance_events_company_cost_event_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "finance_events_company_id_companies_id_fk": { + "name": "finance_events_company_id_companies_id_fk", + "tableFrom": "finance_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_agent_id_agents_id_fk": { + "name": "finance_events_agent_id_agents_id_fk", + "tableFrom": "finance_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_issue_id_issues_id_fk": { + "name": "finance_events_issue_id_issues_id_fk", + "tableFrom": "finance_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_project_id_projects_id_fk": { + "name": "finance_events_project_id_projects_id_fk", + "tableFrom": "finance_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_goal_id_goals_id_fk": { + "name": "finance_events_goal_id_goals_id_fk", + "tableFrom": "finance_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "finance_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "finance_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_cost_event_id_cost_events_id_fk": { + "name": "finance_events_cost_event_id_cost_events_id_fk", + "tableFrom": "finance_events", + "tableTo": "cost_events", + "columnsFrom": [ + "cost_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.goals": { + "name": "goals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'task'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'planned'" + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "goals_company_idx": { + "name": "goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "goals_company_id_companies_id_fk": { + "name": "goals_company_id_companies_id_fk", + "tableFrom": "goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_parent_id_goals_id_fk": { + "name": "goals_parent_id_goals_id_fk", + "tableFrom": "goals", + "tableTo": "goals", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_owner_agent_id_agents_id_fk": { + "name": "goals_owner_agent_id_agents_id_fk", + "tableFrom": "goals", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_run_events": { + "name": "heartbeat_run_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stream": { + "name": "stream", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_run_events_run_seq_idx": { + "name": "heartbeat_run_events_run_seq_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_run_idx": { + "name": "heartbeat_run_events_company_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_created_idx": { + "name": "heartbeat_run_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_run_events_company_id_companies_id_fk": { + "name": "heartbeat_run_events_company_id_companies_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_events_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_agent_id_agents_id_fk": { + "name": "heartbeat_run_events_agent_id_agents_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_runs": { + "name": "heartbeat_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invocation_source": { + "name": "invocation_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'on_demand'" + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wakeup_request_id": { + "name": "wakeup_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "signal": { + "name": "signal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "usage_json": { + "name": "usage_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_json": { + "name": "result_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_id_before": { + "name": "session_id_before", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id_after": { + "name": "session_id_after", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_run_id": { + "name": "external_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "process_pid": { + "name": "process_pid", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "process_started_at": { + "name": "process_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "retry_of_run_id": { + "name": "retry_of_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "process_loss_retry_count": { + "name": "process_loss_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "context_snapshot": { + "name": "context_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_runs_company_agent_started_idx": { + "name": "heartbeat_runs_company_agent_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_runs_company_id_companies_id_fk": { + "name": "heartbeat_runs_company_id_companies_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_agent_id_agents_id_fk": { + "name": "heartbeat_runs_agent_id_agents_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk": { + "name": "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agent_wakeup_requests", + "columnsFrom": [ + "wakeup_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "retry_of_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_settings": { + "name": "instance_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "singleton_key": { + "name": "singleton_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "general": { + "name": "general", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "experimental": { + "name": "experimental", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_settings_singleton_key_idx": { + "name": "instance_settings_singleton_key_idx", + "columns": [ + { + "expression": "singleton_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_user_roles": { + "name": "instance_user_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'instance_admin'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_user_roles_user_role_unique_idx": { + "name": "instance_user_roles_user_role_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "instance_user_roles_role_idx": { + "name": "instance_user_roles_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invites": { + "name": "invites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "invite_type": { + "name": "invite_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company_join'" + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allowed_join_types": { + "name": "allowed_join_types", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'both'" + }, + "defaults_payload": { + "name": "defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "invited_by_user_id": { + "name": "invited_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invites_token_hash_unique_idx": { + "name": "invites_token_hash_unique_idx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invites_company_invite_state_idx": { + "name": "invites_company_invite_state_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invite_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revoked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invites_company_id_companies_id_fk": { + "name": "invites_company_id_companies_id_fk", + "tableFrom": "invites", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_approvals": { + "name": "issue_approvals", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "linked_by_agent_id": { + "name": "linked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "linked_by_user_id": { + "name": "linked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_approvals_issue_idx": { + "name": "issue_approvals_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_approval_idx": { + "name": "issue_approvals_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_company_idx": { + "name": "issue_approvals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_approvals_company_id_companies_id_fk": { + "name": "issue_approvals_company_id_companies_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_approvals_issue_id_issues_id_fk": { + "name": "issue_approvals_issue_id_issues_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_approval_id_approvals_id_fk": { + "name": "issue_approvals_approval_id_approvals_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_linked_by_agent_id_agents_id_fk": { + "name": "issue_approvals_linked_by_agent_id_agents_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "agents", + "columnsFrom": [ + "linked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_approvals_pk": { + "name": "issue_approvals_pk", + "columns": [ + "issue_id", + "approval_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_attachments": { + "name": "issue_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_comment_id": { + "name": "issue_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_attachments_company_issue_idx": { + "name": "issue_attachments_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_issue_comment_idx": { + "name": "issue_attachments_issue_comment_idx", + "columns": [ + { + "expression": "issue_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_asset_uq": { + "name": "issue_attachments_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_attachments_company_id_companies_id_fk": { + "name": "issue_attachments_company_id_companies_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_attachments_issue_id_issues_id_fk": { + "name": "issue_attachments_issue_id_issues_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_asset_id_assets_id_fk": { + "name": "issue_attachments_asset_id_assets_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_issue_comment_id_issue_comments_id_fk": { + "name": "issue_attachments_issue_comment_id_issue_comments_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issue_comments", + "columnsFrom": [ + "issue_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_comments": { + "name": "issue_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_comments_issue_idx": { + "name": "issue_comments_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_idx": { + "name": "issue_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_issue_created_at_idx": { + "name": "issue_comments_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_author_issue_created_at_idx": { + "name": "issue_comments_company_author_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_comments_company_id_companies_id_fk": { + "name": "issue_comments_company_id_companies_id_fk", + "tableFrom": "issue_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_issue_id_issues_id_fk": { + "name": "issue_comments_issue_id_issues_id_fk", + "tableFrom": "issue_comments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_author_agent_id_agents_id_fk": { + "name": "issue_comments_author_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_documents": { + "name": "issue_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_documents_company_issue_key_uq": { + "name": "issue_documents_company_issue_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_document_uq": { + "name": "issue_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_company_issue_updated_idx": { + "name": "issue_documents_company_issue_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_documents_company_id_companies_id_fk": { + "name": "issue_documents_company_id_companies_id_fk", + "tableFrom": "issue_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_documents_issue_id_issues_id_fk": { + "name": "issue_documents_issue_id_issues_id_fk", + "tableFrom": "issue_documents", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_documents_document_id_documents_id_fk": { + "name": "issue_documents_document_id_documents_id_fk", + "tableFrom": "issue_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_inbox_archives": { + "name": "issue_inbox_archives", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_inbox_archives_company_issue_idx": { + "name": "issue_inbox_archives_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_user_idx": { + "name": "issue_inbox_archives_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_issue_user_idx": { + "name": "issue_inbox_archives_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_inbox_archives_company_id_companies_id_fk": { + "name": "issue_inbox_archives_company_id_companies_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_inbox_archives_issue_id_issues_id_fk": { + "name": "issue_inbox_archives_issue_id_issues_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_labels": { + "name": "issue_labels", + "schema": "", + "columns": { + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label_id": { + "name": "label_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_labels_issue_idx": { + "name": "issue_labels_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_label_idx": { + "name": "issue_labels_label_idx", + "columns": [ + { + "expression": "label_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_company_idx": { + "name": "issue_labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_labels_issue_id_issues_id_fk": { + "name": "issue_labels_issue_id_issues_id_fk", + "tableFrom": "issue_labels", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_label_id_labels_id_fk": { + "name": "issue_labels_label_id_labels_id_fk", + "tableFrom": "issue_labels", + "tableTo": "labels", + "columnsFrom": [ + "label_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_company_id_companies_id_fk": { + "name": "issue_labels_company_id_companies_id_fk", + "tableFrom": "issue_labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_labels_pk": { + "name": "issue_labels_pk", + "columns": [ + "issue_id", + "label_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_read_states": { + "name": "issue_read_states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_read_at": { + "name": "last_read_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_read_states_company_issue_idx": { + "name": "issue_read_states_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_user_idx": { + "name": "issue_read_states_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_issue_user_idx": { + "name": "issue_read_states_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_read_states_company_id_companies_id_fk": { + "name": "issue_read_states_company_id_companies_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_read_states_issue_id_issues_id_fk": { + "name": "issue_read_states_issue_id_issues_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_work_products": { + "name": "issue_work_products", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "runtime_service_id": { + "name": "runtime_service_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "review_state": { + "name": "review_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_work_products_company_issue_type_idx": { + "name": "issue_work_products_company_issue_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_execution_workspace_type_idx": { + "name": "issue_work_products_company_execution_workspace_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_provider_external_id_idx": { + "name": "issue_work_products_company_provider_external_id_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_updated_idx": { + "name": "issue_work_products_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_work_products_company_id_companies_id_fk": { + "name": "issue_work_products_company_id_companies_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_work_products_project_id_projects_id_fk": { + "name": "issue_work_products_project_id_projects_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_issue_id_issues_id_fk": { + "name": "issue_work_products_issue_id_issues_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_work_products_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issue_work_products_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk": { + "name": "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "workspace_runtime_services", + "columnsFrom": [ + "runtime_service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_work_products_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issues": { + "name": "issues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assignee_user_id": { + "name": "assignee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "checkout_run_id": { + "name": "checkout_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_run_id": { + "name": "execution_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_agent_name_key": { + "name": "execution_agent_name_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_locked_at": { + "name": "execution_locked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_number": { + "name": "issue_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_kind": { + "name": "origin_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "origin_id": { + "name": "origin_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_depth": { + "name": "request_depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_adapter_overrides": { + "name": "assignee_adapter_overrides", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_preference": { + "name": "execution_workspace_preference", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_settings": { + "name": "execution_workspace_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "hidden_at": { + "name": "hidden_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issues_company_status_idx": { + "name": "issues_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_status_idx": { + "name": "issues_company_assignee_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_user_status_idx": { + "name": "issues_company_assignee_user_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_parent_idx": { + "name": "issues_company_parent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_idx": { + "name": "issues_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_origin_idx": { + "name": "issues_company_origin_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_workspace_idx": { + "name": "issues_company_project_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_execution_workspace_idx": { + "name": "issues_company_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_identifier_idx": { + "name": "issues_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_open_routine_execution_uq": { + "name": "issues_open_routine_execution_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'routine_execution'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"execution_run_id\" is not null\n and \"issues\".\"status\" in ('backlog', 'todo', 'in_progress', 'in_review', 'blocked')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issues_company_id_companies_id_fk": { + "name": "issues_company_id_companies_id_fk", + "tableFrom": "issues", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_id_projects_id_fk": { + "name": "issues_project_id_projects_id_fk", + "tableFrom": "issues", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_workspace_id_project_workspaces_id_fk": { + "name": "issues_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_goal_id_goals_id_fk": { + "name": "issues_goal_id_goals_id_fk", + "tableFrom": "issues", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_parent_id_issues_id_fk": { + "name": "issues_parent_id_issues_id_fk", + "tableFrom": "issues", + "tableTo": "issues", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_assignee_agent_id_agents_id_fk": { + "name": "issues_assignee_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_checkout_run_id_heartbeat_runs_id_fk": { + "name": "issues_checkout_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "checkout_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_execution_run_id_heartbeat_runs_id_fk": { + "name": "issues_execution_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "execution_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_created_by_agent_id_agents_id_fk": { + "name": "issues_created_by_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issues_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.join_requests": { + "name": "join_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "invite_id": { + "name": "invite_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_type": { + "name": "request_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending_approval'" + }, + "request_ip": { + "name": "request_ip", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requesting_user_id": { + "name": "requesting_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_email_snapshot": { + "name": "request_email_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_name": { + "name": "agent_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_defaults_payload": { + "name": "agent_defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "claim_secret_hash": { + "name": "claim_secret_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_secret_expires_at": { + "name": "claim_secret_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claim_secret_consumed_at": { + "name": "claim_secret_consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_agent_id": { + "name": "created_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "rejected_by_user_id": { + "name": "rejected_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejected_at": { + "name": "rejected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "join_requests_invite_unique_idx": { + "name": "join_requests_invite_unique_idx", + "columns": [ + { + "expression": "invite_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_company_status_type_created_idx": { + "name": "join_requests_company_status_type_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "join_requests_invite_id_invites_id_fk": { + "name": "join_requests_invite_id_invites_id_fk", + "tableFrom": "join_requests", + "tableTo": "invites", + "columnsFrom": [ + "invite_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_company_id_companies_id_fk": { + "name": "join_requests_company_id_companies_id_fk", + "tableFrom": "join_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_created_agent_id_agents_id_fk": { + "name": "join_requests_created_agent_id_agents_id_fk", + "tableFrom": "join_requests", + "tableTo": "agents", + "columnsFrom": [ + "created_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.labels": { + "name": "labels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "labels_company_idx": { + "name": "labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "labels_company_name_idx": { + "name": "labels_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "labels_company_id_companies_id_fk": { + "name": "labels_company_id_companies_id_fk", + "tableFrom": "labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_company_settings": { + "name": "plugin_company_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "settings_json": { + "name": "settings_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_company_settings_company_idx": { + "name": "plugin_company_settings_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_plugin_idx": { + "name": "plugin_company_settings_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_company_plugin_uq": { + "name": "plugin_company_settings_company_plugin_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_company_settings_company_id_companies_id_fk": { + "name": "plugin_company_settings_company_id_companies_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_company_settings_plugin_id_plugins_id_fk": { + "name": "plugin_company_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_config": { + "name": "plugin_config", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "config_json": { + "name": "config_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_config_plugin_id_idx": { + "name": "plugin_config_plugin_id_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_config_plugin_id_plugins_id_fk": { + "name": "plugin_config_plugin_id_plugins_id_fk", + "tableFrom": "plugin_config", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_entities": { + "name": "plugin_entities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_entities_plugin_idx": { + "name": "plugin_entities_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_type_idx": { + "name": "plugin_entities_type_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_scope_idx": { + "name": "plugin_entities_scope_idx", + "columns": [ + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_external_idx": { + "name": "plugin_entities_external_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_entities_plugin_id_plugins_id_fk": { + "name": "plugin_entities_plugin_id_plugins_id_fk", + "tableFrom": "plugin_entities", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_job_runs": { + "name": "plugin_job_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "job_id": { + "name": "job_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logs": { + "name": "logs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_job_runs_job_idx": { + "name": "plugin_job_runs_job_idx", + "columns": [ + { + "expression": "job_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_plugin_idx": { + "name": "plugin_job_runs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_status_idx": { + "name": "plugin_job_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_job_runs_job_id_plugin_jobs_id_fk": { + "name": "plugin_job_runs_job_id_plugin_jobs_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugin_jobs", + "columnsFrom": [ + "job_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_job_runs_plugin_id_plugins_id_fk": { + "name": "plugin_job_runs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_jobs": { + "name": "plugin_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "job_key": { + "name": "job_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_jobs_plugin_idx": { + "name": "plugin_jobs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_next_run_idx": { + "name": "plugin_jobs_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_unique_idx": { + "name": "plugin_jobs_unique_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "job_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_jobs_plugin_id_plugins_id_fk": { + "name": "plugin_jobs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_jobs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_logs": { + "name": "plugin_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'info'" + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "meta": { + "name": "meta", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_logs_plugin_time_idx": { + "name": "plugin_logs_plugin_time_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_logs_level_idx": { + "name": "plugin_logs_level_idx", + "columns": [ + { + "expression": "level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_logs_plugin_id_plugins_id_fk": { + "name": "plugin_logs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_logs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_state": { + "name": "plugin_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "namespace": { + "name": "namespace", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "state_key": { + "name": "state_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value_json": { + "name": "value_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_state_plugin_scope_idx": { + "name": "plugin_state_plugin_scope_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_state_plugin_id_plugins_id_fk": { + "name": "plugin_state_plugin_id_plugins_id_fk", + "tableFrom": "plugin_state", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "plugin_state_unique_entry_idx": { + "name": "plugin_state_unique_entry_idx", + "nullsNotDistinct": true, + "columns": [ + "plugin_id", + "scope_kind", + "scope_id", + "namespace", + "state_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_webhook_deliveries": { + "name": "plugin_webhook_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "webhook_key": { + "name": "webhook_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "headers": { + "name": "headers", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_webhook_deliveries_plugin_idx": { + "name": "plugin_webhook_deliveries_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_status_idx": { + "name": "plugin_webhook_deliveries_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_key_idx": { + "name": "plugin_webhook_deliveries_key_idx", + "columns": [ + { + "expression": "webhook_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_webhook_deliveries_plugin_id_plugins_id_fk": { + "name": "plugin_webhook_deliveries_plugin_id_plugins_id_fk", + "tableFrom": "plugin_webhook_deliveries", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugins": { + "name": "plugins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "package_name": { + "name": "package_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "api_version": { + "name": "api_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "manifest_json": { + "name": "manifest_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'installed'" + }, + "install_order": { + "name": "install_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "package_path": { + "name": "package_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_at": { + "name": "installed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugins_plugin_key_idx": { + "name": "plugins_plugin_key_idx", + "columns": [ + { + "expression": "plugin_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugins_status_idx": { + "name": "plugins_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.principal_permission_grants": { + "name": "principal_permission_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_key": { + "name": "permission_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "granted_by_user_id": { + "name": "granted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "principal_permission_grants_unique_idx": { + "name": "principal_permission_grants_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "principal_permission_grants_company_permission_idx": { + "name": "principal_permission_grants_company_permission_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "principal_permission_grants_company_id_companies_id_fk": { + "name": "principal_permission_grants_company_id_companies_id_fk", + "tableFrom": "principal_permission_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_goals": { + "name": "project_goals", + "schema": "", + "columns": { + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_goals_project_idx": { + "name": "project_goals_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_goal_idx": { + "name": "project_goals_goal_idx", + "columns": [ + { + "expression": "goal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_company_idx": { + "name": "project_goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_goals_project_id_projects_id_fk": { + "name": "project_goals_project_id_projects_id_fk", + "tableFrom": "project_goals", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_goal_id_goals_id_fk": { + "name": "project_goals_goal_id_goals_id_fk", + "tableFrom": "project_goals", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_company_id_companies_id_fk": { + "name": "project_goals_company_id_companies_id_fk", + "tableFrom": "project_goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "project_goals_project_id_goal_id_pk": { + "name": "project_goals_project_id_goal_id_pk", + "columns": [ + "project_id", + "goal_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_workspaces": { + "name": "project_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_ref": { + "name": "repo_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_ref": { + "name": "default_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "setup_command": { + "name": "setup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cleanup_command": { + "name": "cleanup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_provider": { + "name": "remote_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_workspace_ref": { + "name": "remote_workspace_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_workspace_key": { + "name": "shared_workspace_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_workspaces_company_project_idx": { + "name": "project_workspaces_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_primary_idx": { + "name": "project_workspaces_project_primary_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_primary", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_source_type_idx": { + "name": "project_workspaces_project_source_type_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_company_shared_key_idx": { + "name": "project_workspaces_company_shared_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "shared_workspace_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_remote_ref_idx": { + "name": "project_workspaces_project_remote_ref_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_workspace_ref", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_workspaces_company_id_companies_id_fk": { + "name": "project_workspaces_company_id_companies_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "project_workspaces_project_id_projects_id_fk": { + "name": "project_workspaces_project_id_projects_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "lead_agent_id": { + "name": "lead_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_date": { + "name": "target_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_policy": { + "name": "execution_workspace_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "projects_company_idx": { + "name": "projects_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "projects_company_id_companies_id_fk": { + "name": "projects_company_id_companies_id_fk", + "tableFrom": "projects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_goal_id_goals_id_fk": { + "name": "projects_goal_id_goals_id_fk", + "tableFrom": "projects", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_lead_agent_id_agents_id_fk": { + "name": "projects_lead_agent_id_agents_id_fk", + "tableFrom": "projects", + "tableTo": "agents", + "columnsFrom": [ + "lead_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_runs": { + "name": "routine_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger_id": { + "name": "trigger_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "triggered_at": { + "name": "triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_payload": { + "name": "trigger_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "linked_issue_id": { + "name": "linked_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "coalesced_into_run_id": { + "name": "coalesced_into_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_runs_company_routine_idx": { + "name": "routine_runs_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idx": { + "name": "routine_runs_trigger_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_linked_issue_idx": { + "name": "routine_runs_linked_issue_idx", + "columns": [ + { + "expression": "linked_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idempotency_idx": { + "name": "routine_runs_trigger_idempotency_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_runs_company_id_companies_id_fk": { + "name": "routine_runs_company_id_companies_id_fk", + "tableFrom": "routine_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_routine_id_routines_id_fk": { + "name": "routine_runs_routine_id_routines_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_trigger_id_routine_triggers_id_fk": { + "name": "routine_runs_trigger_id_routine_triggers_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routine_triggers", + "columnsFrom": [ + "trigger_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_runs_linked_issue_id_issues_id_fk": { + "name": "routine_runs_linked_issue_id_issues_id_fk", + "tableFrom": "routine_runs", + "tableTo": "issues", + "columnsFrom": [ + "linked_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_triggers": { + "name": "routine_triggers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "public_id": { + "name": "public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "signing_mode": { + "name": "signing_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "replay_window_sec": { + "name": "replay_window_sec", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_rotated_at": { + "name": "last_rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_result": { + "name": "last_result", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_triggers_company_routine_idx": { + "name": "routine_triggers_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_company_kind_idx": { + "name": "routine_triggers_company_kind_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_next_run_idx": { + "name": "routine_triggers_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_idx": { + "name": "routine_triggers_public_id_idx", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_uq": { + "name": "routine_triggers_public_id_uq", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_triggers_company_id_companies_id_fk": { + "name": "routine_triggers_company_id_companies_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_routine_id_routines_id_fk": { + "name": "routine_triggers_routine_id_routines_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_secret_id_company_secrets_id_fk": { + "name": "routine_triggers_secret_id_company_secrets_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_created_by_agent_id_agents_id_fk": { + "name": "routine_triggers_created_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_updated_by_agent_id_agents_id_fk": { + "name": "routine_triggers_updated_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routines": { + "name": "routines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_issue_id": { + "name": "parent_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "concurrency_policy": { + "name": "concurrency_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'coalesce_if_active'" + }, + "catch_up_policy": { + "name": "catch_up_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'skip_missed'" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_triggered_at": { + "name": "last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_enqueued_at": { + "name": "last_enqueued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routines_company_status_idx": { + "name": "routines_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_assignee_idx": { + "name": "routines_company_assignee_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_project_idx": { + "name": "routines_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routines_company_id_companies_id_fk": { + "name": "routines_company_id_companies_id_fk", + "tableFrom": "routines", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_project_id_projects_id_fk": { + "name": "routines_project_id_projects_id_fk", + "tableFrom": "routines", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_goal_id_goals_id_fk": { + "name": "routines_goal_id_goals_id_fk", + "tableFrom": "routines", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_parent_issue_id_issues_id_fk": { + "name": "routines_parent_issue_id_issues_id_fk", + "tableFrom": "routines", + "tableTo": "issues", + "columnsFrom": [ + "parent_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_assignee_agent_id_agents_id_fk": { + "name": "routines_assignee_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "routines_created_by_agent_id_agents_id_fk": { + "name": "routines_created_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_updated_by_agent_id_agents_id_fk": { + "name": "routines_updated_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_operations": { + "name": "workspace_operations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_operations_company_run_started_idx": { + "name": "workspace_operations_company_run_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_operations_company_workspace_started_idx": { + "name": "workspace_operations_company_workspace_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_operations_company_id_companies_id_fk": { + "name": "workspace_operations_company_id_companies_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_operations_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_operations_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_runtime_services": { + "name": "workspace_runtime_services", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "service_name": { + "name": "service_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reuse_key": { + "name": "reuse_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "started_by_run_id": { + "name": "started_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stop_policy": { + "name": "stop_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_runtime_services_company_workspace_status_idx": { + "name": "workspace_runtime_services_company_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_execution_workspace_status_idx": { + "name": "workspace_runtime_services_company_execution_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_project_status_idx": { + "name": "workspace_runtime_services_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_run_idx": { + "name": "workspace_runtime_services_run_idx", + "columns": [ + { + "expression": "started_by_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_updated_idx": { + "name": "workspace_runtime_services_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_runtime_services_company_id_companies_id_fk": { + "name": "workspace_runtime_services_company_id_companies_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_id_projects_id_fk": { + "name": "workspace_runtime_services_project_id_projects_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk": { + "name": "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_issue_id_issues_id_fk": { + "name": "workspace_runtime_services_issue_id_issues_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_owner_agent_id_agents_id_fk": { + "name": "workspace_runtime_services_owner_agent_id_agents_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk": { + "name": "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "started_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/src/migrations/meta/0047_snapshot.json b/packages/db/src/migrations/meta/0047_snapshot.json new file mode 100644 index 00000000000..266ecf3750f --- /dev/null +++ b/packages/db/src/migrations/meta/0047_snapshot.json @@ -0,0 +1,12539 @@ +{ + "id": "a0553dd4-5278-406f-bef7-b06597f92cf2", + "prevId": "4ae31a44-1b98-4437-88ef-92b76d014107", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.activity_log": { + "name": "activity_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "activity_log_company_created_idx": { + "name": "activity_log_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_run_id_idx": { + "name": "activity_log_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_entity_type_id_idx": { + "name": "activity_log_entity_type_id_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "activity_log_company_id_companies_id_fk": { + "name": "activity_log_company_id_companies_id_fk", + "tableFrom": "activity_log", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_agent_id_agents_id_fk": { + "name": "activity_log_agent_id_agents_id_fk", + "tableFrom": "activity_log", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_run_id_heartbeat_runs_id_fk": { + "name": "activity_log_run_id_heartbeat_runs_id_fk", + "tableFrom": "activity_log", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_api_keys": { + "name": "agent_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_api_keys_key_hash_idx": { + "name": "agent_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_api_keys_company_agent_idx": { + "name": "agent_api_keys_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_api_keys_agent_id_agents_id_fk": { + "name": "agent_api_keys_agent_id_agents_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_api_keys_company_id_companies_id_fk": { + "name": "agent_api_keys_company_id_companies_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_config_revisions": { + "name": "agent_config_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'patch'" + }, + "rolled_back_from_revision_id": { + "name": "rolled_back_from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "changed_keys": { + "name": "changed_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "before_config": { + "name": "before_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "after_config": { + "name": "after_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_config_revisions_company_agent_created_idx": { + "name": "agent_config_revisions_company_agent_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_config_revisions_agent_created_idx": { + "name": "agent_config_revisions_agent_created_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_config_revisions_company_id_companies_id_fk": { + "name": "agent_config_revisions_company_id_companies_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_config_revisions_agent_id_agents_id_fk": { + "name": "agent_config_revisions_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_config_revisions_created_by_agent_id_agents_id_fk": { + "name": "agent_config_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_runtime_state": { + "name": "agent_runtime_state", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_json": { + "name": "state_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_run_status": { + "name": "last_run_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total_input_tokens": { + "name": "total_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_output_tokens": { + "name": "total_output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cached_input_tokens": { + "name": "total_cached_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost_cents": { + "name": "total_cost_cents", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_runtime_state_company_agent_idx": { + "name": "agent_runtime_state_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_runtime_state_company_updated_idx": { + "name": "agent_runtime_state_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_runtime_state_agent_id_agents_id_fk": { + "name": "agent_runtime_state_agent_id_agents_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_runtime_state_company_id_companies_id_fk": { + "name": "agent_runtime_state_company_id_companies_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_task_sessions": { + "name": "agent_task_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "task_key": { + "name": "task_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_params_json": { + "name": "session_params_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_display_id": { + "name": "session_display_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_task_sessions_company_agent_adapter_task_uniq": { + "name": "agent_task_sessions_company_agent_adapter_task_uniq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "adapter_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_agent_updated_idx": { + "name": "agent_task_sessions_company_agent_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_task_updated_idx": { + "name": "agent_task_sessions_company_task_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_task_sessions_company_id_companies_id_fk": { + "name": "agent_task_sessions_company_id_companies_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_agent_id_agents_id_fk": { + "name": "agent_task_sessions_agent_id_agents_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_last_run_id_heartbeat_runs_id_fk": { + "name": "agent_task_sessions_last_run_id_heartbeat_runs_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "last_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_wakeup_requests": { + "name": "agent_wakeup_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "coalesced_count": { + "name": "coalesced_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "requested_by_actor_type": { + "name": "requested_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by_actor_id": { + "name": "requested_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_wakeup_requests_company_agent_status_idx": { + "name": "agent_wakeup_requests_company_agent_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_company_requested_idx": { + "name": "agent_wakeup_requests_company_requested_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_agent_requested_idx": { + "name": "agent_wakeup_requests_agent_requested_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_wakeup_requests_company_id_companies_id_fk": { + "name": "agent_wakeup_requests_company_id_companies_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_wakeup_requests_agent_id_agents_id_fk": { + "name": "agent_wakeup_requests_agent_id_agents_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "reports_to": { + "name": "reports_to", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'process'" + }, + "adapter_config": { + "name": "adapter_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "runtime_config": { + "name": "runtime_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_heartbeat_at": { + "name": "last_heartbeat_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agents_company_status_idx": { + "name": "agents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_company_reports_to_idx": { + "name": "agents_company_reports_to_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reports_to", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agents_company_id_companies_id_fk": { + "name": "agents_company_id_companies_id_fk", + "tableFrom": "agents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agents_reports_to_agents_id_fk": { + "name": "agents_reports_to_agents_id_fk", + "tableFrom": "agents", + "tableTo": "agents", + "columnsFrom": [ + "reports_to" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approval_comments": { + "name": "approval_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approval_comments_company_idx": { + "name": "approval_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_idx": { + "name": "approval_comments_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_created_idx": { + "name": "approval_comments_approval_created_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approval_comments_company_id_companies_id_fk": { + "name": "approval_comments_company_id_companies_id_fk", + "tableFrom": "approval_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_approval_id_approvals_id_fk": { + "name": "approval_comments_approval_id_approvals_id_fk", + "tableFrom": "approval_comments", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_author_agent_id_agents_id_fk": { + "name": "approval_comments_author_agent_id_agents_id_fk", + "tableFrom": "approval_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approvals": { + "name": "approvals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_by_agent_id": { + "name": "requested_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_by_user_id": { + "name": "requested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "decision_note": { + "name": "decision_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approvals_company_status_type_idx": { + "name": "approvals_company_status_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approvals_company_id_companies_id_fk": { + "name": "approvals_company_id_companies_id_fk", + "tableFrom": "approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approvals_requested_by_agent_id_agents_id_fk": { + "name": "approvals_requested_by_agent_id_agents_id_fk", + "tableFrom": "approvals", + "tableTo": "agents", + "columnsFrom": [ + "requested_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.assets": { + "name": "assets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "object_key": { + "name": "object_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "original_filename": { + "name": "original_filename", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "assets_company_created_idx": { + "name": "assets_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_provider_idx": { + "name": "assets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_object_key_uq": { + "name": "assets_company_object_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "assets_company_id_companies_id_fk": { + "name": "assets_company_id_companies_id_fk", + "tableFrom": "assets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "assets_created_by_agent_id_agents_id_fk": { + "name": "assets_created_by_agent_id_agents_id_fk", + "tableFrom": "assets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.board_api_keys": { + "name": "board_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "board_api_keys_key_hash_idx": { + "name": "board_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "board_api_keys_user_idx": { + "name": "board_api_keys_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "board_api_keys_user_id_user_id_fk": { + "name": "board_api_keys_user_id_user_id_fk", + "tableFrom": "board_api_keys", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_incidents": { + "name": "budget_incidents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "policy_id": { + "name": "policy_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_end": { + "name": "window_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "threshold_type": { + "name": "threshold_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_limit": { + "name": "amount_limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "amount_observed": { + "name": "amount_observed", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_incidents_company_status_idx": { + "name": "budget_incidents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_company_scope_idx": { + "name": "budget_incidents_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_policy_window_threshold_idx": { + "name": "budget_incidents_policy_window_threshold_idx", + "columns": [ + { + "expression": "policy_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "threshold_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"budget_incidents\".\"status\" <> 'dismissed'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_incidents_company_id_companies_id_fk": { + "name": "budget_incidents_company_id_companies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_policy_id_budget_policies_id_fk": { + "name": "budget_incidents_policy_id_budget_policies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "budget_policies", + "columnsFrom": [ + "policy_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_approval_id_approvals_id_fk": { + "name": "budget_incidents_approval_id_approvals_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_policies": { + "name": "budget_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'billed_cents'" + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "warn_percent": { + "name": "warn_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 80 + }, + "hard_stop_enabled": { + "name": "hard_stop_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "notify_enabled": { + "name": "notify_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_policies_company_scope_active_idx": { + "name": "budget_policies_company_scope_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_window_idx": { + "name": "budget_policies_company_window_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_scope_metric_unique_idx": { + "name": "budget_policies_company_scope_metric_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_policies_company_id_companies_id_fk": { + "name": "budget_policies_company_id_companies_id_fk", + "tableFrom": "budget_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cli_auth_challenges": { + "name": "cli_auth_challenges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_hash": { + "name": "secret_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_access": { + "name": "requested_access", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'board'" + }, + "requested_company_id": { + "name": "requested_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "pending_key_hash": { + "name": "pending_key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pending_key_name": { + "name": "pending_key_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "board_api_key_id": { + "name": "board_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cli_auth_challenges_secret_hash_idx": { + "name": "cli_auth_challenges_secret_hash_idx", + "columns": [ + { + "expression": "secret_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_approved_by_idx": { + "name": "cli_auth_challenges_approved_by_idx", + "columns": [ + { + "expression": "approved_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_requested_company_idx": { + "name": "cli_auth_challenges_requested_company_idx", + "columns": [ + { + "expression": "requested_company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cli_auth_challenges_requested_company_id_companies_id_fk": { + "name": "cli_auth_challenges_requested_company_id_companies_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "companies", + "columnsFrom": [ + "requested_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_approved_by_user_id_user_id_fk": { + "name": "cli_auth_challenges_approved_by_user_id_user_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "user", + "columnsFrom": [ + "approved_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk": { + "name": "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "board_api_keys", + "columnsFrom": [ + "board_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.companies": { + "name": "companies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "issue_prefix": { + "name": "issue_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'PAP'" + }, + "issue_counter": { + "name": "issue_counter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "require_board_approval_for_new_agents": { + "name": "require_board_approval_for_new_agents", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "feedback_data_sharing_enabled": { + "name": "feedback_data_sharing_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "feedback_data_sharing_consent_at": { + "name": "feedback_data_sharing_consent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "feedback_data_sharing_consent_by_user_id": { + "name": "feedback_data_sharing_consent_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feedback_data_sharing_terms_version": { + "name": "feedback_data_sharing_terms_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "brand_color": { + "name": "brand_color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "companies_issue_prefix_idx": { + "name": "companies_issue_prefix_idx", + "columns": [ + { + "expression": "issue_prefix", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_logos": { + "name": "company_logos", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_logos_company_uq": { + "name": "company_logos_company_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_logos_asset_uq": { + "name": "company_logos_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_logos_company_id_companies_id_fk": { + "name": "company_logos_company_id_companies_id_fk", + "tableFrom": "company_logos", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_logos_asset_id_assets_id_fk": { + "name": "company_logos_asset_id_assets_id_fk", + "tableFrom": "company_logos", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_memberships": { + "name": "company_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "membership_role": { + "name": "membership_role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_memberships_company_principal_unique_idx": { + "name": "company_memberships_company_principal_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_principal_status_idx": { + "name": "company_memberships_principal_status_idx", + "columns": [ + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_company_status_idx": { + "name": "company_memberships_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_memberships_company_id_companies_id_fk": { + "name": "company_memberships_company_id_companies_id_fk", + "tableFrom": "company_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_versions": { + "name": "company_secret_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "material": { + "name": "material", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "value_sha256": { + "name": "value_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "company_secret_versions_secret_idx": { + "name": "company_secret_versions_secret_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_value_sha256_idx": { + "name": "company_secret_versions_value_sha256_idx", + "columns": [ + { + "expression": "value_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_secret_version_uq": { + "name": "company_secret_versions_secret_version_uq", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_versions_secret_id_company_secrets_id_fk": { + "name": "company_secret_versions_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_versions_created_by_agent_id_agents_id_fk": { + "name": "company_secret_versions_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secrets": { + "name": "company_secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_encrypted'" + }, + "external_ref": { + "name": "external_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "latest_version": { + "name": "latest_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secrets_company_idx": { + "name": "company_secrets_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_provider_idx": { + "name": "company_secrets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_name_uq": { + "name": "company_secrets_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secrets_company_id_companies_id_fk": { + "name": "company_secrets_company_id_companies_id_fk", + "tableFrom": "company_secrets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secrets_created_by_agent_id_agents_id_fk": { + "name": "company_secrets_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secrets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skills": { + "name": "company_skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "markdown": { + "name": "markdown", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "source_locator": { + "name": "source_locator", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_ref": { + "name": "source_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trust_level": { + "name": "trust_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown_only'" + }, + "compatibility": { + "name": "compatibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'compatible'" + }, + "file_inventory": { + "name": "file_inventory", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skills_company_key_idx": { + "name": "company_skills_company_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_name_idx": { + "name": "company_skills_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skills_company_id_companies_id_fk": { + "name": "company_skills_company_id_companies_id_fk", + "tableFrom": "company_skills", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cost_events": { + "name": "cost_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "billing_type": { + "name": "billing_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cached_input_tokens": { + "name": "cached_input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cost_cents": { + "name": "cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cost_events_company_occurred_idx": { + "name": "cost_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_agent_occurred_idx": { + "name": "cost_events_company_agent_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_provider_occurred_idx": { + "name": "cost_events_company_provider_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_biller_occurred_idx": { + "name": "cost_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_heartbeat_run_idx": { + "name": "cost_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cost_events_company_id_companies_id_fk": { + "name": "cost_events_company_id_companies_id_fk", + "tableFrom": "cost_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_agent_id_agents_id_fk": { + "name": "cost_events_agent_id_agents_id_fk", + "tableFrom": "cost_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_issue_id_issues_id_fk": { + "name": "cost_events_issue_id_issues_id_fk", + "tableFrom": "cost_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_project_id_projects_id_fk": { + "name": "cost_events_project_id_projects_id_fk", + "tableFrom": "cost_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_goal_id_goals_id_fk": { + "name": "cost_events_goal_id_goals_id_fk", + "tableFrom": "cost_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "cost_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "cost_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_revisions": { + "name": "document_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_revisions_document_revision_uq": { + "name": "document_revisions_document_revision_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_revisions_company_document_created_idx": { + "name": "document_revisions_company_document_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_revisions_company_id_companies_id_fk": { + "name": "document_revisions_company_id_companies_id_fk", + "tableFrom": "document_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_revisions_document_id_documents_id_fk": { + "name": "document_revisions_document_id_documents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_revisions_created_by_agent_id_agents_id_fk": { + "name": "document_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_revisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "document_revisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "document_revisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.documents": { + "name": "documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "latest_body": { + "name": "latest_body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "latest_revision_id": { + "name": "latest_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "latest_revision_number": { + "name": "latest_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "documents_company_updated_idx": { + "name": "documents_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_company_created_idx": { + "name": "documents_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "documents_company_id_companies_id_fk": { + "name": "documents_company_id_companies_id_fk", + "tableFrom": "documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "documents_created_by_agent_id_agents_id_fk": { + "name": "documents_created_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "documents_updated_by_agent_id_agents_id_fk": { + "name": "documents_updated_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_workspaces": { + "name": "execution_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "strategy_type": { + "name": "strategy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_ref": { + "name": "base_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch_name": { + "name": "branch_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_fs'" + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "derived_from_execution_workspace_id": { + "name": "derived_from_execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "opened_at": { + "name": "opened_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_eligible_at": { + "name": "cleanup_eligible_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_reason": { + "name": "cleanup_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_workspaces_company_project_status_idx": { + "name": "execution_workspaces_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_project_workspace_status_idx": { + "name": "execution_workspaces_company_project_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_source_issue_idx": { + "name": "execution_workspaces_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_last_used_idx": { + "name": "execution_workspaces_company_last_used_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_branch_idx": { + "name": "execution_workspaces_company_branch_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspaces_company_id_companies_id_fk": { + "name": "execution_workspaces_company_id_companies_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "execution_workspaces_project_id_projects_id_fk": { + "name": "execution_workspaces_project_id_projects_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspaces_project_workspace_id_project_workspaces_id_fk": { + "name": "execution_workspaces_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_source_issue_id_issues_id_fk": { + "name": "execution_workspaces_source_issue_id_issues_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk": { + "name": "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "derived_from_execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feedback_exports": { + "name": "feedback_exports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "feedback_vote_id": { + "name": "feedback_vote_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_only'" + }, + "destination": { + "name": "destination", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "export_id": { + "name": "export_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consent_version": { + "name": "consent_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "schema_version": { + "name": "schema_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-envelope-v2'" + }, + "bundle_version": { + "name": "bundle_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-bundle-v2'" + }, + "payload_version": { + "name": "payload_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-v1'" + }, + "payload_digest": { + "name": "payload_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload_snapshot": { + "name": "payload_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "target_summary": { + "name": "target_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "redaction_summary": { + "name": "redaction_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempted_at": { + "name": "last_attempted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "exported_at": { + "name": "exported_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_exports_feedback_vote_idx": { + "name": "feedback_exports_feedback_vote_idx", + "columns": [ + { + "expression": "feedback_vote_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_created_idx": { + "name": "feedback_exports_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_status_idx": { + "name": "feedback_exports_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_issue_idx": { + "name": "feedback_exports_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_project_idx": { + "name": "feedback_exports_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_author_idx": { + "name": "feedback_exports_company_author_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_exports_company_id_companies_id_fk": { + "name": "feedback_exports_company_id_companies_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "feedback_exports_feedback_vote_id_feedback_votes_id_fk": { + "name": "feedback_exports_feedback_vote_id_feedback_votes_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "feedback_votes", + "columnsFrom": [ + "feedback_vote_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_exports_issue_id_issues_id_fk": { + "name": "feedback_exports_issue_id_issues_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_exports_project_id_projects_id_fk": { + "name": "feedback_exports_project_id_projects_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feedback_votes": { + "name": "feedback_votes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_with_labs": { + "name": "shared_with_labs", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "shared_at": { + "name": "shared_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "consent_version": { + "name": "consent_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redaction_summary": { + "name": "redaction_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_votes_company_issue_idx": { + "name": "feedback_votes_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_issue_target_idx": { + "name": "feedback_votes_issue_target_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_author_idx": { + "name": "feedback_votes_author_idx", + "columns": [ + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_company_target_author_idx": { + "name": "feedback_votes_company_target_author_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_votes_company_id_companies_id_fk": { + "name": "feedback_votes_company_id_companies_id_fk", + "tableFrom": "feedback_votes", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "feedback_votes_issue_id_issues_id_fk": { + "name": "feedback_votes_issue_id_issues_id_fk", + "tableFrom": "feedback_votes", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.finance_events": { + "name": "finance_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cost_event_id": { + "name": "cost_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "event_kind": { + "name": "event_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'debit'" + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_adapter_type": { + "name": "execution_adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pricing_tier": { + "name": "pricing_tier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "estimated": { + "name": "estimated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "external_invoice_id": { + "name": "external_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata_json": { + "name": "metadata_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "finance_events_company_occurred_idx": { + "name": "finance_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_biller_occurred_idx": { + "name": "finance_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_kind_occurred_idx": { + "name": "finance_events_company_kind_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_direction_occurred_idx": { + "name": "finance_events_company_direction_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "direction", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_heartbeat_run_idx": { + "name": "finance_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_cost_event_idx": { + "name": "finance_events_company_cost_event_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "finance_events_company_id_companies_id_fk": { + "name": "finance_events_company_id_companies_id_fk", + "tableFrom": "finance_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_agent_id_agents_id_fk": { + "name": "finance_events_agent_id_agents_id_fk", + "tableFrom": "finance_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_issue_id_issues_id_fk": { + "name": "finance_events_issue_id_issues_id_fk", + "tableFrom": "finance_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_project_id_projects_id_fk": { + "name": "finance_events_project_id_projects_id_fk", + "tableFrom": "finance_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_goal_id_goals_id_fk": { + "name": "finance_events_goal_id_goals_id_fk", + "tableFrom": "finance_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "finance_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "finance_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_cost_event_id_cost_events_id_fk": { + "name": "finance_events_cost_event_id_cost_events_id_fk", + "tableFrom": "finance_events", + "tableTo": "cost_events", + "columnsFrom": [ + "cost_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.goals": { + "name": "goals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'task'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'planned'" + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "goals_company_idx": { + "name": "goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "goals_company_id_companies_id_fk": { + "name": "goals_company_id_companies_id_fk", + "tableFrom": "goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_parent_id_goals_id_fk": { + "name": "goals_parent_id_goals_id_fk", + "tableFrom": "goals", + "tableTo": "goals", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_owner_agent_id_agents_id_fk": { + "name": "goals_owner_agent_id_agents_id_fk", + "tableFrom": "goals", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_run_events": { + "name": "heartbeat_run_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stream": { + "name": "stream", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_run_events_run_seq_idx": { + "name": "heartbeat_run_events_run_seq_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_run_idx": { + "name": "heartbeat_run_events_company_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_created_idx": { + "name": "heartbeat_run_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_run_events_company_id_companies_id_fk": { + "name": "heartbeat_run_events_company_id_companies_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_events_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_agent_id_agents_id_fk": { + "name": "heartbeat_run_events_agent_id_agents_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_runs": { + "name": "heartbeat_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invocation_source": { + "name": "invocation_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'on_demand'" + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wakeup_request_id": { + "name": "wakeup_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "signal": { + "name": "signal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "usage_json": { + "name": "usage_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_json": { + "name": "result_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_id_before": { + "name": "session_id_before", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id_after": { + "name": "session_id_after", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_run_id": { + "name": "external_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "process_pid": { + "name": "process_pid", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "process_started_at": { + "name": "process_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "retry_of_run_id": { + "name": "retry_of_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "process_loss_retry_count": { + "name": "process_loss_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "context_snapshot": { + "name": "context_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_runs_company_agent_started_idx": { + "name": "heartbeat_runs_company_agent_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_runs_company_id_companies_id_fk": { + "name": "heartbeat_runs_company_id_companies_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_agent_id_agents_id_fk": { + "name": "heartbeat_runs_agent_id_agents_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk": { + "name": "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agent_wakeup_requests", + "columnsFrom": [ + "wakeup_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "retry_of_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_settings": { + "name": "instance_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "singleton_key": { + "name": "singleton_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "general": { + "name": "general", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "experimental": { + "name": "experimental", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_settings_singleton_key_idx": { + "name": "instance_settings_singleton_key_idx", + "columns": [ + { + "expression": "singleton_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_user_roles": { + "name": "instance_user_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'instance_admin'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_user_roles_user_role_unique_idx": { + "name": "instance_user_roles_user_role_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "instance_user_roles_role_idx": { + "name": "instance_user_roles_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invites": { + "name": "invites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "invite_type": { + "name": "invite_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company_join'" + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allowed_join_types": { + "name": "allowed_join_types", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'both'" + }, + "defaults_payload": { + "name": "defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "invited_by_user_id": { + "name": "invited_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invites_token_hash_unique_idx": { + "name": "invites_token_hash_unique_idx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invites_company_invite_state_idx": { + "name": "invites_company_invite_state_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invite_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revoked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invites_company_id_companies_id_fk": { + "name": "invites_company_id_companies_id_fk", + "tableFrom": "invites", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_approvals": { + "name": "issue_approvals", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "linked_by_agent_id": { + "name": "linked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "linked_by_user_id": { + "name": "linked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_approvals_issue_idx": { + "name": "issue_approvals_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_approval_idx": { + "name": "issue_approvals_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_company_idx": { + "name": "issue_approvals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_approvals_company_id_companies_id_fk": { + "name": "issue_approvals_company_id_companies_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_approvals_issue_id_issues_id_fk": { + "name": "issue_approvals_issue_id_issues_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_approval_id_approvals_id_fk": { + "name": "issue_approvals_approval_id_approvals_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_linked_by_agent_id_agents_id_fk": { + "name": "issue_approvals_linked_by_agent_id_agents_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "agents", + "columnsFrom": [ + "linked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_approvals_pk": { + "name": "issue_approvals_pk", + "columns": [ + "issue_id", + "approval_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_attachments": { + "name": "issue_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_comment_id": { + "name": "issue_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_attachments_company_issue_idx": { + "name": "issue_attachments_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_issue_comment_idx": { + "name": "issue_attachments_issue_comment_idx", + "columns": [ + { + "expression": "issue_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_asset_uq": { + "name": "issue_attachments_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_attachments_company_id_companies_id_fk": { + "name": "issue_attachments_company_id_companies_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_attachments_issue_id_issues_id_fk": { + "name": "issue_attachments_issue_id_issues_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_asset_id_assets_id_fk": { + "name": "issue_attachments_asset_id_assets_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_issue_comment_id_issue_comments_id_fk": { + "name": "issue_attachments_issue_comment_id_issue_comments_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issue_comments", + "columnsFrom": [ + "issue_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_comments": { + "name": "issue_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_comments_issue_idx": { + "name": "issue_comments_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_idx": { + "name": "issue_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_issue_created_at_idx": { + "name": "issue_comments_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_author_issue_created_at_idx": { + "name": "issue_comments_company_author_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_comments_company_id_companies_id_fk": { + "name": "issue_comments_company_id_companies_id_fk", + "tableFrom": "issue_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_issue_id_issues_id_fk": { + "name": "issue_comments_issue_id_issues_id_fk", + "tableFrom": "issue_comments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_author_agent_id_agents_id_fk": { + "name": "issue_comments_author_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_comments_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_documents": { + "name": "issue_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_documents_company_issue_key_uq": { + "name": "issue_documents_company_issue_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_document_uq": { + "name": "issue_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_company_issue_updated_idx": { + "name": "issue_documents_company_issue_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_documents_company_id_companies_id_fk": { + "name": "issue_documents_company_id_companies_id_fk", + "tableFrom": "issue_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_documents_issue_id_issues_id_fk": { + "name": "issue_documents_issue_id_issues_id_fk", + "tableFrom": "issue_documents", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_documents_document_id_documents_id_fk": { + "name": "issue_documents_document_id_documents_id_fk", + "tableFrom": "issue_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_inbox_archives": { + "name": "issue_inbox_archives", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_inbox_archives_company_issue_idx": { + "name": "issue_inbox_archives_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_user_idx": { + "name": "issue_inbox_archives_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_issue_user_idx": { + "name": "issue_inbox_archives_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_inbox_archives_company_id_companies_id_fk": { + "name": "issue_inbox_archives_company_id_companies_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_inbox_archives_issue_id_issues_id_fk": { + "name": "issue_inbox_archives_issue_id_issues_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_labels": { + "name": "issue_labels", + "schema": "", + "columns": { + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label_id": { + "name": "label_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_labels_issue_idx": { + "name": "issue_labels_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_label_idx": { + "name": "issue_labels_label_idx", + "columns": [ + { + "expression": "label_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_company_idx": { + "name": "issue_labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_labels_issue_id_issues_id_fk": { + "name": "issue_labels_issue_id_issues_id_fk", + "tableFrom": "issue_labels", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_label_id_labels_id_fk": { + "name": "issue_labels_label_id_labels_id_fk", + "tableFrom": "issue_labels", + "tableTo": "labels", + "columnsFrom": [ + "label_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_company_id_companies_id_fk": { + "name": "issue_labels_company_id_companies_id_fk", + "tableFrom": "issue_labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_labels_pk": { + "name": "issue_labels_pk", + "columns": [ + "issue_id", + "label_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_read_states": { + "name": "issue_read_states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_read_at": { + "name": "last_read_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_read_states_company_issue_idx": { + "name": "issue_read_states_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_user_idx": { + "name": "issue_read_states_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_issue_user_idx": { + "name": "issue_read_states_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_read_states_company_id_companies_id_fk": { + "name": "issue_read_states_company_id_companies_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_read_states_issue_id_issues_id_fk": { + "name": "issue_read_states_issue_id_issues_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_work_products": { + "name": "issue_work_products", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "runtime_service_id": { + "name": "runtime_service_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "review_state": { + "name": "review_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_work_products_company_issue_type_idx": { + "name": "issue_work_products_company_issue_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_execution_workspace_type_idx": { + "name": "issue_work_products_company_execution_workspace_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_provider_external_id_idx": { + "name": "issue_work_products_company_provider_external_id_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_updated_idx": { + "name": "issue_work_products_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_work_products_company_id_companies_id_fk": { + "name": "issue_work_products_company_id_companies_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_work_products_project_id_projects_id_fk": { + "name": "issue_work_products_project_id_projects_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_issue_id_issues_id_fk": { + "name": "issue_work_products_issue_id_issues_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_work_products_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issue_work_products_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk": { + "name": "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "workspace_runtime_services", + "columnsFrom": [ + "runtime_service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_work_products_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issues": { + "name": "issues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assignee_user_id": { + "name": "assignee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "checkout_run_id": { + "name": "checkout_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_run_id": { + "name": "execution_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_agent_name_key": { + "name": "execution_agent_name_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_locked_at": { + "name": "execution_locked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_number": { + "name": "issue_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_kind": { + "name": "origin_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "origin_id": { + "name": "origin_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_depth": { + "name": "request_depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_adapter_overrides": { + "name": "assignee_adapter_overrides", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_preference": { + "name": "execution_workspace_preference", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_settings": { + "name": "execution_workspace_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "hidden_at": { + "name": "hidden_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issues_company_status_idx": { + "name": "issues_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_status_idx": { + "name": "issues_company_assignee_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_user_status_idx": { + "name": "issues_company_assignee_user_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_parent_idx": { + "name": "issues_company_parent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_idx": { + "name": "issues_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_origin_idx": { + "name": "issues_company_origin_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_workspace_idx": { + "name": "issues_company_project_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_execution_workspace_idx": { + "name": "issues_company_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_identifier_idx": { + "name": "issues_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_open_routine_execution_uq": { + "name": "issues_open_routine_execution_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'routine_execution'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"execution_run_id\" is not null\n and \"issues\".\"status\" in ('backlog', 'todo', 'in_progress', 'in_review', 'blocked')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issues_company_id_companies_id_fk": { + "name": "issues_company_id_companies_id_fk", + "tableFrom": "issues", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_id_projects_id_fk": { + "name": "issues_project_id_projects_id_fk", + "tableFrom": "issues", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_workspace_id_project_workspaces_id_fk": { + "name": "issues_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_goal_id_goals_id_fk": { + "name": "issues_goal_id_goals_id_fk", + "tableFrom": "issues", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_parent_id_issues_id_fk": { + "name": "issues_parent_id_issues_id_fk", + "tableFrom": "issues", + "tableTo": "issues", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_assignee_agent_id_agents_id_fk": { + "name": "issues_assignee_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_checkout_run_id_heartbeat_runs_id_fk": { + "name": "issues_checkout_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "checkout_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_execution_run_id_heartbeat_runs_id_fk": { + "name": "issues_execution_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "execution_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_created_by_agent_id_agents_id_fk": { + "name": "issues_created_by_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issues_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.join_requests": { + "name": "join_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "invite_id": { + "name": "invite_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_type": { + "name": "request_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending_approval'" + }, + "request_ip": { + "name": "request_ip", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requesting_user_id": { + "name": "requesting_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_email_snapshot": { + "name": "request_email_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_name": { + "name": "agent_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_defaults_payload": { + "name": "agent_defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "claim_secret_hash": { + "name": "claim_secret_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_secret_expires_at": { + "name": "claim_secret_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claim_secret_consumed_at": { + "name": "claim_secret_consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_agent_id": { + "name": "created_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "rejected_by_user_id": { + "name": "rejected_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejected_at": { + "name": "rejected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "join_requests_invite_unique_idx": { + "name": "join_requests_invite_unique_idx", + "columns": [ + { + "expression": "invite_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_company_status_type_created_idx": { + "name": "join_requests_company_status_type_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "join_requests_invite_id_invites_id_fk": { + "name": "join_requests_invite_id_invites_id_fk", + "tableFrom": "join_requests", + "tableTo": "invites", + "columnsFrom": [ + "invite_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_company_id_companies_id_fk": { + "name": "join_requests_company_id_companies_id_fk", + "tableFrom": "join_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_created_agent_id_agents_id_fk": { + "name": "join_requests_created_agent_id_agents_id_fk", + "tableFrom": "join_requests", + "tableTo": "agents", + "columnsFrom": [ + "created_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.labels": { + "name": "labels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "labels_company_idx": { + "name": "labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "labels_company_name_idx": { + "name": "labels_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "labels_company_id_companies_id_fk": { + "name": "labels_company_id_companies_id_fk", + "tableFrom": "labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_company_settings": { + "name": "plugin_company_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "settings_json": { + "name": "settings_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_company_settings_company_idx": { + "name": "plugin_company_settings_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_plugin_idx": { + "name": "plugin_company_settings_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_company_plugin_uq": { + "name": "plugin_company_settings_company_plugin_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_company_settings_company_id_companies_id_fk": { + "name": "plugin_company_settings_company_id_companies_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_company_settings_plugin_id_plugins_id_fk": { + "name": "plugin_company_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_config": { + "name": "plugin_config", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "config_json": { + "name": "config_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_config_plugin_id_idx": { + "name": "plugin_config_plugin_id_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_config_plugin_id_plugins_id_fk": { + "name": "plugin_config_plugin_id_plugins_id_fk", + "tableFrom": "plugin_config", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_entities": { + "name": "plugin_entities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_entities_plugin_idx": { + "name": "plugin_entities_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_type_idx": { + "name": "plugin_entities_type_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_scope_idx": { + "name": "plugin_entities_scope_idx", + "columns": [ + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_external_idx": { + "name": "plugin_entities_external_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_entities_plugin_id_plugins_id_fk": { + "name": "plugin_entities_plugin_id_plugins_id_fk", + "tableFrom": "plugin_entities", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_job_runs": { + "name": "plugin_job_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "job_id": { + "name": "job_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logs": { + "name": "logs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_job_runs_job_idx": { + "name": "plugin_job_runs_job_idx", + "columns": [ + { + "expression": "job_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_plugin_idx": { + "name": "plugin_job_runs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_status_idx": { + "name": "plugin_job_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_job_runs_job_id_plugin_jobs_id_fk": { + "name": "plugin_job_runs_job_id_plugin_jobs_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugin_jobs", + "columnsFrom": [ + "job_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_job_runs_plugin_id_plugins_id_fk": { + "name": "plugin_job_runs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_jobs": { + "name": "plugin_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "job_key": { + "name": "job_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_jobs_plugin_idx": { + "name": "plugin_jobs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_next_run_idx": { + "name": "plugin_jobs_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_unique_idx": { + "name": "plugin_jobs_unique_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "job_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_jobs_plugin_id_plugins_id_fk": { + "name": "plugin_jobs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_jobs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_logs": { + "name": "plugin_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'info'" + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "meta": { + "name": "meta", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_logs_plugin_time_idx": { + "name": "plugin_logs_plugin_time_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_logs_level_idx": { + "name": "plugin_logs_level_idx", + "columns": [ + { + "expression": "level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_logs_plugin_id_plugins_id_fk": { + "name": "plugin_logs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_logs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_state": { + "name": "plugin_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "namespace": { + "name": "namespace", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "state_key": { + "name": "state_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value_json": { + "name": "value_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_state_plugin_scope_idx": { + "name": "plugin_state_plugin_scope_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_state_plugin_id_plugins_id_fk": { + "name": "plugin_state_plugin_id_plugins_id_fk", + "tableFrom": "plugin_state", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "plugin_state_unique_entry_idx": { + "name": "plugin_state_unique_entry_idx", + "nullsNotDistinct": true, + "columns": [ + "plugin_id", + "scope_kind", + "scope_id", + "namespace", + "state_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_webhook_deliveries": { + "name": "plugin_webhook_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "webhook_key": { + "name": "webhook_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "headers": { + "name": "headers", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_webhook_deliveries_plugin_idx": { + "name": "plugin_webhook_deliveries_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_status_idx": { + "name": "plugin_webhook_deliveries_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_key_idx": { + "name": "plugin_webhook_deliveries_key_idx", + "columns": [ + { + "expression": "webhook_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_webhook_deliveries_plugin_id_plugins_id_fk": { + "name": "plugin_webhook_deliveries_plugin_id_plugins_id_fk", + "tableFrom": "plugin_webhook_deliveries", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugins": { + "name": "plugins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "package_name": { + "name": "package_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "api_version": { + "name": "api_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "manifest_json": { + "name": "manifest_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'installed'" + }, + "install_order": { + "name": "install_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "package_path": { + "name": "package_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_at": { + "name": "installed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugins_plugin_key_idx": { + "name": "plugins_plugin_key_idx", + "columns": [ + { + "expression": "plugin_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugins_status_idx": { + "name": "plugins_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.principal_permission_grants": { + "name": "principal_permission_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_key": { + "name": "permission_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "granted_by_user_id": { + "name": "granted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "principal_permission_grants_unique_idx": { + "name": "principal_permission_grants_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "principal_permission_grants_company_permission_idx": { + "name": "principal_permission_grants_company_permission_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "principal_permission_grants_company_id_companies_id_fk": { + "name": "principal_permission_grants_company_id_companies_id_fk", + "tableFrom": "principal_permission_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_goals": { + "name": "project_goals", + "schema": "", + "columns": { + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_goals_project_idx": { + "name": "project_goals_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_goal_idx": { + "name": "project_goals_goal_idx", + "columns": [ + { + "expression": "goal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_company_idx": { + "name": "project_goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_goals_project_id_projects_id_fk": { + "name": "project_goals_project_id_projects_id_fk", + "tableFrom": "project_goals", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_goal_id_goals_id_fk": { + "name": "project_goals_goal_id_goals_id_fk", + "tableFrom": "project_goals", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_company_id_companies_id_fk": { + "name": "project_goals_company_id_companies_id_fk", + "tableFrom": "project_goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "project_goals_project_id_goal_id_pk": { + "name": "project_goals_project_id_goal_id_pk", + "columns": [ + "project_id", + "goal_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_workspaces": { + "name": "project_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_ref": { + "name": "repo_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_ref": { + "name": "default_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "setup_command": { + "name": "setup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cleanup_command": { + "name": "cleanup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_provider": { + "name": "remote_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_workspace_ref": { + "name": "remote_workspace_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_workspace_key": { + "name": "shared_workspace_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_workspaces_company_project_idx": { + "name": "project_workspaces_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_primary_idx": { + "name": "project_workspaces_project_primary_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_primary", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_source_type_idx": { + "name": "project_workspaces_project_source_type_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_company_shared_key_idx": { + "name": "project_workspaces_company_shared_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "shared_workspace_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_remote_ref_idx": { + "name": "project_workspaces_project_remote_ref_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_workspace_ref", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_workspaces_company_id_companies_id_fk": { + "name": "project_workspaces_company_id_companies_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "project_workspaces_project_id_projects_id_fk": { + "name": "project_workspaces_project_id_projects_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "lead_agent_id": { + "name": "lead_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_date": { + "name": "target_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_policy": { + "name": "execution_workspace_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "projects_company_idx": { + "name": "projects_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "projects_company_id_companies_id_fk": { + "name": "projects_company_id_companies_id_fk", + "tableFrom": "projects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_goal_id_goals_id_fk": { + "name": "projects_goal_id_goals_id_fk", + "tableFrom": "projects", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_lead_agent_id_agents_id_fk": { + "name": "projects_lead_agent_id_agents_id_fk", + "tableFrom": "projects", + "tableTo": "agents", + "columnsFrom": [ + "lead_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_runs": { + "name": "routine_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger_id": { + "name": "trigger_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "triggered_at": { + "name": "triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_payload": { + "name": "trigger_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "linked_issue_id": { + "name": "linked_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "coalesced_into_run_id": { + "name": "coalesced_into_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_runs_company_routine_idx": { + "name": "routine_runs_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idx": { + "name": "routine_runs_trigger_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_linked_issue_idx": { + "name": "routine_runs_linked_issue_idx", + "columns": [ + { + "expression": "linked_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idempotency_idx": { + "name": "routine_runs_trigger_idempotency_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_runs_company_id_companies_id_fk": { + "name": "routine_runs_company_id_companies_id_fk", + "tableFrom": "routine_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_routine_id_routines_id_fk": { + "name": "routine_runs_routine_id_routines_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_trigger_id_routine_triggers_id_fk": { + "name": "routine_runs_trigger_id_routine_triggers_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routine_triggers", + "columnsFrom": [ + "trigger_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_runs_linked_issue_id_issues_id_fk": { + "name": "routine_runs_linked_issue_id_issues_id_fk", + "tableFrom": "routine_runs", + "tableTo": "issues", + "columnsFrom": [ + "linked_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_triggers": { + "name": "routine_triggers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "public_id": { + "name": "public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "signing_mode": { + "name": "signing_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "replay_window_sec": { + "name": "replay_window_sec", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_rotated_at": { + "name": "last_rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_result": { + "name": "last_result", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_triggers_company_routine_idx": { + "name": "routine_triggers_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_company_kind_idx": { + "name": "routine_triggers_company_kind_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_next_run_idx": { + "name": "routine_triggers_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_idx": { + "name": "routine_triggers_public_id_idx", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_uq": { + "name": "routine_triggers_public_id_uq", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_triggers_company_id_companies_id_fk": { + "name": "routine_triggers_company_id_companies_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_routine_id_routines_id_fk": { + "name": "routine_triggers_routine_id_routines_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_secret_id_company_secrets_id_fk": { + "name": "routine_triggers_secret_id_company_secrets_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_created_by_agent_id_agents_id_fk": { + "name": "routine_triggers_created_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_updated_by_agent_id_agents_id_fk": { + "name": "routine_triggers_updated_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routines": { + "name": "routines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_issue_id": { + "name": "parent_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "concurrency_policy": { + "name": "concurrency_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'coalesce_if_active'" + }, + "catch_up_policy": { + "name": "catch_up_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'skip_missed'" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_triggered_at": { + "name": "last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_enqueued_at": { + "name": "last_enqueued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routines_company_status_idx": { + "name": "routines_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_assignee_idx": { + "name": "routines_company_assignee_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_project_idx": { + "name": "routines_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routines_company_id_companies_id_fk": { + "name": "routines_company_id_companies_id_fk", + "tableFrom": "routines", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_project_id_projects_id_fk": { + "name": "routines_project_id_projects_id_fk", + "tableFrom": "routines", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_goal_id_goals_id_fk": { + "name": "routines_goal_id_goals_id_fk", + "tableFrom": "routines", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_parent_issue_id_issues_id_fk": { + "name": "routines_parent_issue_id_issues_id_fk", + "tableFrom": "routines", + "tableTo": "issues", + "columnsFrom": [ + "parent_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_assignee_agent_id_agents_id_fk": { + "name": "routines_assignee_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "routines_created_by_agent_id_agents_id_fk": { + "name": "routines_created_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_updated_by_agent_id_agents_id_fk": { + "name": "routines_updated_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_operations": { + "name": "workspace_operations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_operations_company_run_started_idx": { + "name": "workspace_operations_company_run_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_operations_company_workspace_started_idx": { + "name": "workspace_operations_company_workspace_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_operations_company_id_companies_id_fk": { + "name": "workspace_operations_company_id_companies_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_operations_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_operations_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_runtime_services": { + "name": "workspace_runtime_services", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "service_name": { + "name": "service_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reuse_key": { + "name": "reuse_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "started_by_run_id": { + "name": "started_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stop_policy": { + "name": "stop_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_runtime_services_company_workspace_status_idx": { + "name": "workspace_runtime_services_company_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_execution_workspace_status_idx": { + "name": "workspace_runtime_services_company_execution_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_project_status_idx": { + "name": "workspace_runtime_services_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_run_idx": { + "name": "workspace_runtime_services_run_idx", + "columns": [ + { + "expression": "started_by_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_updated_idx": { + "name": "workspace_runtime_services_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_runtime_services_company_id_companies_id_fk": { + "name": "workspace_runtime_services_company_id_companies_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_id_projects_id_fk": { + "name": "workspace_runtime_services_project_id_projects_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk": { + "name": "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_issue_id_issues_id_fk": { + "name": "workspace_runtime_services_issue_id_issues_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_owner_agent_id_agents_id_fk": { + "name": "workspace_runtime_services_owner_agent_id_agents_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk": { + "name": "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "started_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/src/migrations/meta/0048_snapshot.json b/packages/db/src/migrations/meta/0048_snapshot.json new file mode 100644 index 00000000000..4b699f933e7 --- /dev/null +++ b/packages/db/src/migrations/meta/0048_snapshot.json @@ -0,0 +1,12546 @@ +{ + "id": "685fdfd9-6d11-440d-b468-fac28279fa46", + "prevId": "a0553dd4-5278-406f-bef7-b06597f92cf2", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.activity_log": { + "name": "activity_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "activity_log_company_created_idx": { + "name": "activity_log_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_run_id_idx": { + "name": "activity_log_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_entity_type_id_idx": { + "name": "activity_log_entity_type_id_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "activity_log_company_id_companies_id_fk": { + "name": "activity_log_company_id_companies_id_fk", + "tableFrom": "activity_log", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_agent_id_agents_id_fk": { + "name": "activity_log_agent_id_agents_id_fk", + "tableFrom": "activity_log", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_run_id_heartbeat_runs_id_fk": { + "name": "activity_log_run_id_heartbeat_runs_id_fk", + "tableFrom": "activity_log", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_api_keys": { + "name": "agent_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_api_keys_key_hash_idx": { + "name": "agent_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_api_keys_company_agent_idx": { + "name": "agent_api_keys_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_api_keys_agent_id_agents_id_fk": { + "name": "agent_api_keys_agent_id_agents_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_api_keys_company_id_companies_id_fk": { + "name": "agent_api_keys_company_id_companies_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_config_revisions": { + "name": "agent_config_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'patch'" + }, + "rolled_back_from_revision_id": { + "name": "rolled_back_from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "changed_keys": { + "name": "changed_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "before_config": { + "name": "before_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "after_config": { + "name": "after_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_config_revisions_company_agent_created_idx": { + "name": "agent_config_revisions_company_agent_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_config_revisions_agent_created_idx": { + "name": "agent_config_revisions_agent_created_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_config_revisions_company_id_companies_id_fk": { + "name": "agent_config_revisions_company_id_companies_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_config_revisions_agent_id_agents_id_fk": { + "name": "agent_config_revisions_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_config_revisions_created_by_agent_id_agents_id_fk": { + "name": "agent_config_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_runtime_state": { + "name": "agent_runtime_state", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_json": { + "name": "state_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_run_status": { + "name": "last_run_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total_input_tokens": { + "name": "total_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_output_tokens": { + "name": "total_output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cached_input_tokens": { + "name": "total_cached_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost_cents": { + "name": "total_cost_cents", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_runtime_state_company_agent_idx": { + "name": "agent_runtime_state_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_runtime_state_company_updated_idx": { + "name": "agent_runtime_state_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_runtime_state_agent_id_agents_id_fk": { + "name": "agent_runtime_state_agent_id_agents_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_runtime_state_company_id_companies_id_fk": { + "name": "agent_runtime_state_company_id_companies_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_task_sessions": { + "name": "agent_task_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "task_key": { + "name": "task_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_params_json": { + "name": "session_params_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_display_id": { + "name": "session_display_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_task_sessions_company_agent_adapter_task_uniq": { + "name": "agent_task_sessions_company_agent_adapter_task_uniq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "adapter_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_agent_updated_idx": { + "name": "agent_task_sessions_company_agent_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_task_updated_idx": { + "name": "agent_task_sessions_company_task_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_task_sessions_company_id_companies_id_fk": { + "name": "agent_task_sessions_company_id_companies_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_agent_id_agents_id_fk": { + "name": "agent_task_sessions_agent_id_agents_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_last_run_id_heartbeat_runs_id_fk": { + "name": "agent_task_sessions_last_run_id_heartbeat_runs_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "last_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_wakeup_requests": { + "name": "agent_wakeup_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "coalesced_count": { + "name": "coalesced_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "requested_by_actor_type": { + "name": "requested_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by_actor_id": { + "name": "requested_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_wakeup_requests_company_agent_status_idx": { + "name": "agent_wakeup_requests_company_agent_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_company_requested_idx": { + "name": "agent_wakeup_requests_company_requested_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_agent_requested_idx": { + "name": "agent_wakeup_requests_agent_requested_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_wakeup_requests_company_id_companies_id_fk": { + "name": "agent_wakeup_requests_company_id_companies_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_wakeup_requests_agent_id_agents_id_fk": { + "name": "agent_wakeup_requests_agent_id_agents_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "reports_to": { + "name": "reports_to", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'process'" + }, + "adapter_config": { + "name": "adapter_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "runtime_config": { + "name": "runtime_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_heartbeat_at": { + "name": "last_heartbeat_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agents_company_status_idx": { + "name": "agents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_company_reports_to_idx": { + "name": "agents_company_reports_to_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reports_to", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agents_company_id_companies_id_fk": { + "name": "agents_company_id_companies_id_fk", + "tableFrom": "agents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agents_reports_to_agents_id_fk": { + "name": "agents_reports_to_agents_id_fk", + "tableFrom": "agents", + "tableTo": "agents", + "columnsFrom": [ + "reports_to" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approval_comments": { + "name": "approval_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approval_comments_company_idx": { + "name": "approval_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_idx": { + "name": "approval_comments_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_created_idx": { + "name": "approval_comments_approval_created_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approval_comments_company_id_companies_id_fk": { + "name": "approval_comments_company_id_companies_id_fk", + "tableFrom": "approval_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_approval_id_approvals_id_fk": { + "name": "approval_comments_approval_id_approvals_id_fk", + "tableFrom": "approval_comments", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_author_agent_id_agents_id_fk": { + "name": "approval_comments_author_agent_id_agents_id_fk", + "tableFrom": "approval_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approvals": { + "name": "approvals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_by_agent_id": { + "name": "requested_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_by_user_id": { + "name": "requested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "decision_note": { + "name": "decision_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approvals_company_status_type_idx": { + "name": "approvals_company_status_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approvals_company_id_companies_id_fk": { + "name": "approvals_company_id_companies_id_fk", + "tableFrom": "approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approvals_requested_by_agent_id_agents_id_fk": { + "name": "approvals_requested_by_agent_id_agents_id_fk", + "tableFrom": "approvals", + "tableTo": "agents", + "columnsFrom": [ + "requested_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.assets": { + "name": "assets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "object_key": { + "name": "object_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "original_filename": { + "name": "original_filename", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "assets_company_created_idx": { + "name": "assets_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_provider_idx": { + "name": "assets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_object_key_uq": { + "name": "assets_company_object_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "assets_company_id_companies_id_fk": { + "name": "assets_company_id_companies_id_fk", + "tableFrom": "assets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "assets_created_by_agent_id_agents_id_fk": { + "name": "assets_created_by_agent_id_agents_id_fk", + "tableFrom": "assets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.board_api_keys": { + "name": "board_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "board_api_keys_key_hash_idx": { + "name": "board_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "board_api_keys_user_idx": { + "name": "board_api_keys_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "board_api_keys_user_id_user_id_fk": { + "name": "board_api_keys_user_id_user_id_fk", + "tableFrom": "board_api_keys", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_incidents": { + "name": "budget_incidents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "policy_id": { + "name": "policy_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_end": { + "name": "window_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "threshold_type": { + "name": "threshold_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_limit": { + "name": "amount_limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "amount_observed": { + "name": "amount_observed", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_incidents_company_status_idx": { + "name": "budget_incidents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_company_scope_idx": { + "name": "budget_incidents_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_policy_window_threshold_idx": { + "name": "budget_incidents_policy_window_threshold_idx", + "columns": [ + { + "expression": "policy_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "threshold_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"budget_incidents\".\"status\" <> 'dismissed'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_incidents_company_id_companies_id_fk": { + "name": "budget_incidents_company_id_companies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_policy_id_budget_policies_id_fk": { + "name": "budget_incidents_policy_id_budget_policies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "budget_policies", + "columnsFrom": [ + "policy_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_approval_id_approvals_id_fk": { + "name": "budget_incidents_approval_id_approvals_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_policies": { + "name": "budget_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'billed_cents'" + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "warn_percent": { + "name": "warn_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 80 + }, + "hard_stop_enabled": { + "name": "hard_stop_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "notify_enabled": { + "name": "notify_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_policies_company_scope_active_idx": { + "name": "budget_policies_company_scope_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_window_idx": { + "name": "budget_policies_company_window_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_scope_metric_unique_idx": { + "name": "budget_policies_company_scope_metric_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_policies_company_id_companies_id_fk": { + "name": "budget_policies_company_id_companies_id_fk", + "tableFrom": "budget_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cli_auth_challenges": { + "name": "cli_auth_challenges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_hash": { + "name": "secret_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_access": { + "name": "requested_access", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'board'" + }, + "requested_company_id": { + "name": "requested_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "pending_key_hash": { + "name": "pending_key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pending_key_name": { + "name": "pending_key_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "board_api_key_id": { + "name": "board_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cli_auth_challenges_secret_hash_idx": { + "name": "cli_auth_challenges_secret_hash_idx", + "columns": [ + { + "expression": "secret_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_approved_by_idx": { + "name": "cli_auth_challenges_approved_by_idx", + "columns": [ + { + "expression": "approved_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_requested_company_idx": { + "name": "cli_auth_challenges_requested_company_idx", + "columns": [ + { + "expression": "requested_company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cli_auth_challenges_requested_company_id_companies_id_fk": { + "name": "cli_auth_challenges_requested_company_id_companies_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "companies", + "columnsFrom": [ + "requested_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_approved_by_user_id_user_id_fk": { + "name": "cli_auth_challenges_approved_by_user_id_user_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "user", + "columnsFrom": [ + "approved_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk": { + "name": "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "board_api_keys", + "columnsFrom": [ + "board_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.companies": { + "name": "companies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "issue_prefix": { + "name": "issue_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'PAP'" + }, + "issue_counter": { + "name": "issue_counter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "require_board_approval_for_new_agents": { + "name": "require_board_approval_for_new_agents", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "feedback_data_sharing_enabled": { + "name": "feedback_data_sharing_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "feedback_data_sharing_consent_at": { + "name": "feedback_data_sharing_consent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "feedback_data_sharing_consent_by_user_id": { + "name": "feedback_data_sharing_consent_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feedback_data_sharing_terms_version": { + "name": "feedback_data_sharing_terms_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "brand_color": { + "name": "brand_color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "companies_issue_prefix_idx": { + "name": "companies_issue_prefix_idx", + "columns": [ + { + "expression": "issue_prefix", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_logos": { + "name": "company_logos", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_logos_company_uq": { + "name": "company_logos_company_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_logos_asset_uq": { + "name": "company_logos_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_logos_company_id_companies_id_fk": { + "name": "company_logos_company_id_companies_id_fk", + "tableFrom": "company_logos", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_logos_asset_id_assets_id_fk": { + "name": "company_logos_asset_id_assets_id_fk", + "tableFrom": "company_logos", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_memberships": { + "name": "company_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "membership_role": { + "name": "membership_role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_memberships_company_principal_unique_idx": { + "name": "company_memberships_company_principal_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_principal_status_idx": { + "name": "company_memberships_principal_status_idx", + "columns": [ + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_company_status_idx": { + "name": "company_memberships_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_memberships_company_id_companies_id_fk": { + "name": "company_memberships_company_id_companies_id_fk", + "tableFrom": "company_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_versions": { + "name": "company_secret_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "material": { + "name": "material", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "value_sha256": { + "name": "value_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "company_secret_versions_secret_idx": { + "name": "company_secret_versions_secret_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_value_sha256_idx": { + "name": "company_secret_versions_value_sha256_idx", + "columns": [ + { + "expression": "value_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_secret_version_uq": { + "name": "company_secret_versions_secret_version_uq", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_versions_secret_id_company_secrets_id_fk": { + "name": "company_secret_versions_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_versions_created_by_agent_id_agents_id_fk": { + "name": "company_secret_versions_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secrets": { + "name": "company_secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_encrypted'" + }, + "external_ref": { + "name": "external_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "latest_version": { + "name": "latest_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secrets_company_idx": { + "name": "company_secrets_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_provider_idx": { + "name": "company_secrets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_name_uq": { + "name": "company_secrets_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secrets_company_id_companies_id_fk": { + "name": "company_secrets_company_id_companies_id_fk", + "tableFrom": "company_secrets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secrets_created_by_agent_id_agents_id_fk": { + "name": "company_secrets_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secrets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skills": { + "name": "company_skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "markdown": { + "name": "markdown", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "source_locator": { + "name": "source_locator", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_ref": { + "name": "source_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trust_level": { + "name": "trust_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown_only'" + }, + "compatibility": { + "name": "compatibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'compatible'" + }, + "file_inventory": { + "name": "file_inventory", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skills_company_key_idx": { + "name": "company_skills_company_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_name_idx": { + "name": "company_skills_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skills_company_id_companies_id_fk": { + "name": "company_skills_company_id_companies_id_fk", + "tableFrom": "company_skills", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cost_events": { + "name": "cost_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "billing_type": { + "name": "billing_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cached_input_tokens": { + "name": "cached_input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cost_cents": { + "name": "cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cost_events_company_occurred_idx": { + "name": "cost_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_agent_occurred_idx": { + "name": "cost_events_company_agent_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_provider_occurred_idx": { + "name": "cost_events_company_provider_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_biller_occurred_idx": { + "name": "cost_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_heartbeat_run_idx": { + "name": "cost_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cost_events_company_id_companies_id_fk": { + "name": "cost_events_company_id_companies_id_fk", + "tableFrom": "cost_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_agent_id_agents_id_fk": { + "name": "cost_events_agent_id_agents_id_fk", + "tableFrom": "cost_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_issue_id_issues_id_fk": { + "name": "cost_events_issue_id_issues_id_fk", + "tableFrom": "cost_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_project_id_projects_id_fk": { + "name": "cost_events_project_id_projects_id_fk", + "tableFrom": "cost_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_goal_id_goals_id_fk": { + "name": "cost_events_goal_id_goals_id_fk", + "tableFrom": "cost_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "cost_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "cost_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_revisions": { + "name": "document_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_revisions_document_revision_uq": { + "name": "document_revisions_document_revision_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_revisions_company_document_created_idx": { + "name": "document_revisions_company_document_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_revisions_company_id_companies_id_fk": { + "name": "document_revisions_company_id_companies_id_fk", + "tableFrom": "document_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_revisions_document_id_documents_id_fk": { + "name": "document_revisions_document_id_documents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_revisions_created_by_agent_id_agents_id_fk": { + "name": "document_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_revisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "document_revisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "document_revisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.documents": { + "name": "documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "latest_body": { + "name": "latest_body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "latest_revision_id": { + "name": "latest_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "latest_revision_number": { + "name": "latest_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "documents_company_updated_idx": { + "name": "documents_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_company_created_idx": { + "name": "documents_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "documents_company_id_companies_id_fk": { + "name": "documents_company_id_companies_id_fk", + "tableFrom": "documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "documents_created_by_agent_id_agents_id_fk": { + "name": "documents_created_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "documents_updated_by_agent_id_agents_id_fk": { + "name": "documents_updated_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_workspaces": { + "name": "execution_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "strategy_type": { + "name": "strategy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_ref": { + "name": "base_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch_name": { + "name": "branch_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_fs'" + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "derived_from_execution_workspace_id": { + "name": "derived_from_execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "opened_at": { + "name": "opened_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_eligible_at": { + "name": "cleanup_eligible_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_reason": { + "name": "cleanup_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_workspaces_company_project_status_idx": { + "name": "execution_workspaces_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_project_workspace_status_idx": { + "name": "execution_workspaces_company_project_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_source_issue_idx": { + "name": "execution_workspaces_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_last_used_idx": { + "name": "execution_workspaces_company_last_used_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_branch_idx": { + "name": "execution_workspaces_company_branch_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspaces_company_id_companies_id_fk": { + "name": "execution_workspaces_company_id_companies_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "execution_workspaces_project_id_projects_id_fk": { + "name": "execution_workspaces_project_id_projects_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspaces_project_workspace_id_project_workspaces_id_fk": { + "name": "execution_workspaces_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_source_issue_id_issues_id_fk": { + "name": "execution_workspaces_source_issue_id_issues_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk": { + "name": "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "derived_from_execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feedback_exports": { + "name": "feedback_exports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "feedback_vote_id": { + "name": "feedback_vote_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_only'" + }, + "destination": { + "name": "destination", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "export_id": { + "name": "export_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consent_version": { + "name": "consent_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "schema_version": { + "name": "schema_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-envelope-v2'" + }, + "bundle_version": { + "name": "bundle_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-bundle-v2'" + }, + "payload_version": { + "name": "payload_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-v1'" + }, + "payload_digest": { + "name": "payload_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload_snapshot": { + "name": "payload_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "target_summary": { + "name": "target_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "redaction_summary": { + "name": "redaction_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempted_at": { + "name": "last_attempted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "exported_at": { + "name": "exported_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_exports_feedback_vote_idx": { + "name": "feedback_exports_feedback_vote_idx", + "columns": [ + { + "expression": "feedback_vote_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_created_idx": { + "name": "feedback_exports_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_status_idx": { + "name": "feedback_exports_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_issue_idx": { + "name": "feedback_exports_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_project_idx": { + "name": "feedback_exports_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_author_idx": { + "name": "feedback_exports_company_author_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_exports_company_id_companies_id_fk": { + "name": "feedback_exports_company_id_companies_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "feedback_exports_feedback_vote_id_feedback_votes_id_fk": { + "name": "feedback_exports_feedback_vote_id_feedback_votes_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "feedback_votes", + "columnsFrom": [ + "feedback_vote_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_exports_issue_id_issues_id_fk": { + "name": "feedback_exports_issue_id_issues_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_exports_project_id_projects_id_fk": { + "name": "feedback_exports_project_id_projects_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feedback_votes": { + "name": "feedback_votes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_with_labs": { + "name": "shared_with_labs", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "shared_at": { + "name": "shared_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "consent_version": { + "name": "consent_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redaction_summary": { + "name": "redaction_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_votes_company_issue_idx": { + "name": "feedback_votes_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_issue_target_idx": { + "name": "feedback_votes_issue_target_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_author_idx": { + "name": "feedback_votes_author_idx", + "columns": [ + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_company_target_author_idx": { + "name": "feedback_votes_company_target_author_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_votes_company_id_companies_id_fk": { + "name": "feedback_votes_company_id_companies_id_fk", + "tableFrom": "feedback_votes", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "feedback_votes_issue_id_issues_id_fk": { + "name": "feedback_votes_issue_id_issues_id_fk", + "tableFrom": "feedback_votes", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.finance_events": { + "name": "finance_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cost_event_id": { + "name": "cost_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "event_kind": { + "name": "event_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'debit'" + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_adapter_type": { + "name": "execution_adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pricing_tier": { + "name": "pricing_tier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "estimated": { + "name": "estimated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "external_invoice_id": { + "name": "external_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata_json": { + "name": "metadata_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "finance_events_company_occurred_idx": { + "name": "finance_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_biller_occurred_idx": { + "name": "finance_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_kind_occurred_idx": { + "name": "finance_events_company_kind_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_direction_occurred_idx": { + "name": "finance_events_company_direction_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "direction", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_heartbeat_run_idx": { + "name": "finance_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_cost_event_idx": { + "name": "finance_events_company_cost_event_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "finance_events_company_id_companies_id_fk": { + "name": "finance_events_company_id_companies_id_fk", + "tableFrom": "finance_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_agent_id_agents_id_fk": { + "name": "finance_events_agent_id_agents_id_fk", + "tableFrom": "finance_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_issue_id_issues_id_fk": { + "name": "finance_events_issue_id_issues_id_fk", + "tableFrom": "finance_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_project_id_projects_id_fk": { + "name": "finance_events_project_id_projects_id_fk", + "tableFrom": "finance_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_goal_id_goals_id_fk": { + "name": "finance_events_goal_id_goals_id_fk", + "tableFrom": "finance_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "finance_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "finance_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_cost_event_id_cost_events_id_fk": { + "name": "finance_events_cost_event_id_cost_events_id_fk", + "tableFrom": "finance_events", + "tableTo": "cost_events", + "columnsFrom": [ + "cost_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.goals": { + "name": "goals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'task'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'planned'" + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "goals_company_idx": { + "name": "goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "goals_company_id_companies_id_fk": { + "name": "goals_company_id_companies_id_fk", + "tableFrom": "goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_parent_id_goals_id_fk": { + "name": "goals_parent_id_goals_id_fk", + "tableFrom": "goals", + "tableTo": "goals", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_owner_agent_id_agents_id_fk": { + "name": "goals_owner_agent_id_agents_id_fk", + "tableFrom": "goals", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_run_events": { + "name": "heartbeat_run_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stream": { + "name": "stream", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_run_events_run_seq_idx": { + "name": "heartbeat_run_events_run_seq_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_run_idx": { + "name": "heartbeat_run_events_company_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_created_idx": { + "name": "heartbeat_run_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_run_events_company_id_companies_id_fk": { + "name": "heartbeat_run_events_company_id_companies_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_events_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_agent_id_agents_id_fk": { + "name": "heartbeat_run_events_agent_id_agents_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_runs": { + "name": "heartbeat_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invocation_source": { + "name": "invocation_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'on_demand'" + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wakeup_request_id": { + "name": "wakeup_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "signal": { + "name": "signal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "usage_json": { + "name": "usage_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_json": { + "name": "result_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_id_before": { + "name": "session_id_before", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id_after": { + "name": "session_id_after", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_run_id": { + "name": "external_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "process_pid": { + "name": "process_pid", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "process_started_at": { + "name": "process_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "retry_of_run_id": { + "name": "retry_of_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "process_loss_retry_count": { + "name": "process_loss_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "context_snapshot": { + "name": "context_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_runs_company_agent_started_idx": { + "name": "heartbeat_runs_company_agent_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_runs_company_id_companies_id_fk": { + "name": "heartbeat_runs_company_id_companies_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_agent_id_agents_id_fk": { + "name": "heartbeat_runs_agent_id_agents_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk": { + "name": "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agent_wakeup_requests", + "columnsFrom": [ + "wakeup_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "retry_of_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_settings": { + "name": "instance_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "singleton_key": { + "name": "singleton_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "general": { + "name": "general", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "experimental": { + "name": "experimental", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_settings_singleton_key_idx": { + "name": "instance_settings_singleton_key_idx", + "columns": [ + { + "expression": "singleton_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_user_roles": { + "name": "instance_user_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'instance_admin'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_user_roles_user_role_unique_idx": { + "name": "instance_user_roles_user_role_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "instance_user_roles_role_idx": { + "name": "instance_user_roles_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invites": { + "name": "invites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "invite_type": { + "name": "invite_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company_join'" + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allowed_join_types": { + "name": "allowed_join_types", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'both'" + }, + "defaults_payload": { + "name": "defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "invited_by_user_id": { + "name": "invited_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invites_token_hash_unique_idx": { + "name": "invites_token_hash_unique_idx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invites_company_invite_state_idx": { + "name": "invites_company_invite_state_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invite_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revoked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invites_company_id_companies_id_fk": { + "name": "invites_company_id_companies_id_fk", + "tableFrom": "invites", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_approvals": { + "name": "issue_approvals", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "linked_by_agent_id": { + "name": "linked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "linked_by_user_id": { + "name": "linked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_approvals_issue_idx": { + "name": "issue_approvals_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_approval_idx": { + "name": "issue_approvals_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_company_idx": { + "name": "issue_approvals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_approvals_company_id_companies_id_fk": { + "name": "issue_approvals_company_id_companies_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_approvals_issue_id_issues_id_fk": { + "name": "issue_approvals_issue_id_issues_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_approval_id_approvals_id_fk": { + "name": "issue_approvals_approval_id_approvals_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_linked_by_agent_id_agents_id_fk": { + "name": "issue_approvals_linked_by_agent_id_agents_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "agents", + "columnsFrom": [ + "linked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_approvals_pk": { + "name": "issue_approvals_pk", + "columns": [ + "issue_id", + "approval_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_attachments": { + "name": "issue_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_comment_id": { + "name": "issue_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_attachments_company_issue_idx": { + "name": "issue_attachments_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_issue_comment_idx": { + "name": "issue_attachments_issue_comment_idx", + "columns": [ + { + "expression": "issue_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_asset_uq": { + "name": "issue_attachments_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_attachments_company_id_companies_id_fk": { + "name": "issue_attachments_company_id_companies_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_attachments_issue_id_issues_id_fk": { + "name": "issue_attachments_issue_id_issues_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_asset_id_assets_id_fk": { + "name": "issue_attachments_asset_id_assets_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_issue_comment_id_issue_comments_id_fk": { + "name": "issue_attachments_issue_comment_id_issue_comments_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issue_comments", + "columnsFrom": [ + "issue_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_comments": { + "name": "issue_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_comments_issue_idx": { + "name": "issue_comments_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_idx": { + "name": "issue_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_issue_created_at_idx": { + "name": "issue_comments_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_author_issue_created_at_idx": { + "name": "issue_comments_company_author_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_comments_company_id_companies_id_fk": { + "name": "issue_comments_company_id_companies_id_fk", + "tableFrom": "issue_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_issue_id_issues_id_fk": { + "name": "issue_comments_issue_id_issues_id_fk", + "tableFrom": "issue_comments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_author_agent_id_agents_id_fk": { + "name": "issue_comments_author_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_comments_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_documents": { + "name": "issue_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_documents_company_issue_key_uq": { + "name": "issue_documents_company_issue_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_document_uq": { + "name": "issue_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_company_issue_updated_idx": { + "name": "issue_documents_company_issue_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_documents_company_id_companies_id_fk": { + "name": "issue_documents_company_id_companies_id_fk", + "tableFrom": "issue_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_documents_issue_id_issues_id_fk": { + "name": "issue_documents_issue_id_issues_id_fk", + "tableFrom": "issue_documents", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_documents_document_id_documents_id_fk": { + "name": "issue_documents_document_id_documents_id_fk", + "tableFrom": "issue_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_inbox_archives": { + "name": "issue_inbox_archives", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_inbox_archives_company_issue_idx": { + "name": "issue_inbox_archives_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_user_idx": { + "name": "issue_inbox_archives_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_issue_user_idx": { + "name": "issue_inbox_archives_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_inbox_archives_company_id_companies_id_fk": { + "name": "issue_inbox_archives_company_id_companies_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_inbox_archives_issue_id_issues_id_fk": { + "name": "issue_inbox_archives_issue_id_issues_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_labels": { + "name": "issue_labels", + "schema": "", + "columns": { + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label_id": { + "name": "label_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_labels_issue_idx": { + "name": "issue_labels_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_label_idx": { + "name": "issue_labels_label_idx", + "columns": [ + { + "expression": "label_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_company_idx": { + "name": "issue_labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_labels_issue_id_issues_id_fk": { + "name": "issue_labels_issue_id_issues_id_fk", + "tableFrom": "issue_labels", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_label_id_labels_id_fk": { + "name": "issue_labels_label_id_labels_id_fk", + "tableFrom": "issue_labels", + "tableTo": "labels", + "columnsFrom": [ + "label_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_company_id_companies_id_fk": { + "name": "issue_labels_company_id_companies_id_fk", + "tableFrom": "issue_labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_labels_pk": { + "name": "issue_labels_pk", + "columns": [ + "issue_id", + "label_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_read_states": { + "name": "issue_read_states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_read_at": { + "name": "last_read_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_read_states_company_issue_idx": { + "name": "issue_read_states_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_user_idx": { + "name": "issue_read_states_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_issue_user_idx": { + "name": "issue_read_states_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_read_states_company_id_companies_id_fk": { + "name": "issue_read_states_company_id_companies_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_read_states_issue_id_issues_id_fk": { + "name": "issue_read_states_issue_id_issues_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_work_products": { + "name": "issue_work_products", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "runtime_service_id": { + "name": "runtime_service_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "review_state": { + "name": "review_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_work_products_company_issue_type_idx": { + "name": "issue_work_products_company_issue_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_execution_workspace_type_idx": { + "name": "issue_work_products_company_execution_workspace_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_provider_external_id_idx": { + "name": "issue_work_products_company_provider_external_id_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_updated_idx": { + "name": "issue_work_products_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_work_products_company_id_companies_id_fk": { + "name": "issue_work_products_company_id_companies_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_work_products_project_id_projects_id_fk": { + "name": "issue_work_products_project_id_projects_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_issue_id_issues_id_fk": { + "name": "issue_work_products_issue_id_issues_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_work_products_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issue_work_products_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk": { + "name": "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "workspace_runtime_services", + "columnsFrom": [ + "runtime_service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_work_products_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issues": { + "name": "issues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assignee_user_id": { + "name": "assignee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "checkout_run_id": { + "name": "checkout_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_run_id": { + "name": "execution_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_agent_name_key": { + "name": "execution_agent_name_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_locked_at": { + "name": "execution_locked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_number": { + "name": "issue_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_kind": { + "name": "origin_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "origin_id": { + "name": "origin_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_depth": { + "name": "request_depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_adapter_overrides": { + "name": "assignee_adapter_overrides", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_preference": { + "name": "execution_workspace_preference", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_settings": { + "name": "execution_workspace_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "hidden_at": { + "name": "hidden_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issues_company_status_idx": { + "name": "issues_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_status_idx": { + "name": "issues_company_assignee_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_user_status_idx": { + "name": "issues_company_assignee_user_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_parent_idx": { + "name": "issues_company_parent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_idx": { + "name": "issues_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_origin_idx": { + "name": "issues_company_origin_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_workspace_idx": { + "name": "issues_company_project_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_execution_workspace_idx": { + "name": "issues_company_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_identifier_idx": { + "name": "issues_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_open_routine_execution_uq": { + "name": "issues_open_routine_execution_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'routine_execution'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"execution_run_id\" is not null\n and \"issues\".\"status\" in ('backlog', 'todo', 'in_progress', 'in_review', 'blocked')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issues_company_id_companies_id_fk": { + "name": "issues_company_id_companies_id_fk", + "tableFrom": "issues", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_id_projects_id_fk": { + "name": "issues_project_id_projects_id_fk", + "tableFrom": "issues", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_workspace_id_project_workspaces_id_fk": { + "name": "issues_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_goal_id_goals_id_fk": { + "name": "issues_goal_id_goals_id_fk", + "tableFrom": "issues", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_parent_id_issues_id_fk": { + "name": "issues_parent_id_issues_id_fk", + "tableFrom": "issues", + "tableTo": "issues", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_assignee_agent_id_agents_id_fk": { + "name": "issues_assignee_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_checkout_run_id_heartbeat_runs_id_fk": { + "name": "issues_checkout_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "checkout_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_execution_run_id_heartbeat_runs_id_fk": { + "name": "issues_execution_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "execution_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_created_by_agent_id_agents_id_fk": { + "name": "issues_created_by_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issues_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.join_requests": { + "name": "join_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "invite_id": { + "name": "invite_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_type": { + "name": "request_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending_approval'" + }, + "request_ip": { + "name": "request_ip", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requesting_user_id": { + "name": "requesting_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_email_snapshot": { + "name": "request_email_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_name": { + "name": "agent_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_defaults_payload": { + "name": "agent_defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "claim_secret_hash": { + "name": "claim_secret_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_secret_expires_at": { + "name": "claim_secret_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claim_secret_consumed_at": { + "name": "claim_secret_consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_agent_id": { + "name": "created_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "rejected_by_user_id": { + "name": "rejected_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejected_at": { + "name": "rejected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "join_requests_invite_unique_idx": { + "name": "join_requests_invite_unique_idx", + "columns": [ + { + "expression": "invite_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_company_status_type_created_idx": { + "name": "join_requests_company_status_type_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "join_requests_invite_id_invites_id_fk": { + "name": "join_requests_invite_id_invites_id_fk", + "tableFrom": "join_requests", + "tableTo": "invites", + "columnsFrom": [ + "invite_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_company_id_companies_id_fk": { + "name": "join_requests_company_id_companies_id_fk", + "tableFrom": "join_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_created_agent_id_agents_id_fk": { + "name": "join_requests_created_agent_id_agents_id_fk", + "tableFrom": "join_requests", + "tableTo": "agents", + "columnsFrom": [ + "created_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.labels": { + "name": "labels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "labels_company_idx": { + "name": "labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "labels_company_name_idx": { + "name": "labels_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "labels_company_id_companies_id_fk": { + "name": "labels_company_id_companies_id_fk", + "tableFrom": "labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_company_settings": { + "name": "plugin_company_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "settings_json": { + "name": "settings_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_company_settings_company_idx": { + "name": "plugin_company_settings_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_plugin_idx": { + "name": "plugin_company_settings_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_company_plugin_uq": { + "name": "plugin_company_settings_company_plugin_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_company_settings_company_id_companies_id_fk": { + "name": "plugin_company_settings_company_id_companies_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_company_settings_plugin_id_plugins_id_fk": { + "name": "plugin_company_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_config": { + "name": "plugin_config", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "config_json": { + "name": "config_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_config_plugin_id_idx": { + "name": "plugin_config_plugin_id_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_config_plugin_id_plugins_id_fk": { + "name": "plugin_config_plugin_id_plugins_id_fk", + "tableFrom": "plugin_config", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_entities": { + "name": "plugin_entities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_entities_plugin_idx": { + "name": "plugin_entities_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_type_idx": { + "name": "plugin_entities_type_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_scope_idx": { + "name": "plugin_entities_scope_idx", + "columns": [ + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_external_idx": { + "name": "plugin_entities_external_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_entities_plugin_id_plugins_id_fk": { + "name": "plugin_entities_plugin_id_plugins_id_fk", + "tableFrom": "plugin_entities", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_job_runs": { + "name": "plugin_job_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "job_id": { + "name": "job_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logs": { + "name": "logs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_job_runs_job_idx": { + "name": "plugin_job_runs_job_idx", + "columns": [ + { + "expression": "job_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_plugin_idx": { + "name": "plugin_job_runs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_status_idx": { + "name": "plugin_job_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_job_runs_job_id_plugin_jobs_id_fk": { + "name": "plugin_job_runs_job_id_plugin_jobs_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugin_jobs", + "columnsFrom": [ + "job_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_job_runs_plugin_id_plugins_id_fk": { + "name": "plugin_job_runs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_jobs": { + "name": "plugin_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "job_key": { + "name": "job_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_jobs_plugin_idx": { + "name": "plugin_jobs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_next_run_idx": { + "name": "plugin_jobs_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_unique_idx": { + "name": "plugin_jobs_unique_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "job_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_jobs_plugin_id_plugins_id_fk": { + "name": "plugin_jobs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_jobs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_logs": { + "name": "plugin_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'info'" + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "meta": { + "name": "meta", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_logs_plugin_time_idx": { + "name": "plugin_logs_plugin_time_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_logs_level_idx": { + "name": "plugin_logs_level_idx", + "columns": [ + { + "expression": "level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_logs_plugin_id_plugins_id_fk": { + "name": "plugin_logs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_logs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_state": { + "name": "plugin_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "namespace": { + "name": "namespace", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "state_key": { + "name": "state_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value_json": { + "name": "value_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_state_plugin_scope_idx": { + "name": "plugin_state_plugin_scope_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_state_plugin_id_plugins_id_fk": { + "name": "plugin_state_plugin_id_plugins_id_fk", + "tableFrom": "plugin_state", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "plugin_state_unique_entry_idx": { + "name": "plugin_state_unique_entry_idx", + "nullsNotDistinct": true, + "columns": [ + "plugin_id", + "scope_kind", + "scope_id", + "namespace", + "state_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_webhook_deliveries": { + "name": "plugin_webhook_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "webhook_key": { + "name": "webhook_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "headers": { + "name": "headers", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_webhook_deliveries_plugin_idx": { + "name": "plugin_webhook_deliveries_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_status_idx": { + "name": "plugin_webhook_deliveries_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_key_idx": { + "name": "plugin_webhook_deliveries_key_idx", + "columns": [ + { + "expression": "webhook_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_webhook_deliveries_plugin_id_plugins_id_fk": { + "name": "plugin_webhook_deliveries_plugin_id_plugins_id_fk", + "tableFrom": "plugin_webhook_deliveries", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugins": { + "name": "plugins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "package_name": { + "name": "package_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "api_version": { + "name": "api_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "manifest_json": { + "name": "manifest_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'installed'" + }, + "install_order": { + "name": "install_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "package_path": { + "name": "package_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_at": { + "name": "installed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugins_plugin_key_idx": { + "name": "plugins_plugin_key_idx", + "columns": [ + { + "expression": "plugin_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugins_status_idx": { + "name": "plugins_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.principal_permission_grants": { + "name": "principal_permission_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_key": { + "name": "permission_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "granted_by_user_id": { + "name": "granted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "principal_permission_grants_unique_idx": { + "name": "principal_permission_grants_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "principal_permission_grants_company_permission_idx": { + "name": "principal_permission_grants_company_permission_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "principal_permission_grants_company_id_companies_id_fk": { + "name": "principal_permission_grants_company_id_companies_id_fk", + "tableFrom": "principal_permission_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_goals": { + "name": "project_goals", + "schema": "", + "columns": { + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_goals_project_idx": { + "name": "project_goals_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_goal_idx": { + "name": "project_goals_goal_idx", + "columns": [ + { + "expression": "goal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_company_idx": { + "name": "project_goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_goals_project_id_projects_id_fk": { + "name": "project_goals_project_id_projects_id_fk", + "tableFrom": "project_goals", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_goal_id_goals_id_fk": { + "name": "project_goals_goal_id_goals_id_fk", + "tableFrom": "project_goals", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_company_id_companies_id_fk": { + "name": "project_goals_company_id_companies_id_fk", + "tableFrom": "project_goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "project_goals_project_id_goal_id_pk": { + "name": "project_goals_project_id_goal_id_pk", + "columns": [ + "project_id", + "goal_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_workspaces": { + "name": "project_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_ref": { + "name": "repo_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_ref": { + "name": "default_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "setup_command": { + "name": "setup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cleanup_command": { + "name": "cleanup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_provider": { + "name": "remote_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_workspace_ref": { + "name": "remote_workspace_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_workspace_key": { + "name": "shared_workspace_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_workspaces_company_project_idx": { + "name": "project_workspaces_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_primary_idx": { + "name": "project_workspaces_project_primary_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_primary", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_source_type_idx": { + "name": "project_workspaces_project_source_type_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_company_shared_key_idx": { + "name": "project_workspaces_company_shared_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "shared_workspace_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_remote_ref_idx": { + "name": "project_workspaces_project_remote_ref_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_workspace_ref", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_workspaces_company_id_companies_id_fk": { + "name": "project_workspaces_company_id_companies_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "project_workspaces_project_id_projects_id_fk": { + "name": "project_workspaces_project_id_projects_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "lead_agent_id": { + "name": "lead_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_date": { + "name": "target_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_policy": { + "name": "execution_workspace_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "projects_company_idx": { + "name": "projects_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "projects_company_id_companies_id_fk": { + "name": "projects_company_id_companies_id_fk", + "tableFrom": "projects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_goal_id_goals_id_fk": { + "name": "projects_goal_id_goals_id_fk", + "tableFrom": "projects", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_lead_agent_id_agents_id_fk": { + "name": "projects_lead_agent_id_agents_id_fk", + "tableFrom": "projects", + "tableTo": "agents", + "columnsFrom": [ + "lead_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_runs": { + "name": "routine_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger_id": { + "name": "trigger_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "triggered_at": { + "name": "triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_payload": { + "name": "trigger_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "linked_issue_id": { + "name": "linked_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "coalesced_into_run_id": { + "name": "coalesced_into_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_runs_company_routine_idx": { + "name": "routine_runs_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idx": { + "name": "routine_runs_trigger_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_linked_issue_idx": { + "name": "routine_runs_linked_issue_idx", + "columns": [ + { + "expression": "linked_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idempotency_idx": { + "name": "routine_runs_trigger_idempotency_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_runs_company_id_companies_id_fk": { + "name": "routine_runs_company_id_companies_id_fk", + "tableFrom": "routine_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_routine_id_routines_id_fk": { + "name": "routine_runs_routine_id_routines_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_trigger_id_routine_triggers_id_fk": { + "name": "routine_runs_trigger_id_routine_triggers_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routine_triggers", + "columnsFrom": [ + "trigger_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_runs_linked_issue_id_issues_id_fk": { + "name": "routine_runs_linked_issue_id_issues_id_fk", + "tableFrom": "routine_runs", + "tableTo": "issues", + "columnsFrom": [ + "linked_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_triggers": { + "name": "routine_triggers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "public_id": { + "name": "public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "signing_mode": { + "name": "signing_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "replay_window_sec": { + "name": "replay_window_sec", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_rotated_at": { + "name": "last_rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_result": { + "name": "last_result", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_triggers_company_routine_idx": { + "name": "routine_triggers_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_company_kind_idx": { + "name": "routine_triggers_company_kind_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_next_run_idx": { + "name": "routine_triggers_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_idx": { + "name": "routine_triggers_public_id_idx", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_uq": { + "name": "routine_triggers_public_id_uq", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_triggers_company_id_companies_id_fk": { + "name": "routine_triggers_company_id_companies_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_routine_id_routines_id_fk": { + "name": "routine_triggers_routine_id_routines_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_secret_id_company_secrets_id_fk": { + "name": "routine_triggers_secret_id_company_secrets_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_created_by_agent_id_agents_id_fk": { + "name": "routine_triggers_created_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_updated_by_agent_id_agents_id_fk": { + "name": "routine_triggers_updated_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routines": { + "name": "routines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_issue_id": { + "name": "parent_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "concurrency_policy": { + "name": "concurrency_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'coalesce_if_active'" + }, + "catch_up_policy": { + "name": "catch_up_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'skip_missed'" + }, + "variables": { + "name": "variables", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_triggered_at": { + "name": "last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_enqueued_at": { + "name": "last_enqueued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routines_company_status_idx": { + "name": "routines_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_assignee_idx": { + "name": "routines_company_assignee_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_project_idx": { + "name": "routines_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routines_company_id_companies_id_fk": { + "name": "routines_company_id_companies_id_fk", + "tableFrom": "routines", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_project_id_projects_id_fk": { + "name": "routines_project_id_projects_id_fk", + "tableFrom": "routines", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_goal_id_goals_id_fk": { + "name": "routines_goal_id_goals_id_fk", + "tableFrom": "routines", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_parent_issue_id_issues_id_fk": { + "name": "routines_parent_issue_id_issues_id_fk", + "tableFrom": "routines", + "tableTo": "issues", + "columnsFrom": [ + "parent_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_assignee_agent_id_agents_id_fk": { + "name": "routines_assignee_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "routines_created_by_agent_id_agents_id_fk": { + "name": "routines_created_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_updated_by_agent_id_agents_id_fk": { + "name": "routines_updated_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_operations": { + "name": "workspace_operations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_operations_company_run_started_idx": { + "name": "workspace_operations_company_run_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_operations_company_workspace_started_idx": { + "name": "workspace_operations_company_workspace_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_operations_company_id_companies_id_fk": { + "name": "workspace_operations_company_id_companies_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_operations_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_operations_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_runtime_services": { + "name": "workspace_runtime_services", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "service_name": { + "name": "service_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reuse_key": { + "name": "reuse_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "started_by_run_id": { + "name": "started_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stop_policy": { + "name": "stop_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_runtime_services_company_workspace_status_idx": { + "name": "workspace_runtime_services_company_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_execution_workspace_status_idx": { + "name": "workspace_runtime_services_company_execution_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_project_status_idx": { + "name": "workspace_runtime_services_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_run_idx": { + "name": "workspace_runtime_services_run_idx", + "columns": [ + { + "expression": "started_by_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_updated_idx": { + "name": "workspace_runtime_services_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_runtime_services_company_id_companies_id_fk": { + "name": "workspace_runtime_services_company_id_companies_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_id_projects_id_fk": { + "name": "workspace_runtime_services_project_id_projects_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk": { + "name": "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_issue_id_issues_id_fk": { + "name": "workspace_runtime_services_issue_id_issues_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_owner_agent_id_agents_id_fk": { + "name": "workspace_runtime_services_owner_agent_id_agents_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk": { + "name": "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "started_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/src/migrations/meta/0049_snapshot.json b/packages/db/src/migrations/meta/0049_snapshot.json new file mode 100644 index 00000000000..1157764bb78 --- /dev/null +++ b/packages/db/src/migrations/meta/0049_snapshot.json @@ -0,0 +1,12766 @@ +{ + "id": "08a85437-3008-49a3-af78-d7a1c10a0437", + "prevId": "685fdfd9-6d11-440d-b468-fac28279fa46", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.activity_log": { + "name": "activity_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "activity_log_company_created_idx": { + "name": "activity_log_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_run_id_idx": { + "name": "activity_log_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_entity_type_id_idx": { + "name": "activity_log_entity_type_id_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "activity_log_company_id_companies_id_fk": { + "name": "activity_log_company_id_companies_id_fk", + "tableFrom": "activity_log", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_agent_id_agents_id_fk": { + "name": "activity_log_agent_id_agents_id_fk", + "tableFrom": "activity_log", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_run_id_heartbeat_runs_id_fk": { + "name": "activity_log_run_id_heartbeat_runs_id_fk", + "tableFrom": "activity_log", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_api_keys": { + "name": "agent_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_api_keys_key_hash_idx": { + "name": "agent_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_api_keys_company_agent_idx": { + "name": "agent_api_keys_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_api_keys_agent_id_agents_id_fk": { + "name": "agent_api_keys_agent_id_agents_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_api_keys_company_id_companies_id_fk": { + "name": "agent_api_keys_company_id_companies_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_config_revisions": { + "name": "agent_config_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'patch'" + }, + "rolled_back_from_revision_id": { + "name": "rolled_back_from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "changed_keys": { + "name": "changed_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "before_config": { + "name": "before_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "after_config": { + "name": "after_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_config_revisions_company_agent_created_idx": { + "name": "agent_config_revisions_company_agent_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_config_revisions_agent_created_idx": { + "name": "agent_config_revisions_agent_created_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_config_revisions_company_id_companies_id_fk": { + "name": "agent_config_revisions_company_id_companies_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_config_revisions_agent_id_agents_id_fk": { + "name": "agent_config_revisions_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_config_revisions_created_by_agent_id_agents_id_fk": { + "name": "agent_config_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_runtime_state": { + "name": "agent_runtime_state", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_json": { + "name": "state_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_run_status": { + "name": "last_run_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total_input_tokens": { + "name": "total_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_output_tokens": { + "name": "total_output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cached_input_tokens": { + "name": "total_cached_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost_cents": { + "name": "total_cost_cents", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_runtime_state_company_agent_idx": { + "name": "agent_runtime_state_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_runtime_state_company_updated_idx": { + "name": "agent_runtime_state_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_runtime_state_agent_id_agents_id_fk": { + "name": "agent_runtime_state_agent_id_agents_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_runtime_state_company_id_companies_id_fk": { + "name": "agent_runtime_state_company_id_companies_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_task_sessions": { + "name": "agent_task_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "task_key": { + "name": "task_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_params_json": { + "name": "session_params_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_display_id": { + "name": "session_display_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_task_sessions_company_agent_adapter_task_uniq": { + "name": "agent_task_sessions_company_agent_adapter_task_uniq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "adapter_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_agent_updated_idx": { + "name": "agent_task_sessions_company_agent_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_task_updated_idx": { + "name": "agent_task_sessions_company_task_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_task_sessions_company_id_companies_id_fk": { + "name": "agent_task_sessions_company_id_companies_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_agent_id_agents_id_fk": { + "name": "agent_task_sessions_agent_id_agents_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_last_run_id_heartbeat_runs_id_fk": { + "name": "agent_task_sessions_last_run_id_heartbeat_runs_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "last_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_wakeup_requests": { + "name": "agent_wakeup_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "coalesced_count": { + "name": "coalesced_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "requested_by_actor_type": { + "name": "requested_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by_actor_id": { + "name": "requested_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_wakeup_requests_company_agent_status_idx": { + "name": "agent_wakeup_requests_company_agent_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_company_requested_idx": { + "name": "agent_wakeup_requests_company_requested_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_agent_requested_idx": { + "name": "agent_wakeup_requests_agent_requested_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_wakeup_requests_company_id_companies_id_fk": { + "name": "agent_wakeup_requests_company_id_companies_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_wakeup_requests_agent_id_agents_id_fk": { + "name": "agent_wakeup_requests_agent_id_agents_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "reports_to": { + "name": "reports_to", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'process'" + }, + "adapter_config": { + "name": "adapter_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "runtime_config": { + "name": "runtime_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_heartbeat_at": { + "name": "last_heartbeat_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agents_company_status_idx": { + "name": "agents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_company_reports_to_idx": { + "name": "agents_company_reports_to_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reports_to", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agents_company_id_companies_id_fk": { + "name": "agents_company_id_companies_id_fk", + "tableFrom": "agents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agents_reports_to_agents_id_fk": { + "name": "agents_reports_to_agents_id_fk", + "tableFrom": "agents", + "tableTo": "agents", + "columnsFrom": [ + "reports_to" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approval_comments": { + "name": "approval_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approval_comments_company_idx": { + "name": "approval_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_idx": { + "name": "approval_comments_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_created_idx": { + "name": "approval_comments_approval_created_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approval_comments_company_id_companies_id_fk": { + "name": "approval_comments_company_id_companies_id_fk", + "tableFrom": "approval_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_approval_id_approvals_id_fk": { + "name": "approval_comments_approval_id_approvals_id_fk", + "tableFrom": "approval_comments", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_author_agent_id_agents_id_fk": { + "name": "approval_comments_author_agent_id_agents_id_fk", + "tableFrom": "approval_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approvals": { + "name": "approvals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_by_agent_id": { + "name": "requested_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_by_user_id": { + "name": "requested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "decision_note": { + "name": "decision_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approvals_company_status_type_idx": { + "name": "approvals_company_status_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approvals_company_id_companies_id_fk": { + "name": "approvals_company_id_companies_id_fk", + "tableFrom": "approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approvals_requested_by_agent_id_agents_id_fk": { + "name": "approvals_requested_by_agent_id_agents_id_fk", + "tableFrom": "approvals", + "tableTo": "agents", + "columnsFrom": [ + "requested_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.assets": { + "name": "assets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "object_key": { + "name": "object_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "original_filename": { + "name": "original_filename", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "assets_company_created_idx": { + "name": "assets_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_provider_idx": { + "name": "assets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_object_key_uq": { + "name": "assets_company_object_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "assets_company_id_companies_id_fk": { + "name": "assets_company_id_companies_id_fk", + "tableFrom": "assets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "assets_created_by_agent_id_agents_id_fk": { + "name": "assets_created_by_agent_id_agents_id_fk", + "tableFrom": "assets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.board_api_keys": { + "name": "board_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "board_api_keys_key_hash_idx": { + "name": "board_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "board_api_keys_user_idx": { + "name": "board_api_keys_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "board_api_keys_user_id_user_id_fk": { + "name": "board_api_keys_user_id_user_id_fk", + "tableFrom": "board_api_keys", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_incidents": { + "name": "budget_incidents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "policy_id": { + "name": "policy_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_end": { + "name": "window_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "threshold_type": { + "name": "threshold_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_limit": { + "name": "amount_limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "amount_observed": { + "name": "amount_observed", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_incidents_company_status_idx": { + "name": "budget_incidents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_company_scope_idx": { + "name": "budget_incidents_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_policy_window_threshold_idx": { + "name": "budget_incidents_policy_window_threshold_idx", + "columns": [ + { + "expression": "policy_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "threshold_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"budget_incidents\".\"status\" <> 'dismissed'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_incidents_company_id_companies_id_fk": { + "name": "budget_incidents_company_id_companies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_policy_id_budget_policies_id_fk": { + "name": "budget_incidents_policy_id_budget_policies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "budget_policies", + "columnsFrom": [ + "policy_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_approval_id_approvals_id_fk": { + "name": "budget_incidents_approval_id_approvals_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_policies": { + "name": "budget_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'billed_cents'" + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "warn_percent": { + "name": "warn_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 80 + }, + "hard_stop_enabled": { + "name": "hard_stop_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "notify_enabled": { + "name": "notify_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_policies_company_scope_active_idx": { + "name": "budget_policies_company_scope_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_window_idx": { + "name": "budget_policies_company_window_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_scope_metric_unique_idx": { + "name": "budget_policies_company_scope_metric_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_policies_company_id_companies_id_fk": { + "name": "budget_policies_company_id_companies_id_fk", + "tableFrom": "budget_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cli_auth_challenges": { + "name": "cli_auth_challenges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_hash": { + "name": "secret_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_access": { + "name": "requested_access", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'board'" + }, + "requested_company_id": { + "name": "requested_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "pending_key_hash": { + "name": "pending_key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pending_key_name": { + "name": "pending_key_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "board_api_key_id": { + "name": "board_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cli_auth_challenges_secret_hash_idx": { + "name": "cli_auth_challenges_secret_hash_idx", + "columns": [ + { + "expression": "secret_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_approved_by_idx": { + "name": "cli_auth_challenges_approved_by_idx", + "columns": [ + { + "expression": "approved_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_requested_company_idx": { + "name": "cli_auth_challenges_requested_company_idx", + "columns": [ + { + "expression": "requested_company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cli_auth_challenges_requested_company_id_companies_id_fk": { + "name": "cli_auth_challenges_requested_company_id_companies_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "companies", + "columnsFrom": [ + "requested_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_approved_by_user_id_user_id_fk": { + "name": "cli_auth_challenges_approved_by_user_id_user_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "user", + "columnsFrom": [ + "approved_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk": { + "name": "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "board_api_keys", + "columnsFrom": [ + "board_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.companies": { + "name": "companies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "issue_prefix": { + "name": "issue_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'PAP'" + }, + "issue_counter": { + "name": "issue_counter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "require_board_approval_for_new_agents": { + "name": "require_board_approval_for_new_agents", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "feedback_data_sharing_enabled": { + "name": "feedback_data_sharing_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "feedback_data_sharing_consent_at": { + "name": "feedback_data_sharing_consent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "feedback_data_sharing_consent_by_user_id": { + "name": "feedback_data_sharing_consent_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feedback_data_sharing_terms_version": { + "name": "feedback_data_sharing_terms_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "brand_color": { + "name": "brand_color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "companies_issue_prefix_idx": { + "name": "companies_issue_prefix_idx", + "columns": [ + { + "expression": "issue_prefix", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_logos": { + "name": "company_logos", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_logos_company_uq": { + "name": "company_logos_company_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_logos_asset_uq": { + "name": "company_logos_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_logos_company_id_companies_id_fk": { + "name": "company_logos_company_id_companies_id_fk", + "tableFrom": "company_logos", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_logos_asset_id_assets_id_fk": { + "name": "company_logos_asset_id_assets_id_fk", + "tableFrom": "company_logos", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_memberships": { + "name": "company_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "membership_role": { + "name": "membership_role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_memberships_company_principal_unique_idx": { + "name": "company_memberships_company_principal_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_principal_status_idx": { + "name": "company_memberships_principal_status_idx", + "columns": [ + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_company_status_idx": { + "name": "company_memberships_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_memberships_company_id_companies_id_fk": { + "name": "company_memberships_company_id_companies_id_fk", + "tableFrom": "company_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_versions": { + "name": "company_secret_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "material": { + "name": "material", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "value_sha256": { + "name": "value_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "company_secret_versions_secret_idx": { + "name": "company_secret_versions_secret_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_value_sha256_idx": { + "name": "company_secret_versions_value_sha256_idx", + "columns": [ + { + "expression": "value_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_secret_version_uq": { + "name": "company_secret_versions_secret_version_uq", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_versions_secret_id_company_secrets_id_fk": { + "name": "company_secret_versions_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_versions_created_by_agent_id_agents_id_fk": { + "name": "company_secret_versions_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secrets": { + "name": "company_secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_encrypted'" + }, + "external_ref": { + "name": "external_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "latest_version": { + "name": "latest_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secrets_company_idx": { + "name": "company_secrets_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_provider_idx": { + "name": "company_secrets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_name_uq": { + "name": "company_secrets_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secrets_company_id_companies_id_fk": { + "name": "company_secrets_company_id_companies_id_fk", + "tableFrom": "company_secrets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secrets_created_by_agent_id_agents_id_fk": { + "name": "company_secrets_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secrets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skills": { + "name": "company_skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "markdown": { + "name": "markdown", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "source_locator": { + "name": "source_locator", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_ref": { + "name": "source_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trust_level": { + "name": "trust_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown_only'" + }, + "compatibility": { + "name": "compatibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'compatible'" + }, + "file_inventory": { + "name": "file_inventory", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skills_company_key_idx": { + "name": "company_skills_company_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_name_idx": { + "name": "company_skills_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skills_company_id_companies_id_fk": { + "name": "company_skills_company_id_companies_id_fk", + "tableFrom": "company_skills", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cost_events": { + "name": "cost_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "billing_type": { + "name": "billing_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cached_input_tokens": { + "name": "cached_input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cost_cents": { + "name": "cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cost_events_company_occurred_idx": { + "name": "cost_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_agent_occurred_idx": { + "name": "cost_events_company_agent_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_provider_occurred_idx": { + "name": "cost_events_company_provider_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_biller_occurred_idx": { + "name": "cost_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_heartbeat_run_idx": { + "name": "cost_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cost_events_company_id_companies_id_fk": { + "name": "cost_events_company_id_companies_id_fk", + "tableFrom": "cost_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_agent_id_agents_id_fk": { + "name": "cost_events_agent_id_agents_id_fk", + "tableFrom": "cost_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_issue_id_issues_id_fk": { + "name": "cost_events_issue_id_issues_id_fk", + "tableFrom": "cost_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_project_id_projects_id_fk": { + "name": "cost_events_project_id_projects_id_fk", + "tableFrom": "cost_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_goal_id_goals_id_fk": { + "name": "cost_events_goal_id_goals_id_fk", + "tableFrom": "cost_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "cost_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "cost_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_revisions": { + "name": "document_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_revisions_document_revision_uq": { + "name": "document_revisions_document_revision_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_revisions_company_document_created_idx": { + "name": "document_revisions_company_document_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_revisions_company_id_companies_id_fk": { + "name": "document_revisions_company_id_companies_id_fk", + "tableFrom": "document_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_revisions_document_id_documents_id_fk": { + "name": "document_revisions_document_id_documents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_revisions_created_by_agent_id_agents_id_fk": { + "name": "document_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_revisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "document_revisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "document_revisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.documents": { + "name": "documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "latest_body": { + "name": "latest_body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "latest_revision_id": { + "name": "latest_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "latest_revision_number": { + "name": "latest_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "documents_company_updated_idx": { + "name": "documents_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_company_created_idx": { + "name": "documents_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "documents_company_id_companies_id_fk": { + "name": "documents_company_id_companies_id_fk", + "tableFrom": "documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "documents_created_by_agent_id_agents_id_fk": { + "name": "documents_created_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "documents_updated_by_agent_id_agents_id_fk": { + "name": "documents_updated_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_workspaces": { + "name": "execution_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "strategy_type": { + "name": "strategy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_ref": { + "name": "base_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch_name": { + "name": "branch_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_fs'" + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "derived_from_execution_workspace_id": { + "name": "derived_from_execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "opened_at": { + "name": "opened_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_eligible_at": { + "name": "cleanup_eligible_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_reason": { + "name": "cleanup_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_workspaces_company_project_status_idx": { + "name": "execution_workspaces_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_project_workspace_status_idx": { + "name": "execution_workspaces_company_project_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_source_issue_idx": { + "name": "execution_workspaces_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_last_used_idx": { + "name": "execution_workspaces_company_last_used_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_branch_idx": { + "name": "execution_workspaces_company_branch_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspaces_company_id_companies_id_fk": { + "name": "execution_workspaces_company_id_companies_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "execution_workspaces_project_id_projects_id_fk": { + "name": "execution_workspaces_project_id_projects_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspaces_project_workspace_id_project_workspaces_id_fk": { + "name": "execution_workspaces_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_source_issue_id_issues_id_fk": { + "name": "execution_workspaces_source_issue_id_issues_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk": { + "name": "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "derived_from_execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feedback_exports": { + "name": "feedback_exports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "feedback_vote_id": { + "name": "feedback_vote_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_only'" + }, + "destination": { + "name": "destination", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "export_id": { + "name": "export_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consent_version": { + "name": "consent_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "schema_version": { + "name": "schema_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-envelope-v2'" + }, + "bundle_version": { + "name": "bundle_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-bundle-v2'" + }, + "payload_version": { + "name": "payload_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-v1'" + }, + "payload_digest": { + "name": "payload_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload_snapshot": { + "name": "payload_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "target_summary": { + "name": "target_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "redaction_summary": { + "name": "redaction_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempted_at": { + "name": "last_attempted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "exported_at": { + "name": "exported_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_exports_feedback_vote_idx": { + "name": "feedback_exports_feedback_vote_idx", + "columns": [ + { + "expression": "feedback_vote_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_created_idx": { + "name": "feedback_exports_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_status_idx": { + "name": "feedback_exports_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_issue_idx": { + "name": "feedback_exports_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_project_idx": { + "name": "feedback_exports_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_author_idx": { + "name": "feedback_exports_company_author_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_exports_company_id_companies_id_fk": { + "name": "feedback_exports_company_id_companies_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "feedback_exports_feedback_vote_id_feedback_votes_id_fk": { + "name": "feedback_exports_feedback_vote_id_feedback_votes_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "feedback_votes", + "columnsFrom": [ + "feedback_vote_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_exports_issue_id_issues_id_fk": { + "name": "feedback_exports_issue_id_issues_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_exports_project_id_projects_id_fk": { + "name": "feedback_exports_project_id_projects_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feedback_votes": { + "name": "feedback_votes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_with_labs": { + "name": "shared_with_labs", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "shared_at": { + "name": "shared_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "consent_version": { + "name": "consent_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redaction_summary": { + "name": "redaction_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_votes_company_issue_idx": { + "name": "feedback_votes_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_issue_target_idx": { + "name": "feedback_votes_issue_target_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_author_idx": { + "name": "feedback_votes_author_idx", + "columns": [ + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_company_target_author_idx": { + "name": "feedback_votes_company_target_author_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_votes_company_id_companies_id_fk": { + "name": "feedback_votes_company_id_companies_id_fk", + "tableFrom": "feedback_votes", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "feedback_votes_issue_id_issues_id_fk": { + "name": "feedback_votes_issue_id_issues_id_fk", + "tableFrom": "feedback_votes", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.finance_events": { + "name": "finance_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cost_event_id": { + "name": "cost_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "event_kind": { + "name": "event_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'debit'" + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_adapter_type": { + "name": "execution_adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pricing_tier": { + "name": "pricing_tier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "estimated": { + "name": "estimated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "external_invoice_id": { + "name": "external_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata_json": { + "name": "metadata_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "finance_events_company_occurred_idx": { + "name": "finance_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_biller_occurred_idx": { + "name": "finance_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_kind_occurred_idx": { + "name": "finance_events_company_kind_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_direction_occurred_idx": { + "name": "finance_events_company_direction_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "direction", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_heartbeat_run_idx": { + "name": "finance_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_cost_event_idx": { + "name": "finance_events_company_cost_event_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "finance_events_company_id_companies_id_fk": { + "name": "finance_events_company_id_companies_id_fk", + "tableFrom": "finance_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_agent_id_agents_id_fk": { + "name": "finance_events_agent_id_agents_id_fk", + "tableFrom": "finance_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_issue_id_issues_id_fk": { + "name": "finance_events_issue_id_issues_id_fk", + "tableFrom": "finance_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_project_id_projects_id_fk": { + "name": "finance_events_project_id_projects_id_fk", + "tableFrom": "finance_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_goal_id_goals_id_fk": { + "name": "finance_events_goal_id_goals_id_fk", + "tableFrom": "finance_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "finance_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "finance_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_cost_event_id_cost_events_id_fk": { + "name": "finance_events_cost_event_id_cost_events_id_fk", + "tableFrom": "finance_events", + "tableTo": "cost_events", + "columnsFrom": [ + "cost_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.goals": { + "name": "goals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'task'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'planned'" + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "goals_company_idx": { + "name": "goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "goals_company_id_companies_id_fk": { + "name": "goals_company_id_companies_id_fk", + "tableFrom": "goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_parent_id_goals_id_fk": { + "name": "goals_parent_id_goals_id_fk", + "tableFrom": "goals", + "tableTo": "goals", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_owner_agent_id_agents_id_fk": { + "name": "goals_owner_agent_id_agents_id_fk", + "tableFrom": "goals", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_run_events": { + "name": "heartbeat_run_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stream": { + "name": "stream", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_run_events_run_seq_idx": { + "name": "heartbeat_run_events_run_seq_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_run_idx": { + "name": "heartbeat_run_events_company_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_created_idx": { + "name": "heartbeat_run_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_run_events_company_id_companies_id_fk": { + "name": "heartbeat_run_events_company_id_companies_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_events_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_agent_id_agents_id_fk": { + "name": "heartbeat_run_events_agent_id_agents_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_runs": { + "name": "heartbeat_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invocation_source": { + "name": "invocation_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'on_demand'" + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wakeup_request_id": { + "name": "wakeup_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "signal": { + "name": "signal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "usage_json": { + "name": "usage_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_json": { + "name": "result_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_id_before": { + "name": "session_id_before", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id_after": { + "name": "session_id_after", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_run_id": { + "name": "external_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "process_pid": { + "name": "process_pid", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "process_started_at": { + "name": "process_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "retry_of_run_id": { + "name": "retry_of_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "process_loss_retry_count": { + "name": "process_loss_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "context_snapshot": { + "name": "context_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_runs_company_agent_started_idx": { + "name": "heartbeat_runs_company_agent_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_runs_company_id_companies_id_fk": { + "name": "heartbeat_runs_company_id_companies_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_agent_id_agents_id_fk": { + "name": "heartbeat_runs_agent_id_agents_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk": { + "name": "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agent_wakeup_requests", + "columnsFrom": [ + "wakeup_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "retry_of_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_settings": { + "name": "instance_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "singleton_key": { + "name": "singleton_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "general": { + "name": "general", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "experimental": { + "name": "experimental", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_settings_singleton_key_idx": { + "name": "instance_settings_singleton_key_idx", + "columns": [ + { + "expression": "singleton_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_user_roles": { + "name": "instance_user_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'instance_admin'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_user_roles_user_role_unique_idx": { + "name": "instance_user_roles_user_role_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "instance_user_roles_role_idx": { + "name": "instance_user_roles_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invites": { + "name": "invites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "invite_type": { + "name": "invite_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company_join'" + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allowed_join_types": { + "name": "allowed_join_types", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'both'" + }, + "defaults_payload": { + "name": "defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "invited_by_user_id": { + "name": "invited_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invites_token_hash_unique_idx": { + "name": "invites_token_hash_unique_idx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invites_company_invite_state_idx": { + "name": "invites_company_invite_state_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invite_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revoked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invites_company_id_companies_id_fk": { + "name": "invites_company_id_companies_id_fk", + "tableFrom": "invites", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_approvals": { + "name": "issue_approvals", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "linked_by_agent_id": { + "name": "linked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "linked_by_user_id": { + "name": "linked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_approvals_issue_idx": { + "name": "issue_approvals_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_approval_idx": { + "name": "issue_approvals_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_company_idx": { + "name": "issue_approvals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_approvals_company_id_companies_id_fk": { + "name": "issue_approvals_company_id_companies_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_approvals_issue_id_issues_id_fk": { + "name": "issue_approvals_issue_id_issues_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_approval_id_approvals_id_fk": { + "name": "issue_approvals_approval_id_approvals_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_linked_by_agent_id_agents_id_fk": { + "name": "issue_approvals_linked_by_agent_id_agents_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "agents", + "columnsFrom": [ + "linked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_approvals_pk": { + "name": "issue_approvals_pk", + "columns": [ + "issue_id", + "approval_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_attachments": { + "name": "issue_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_comment_id": { + "name": "issue_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_attachments_company_issue_idx": { + "name": "issue_attachments_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_issue_comment_idx": { + "name": "issue_attachments_issue_comment_idx", + "columns": [ + { + "expression": "issue_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_asset_uq": { + "name": "issue_attachments_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_attachments_company_id_companies_id_fk": { + "name": "issue_attachments_company_id_companies_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_attachments_issue_id_issues_id_fk": { + "name": "issue_attachments_issue_id_issues_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_asset_id_assets_id_fk": { + "name": "issue_attachments_asset_id_assets_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_issue_comment_id_issue_comments_id_fk": { + "name": "issue_attachments_issue_comment_id_issue_comments_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issue_comments", + "columnsFrom": [ + "issue_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_comments": { + "name": "issue_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_comments_issue_idx": { + "name": "issue_comments_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_idx": { + "name": "issue_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_issue_created_at_idx": { + "name": "issue_comments_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_author_issue_created_at_idx": { + "name": "issue_comments_company_author_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_comments_company_id_companies_id_fk": { + "name": "issue_comments_company_id_companies_id_fk", + "tableFrom": "issue_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_issue_id_issues_id_fk": { + "name": "issue_comments_issue_id_issues_id_fk", + "tableFrom": "issue_comments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_author_agent_id_agents_id_fk": { + "name": "issue_comments_author_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_comments_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_documents": { + "name": "issue_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_documents_company_issue_key_uq": { + "name": "issue_documents_company_issue_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_document_uq": { + "name": "issue_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_company_issue_updated_idx": { + "name": "issue_documents_company_issue_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_documents_company_id_companies_id_fk": { + "name": "issue_documents_company_id_companies_id_fk", + "tableFrom": "issue_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_documents_issue_id_issues_id_fk": { + "name": "issue_documents_issue_id_issues_id_fk", + "tableFrom": "issue_documents", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_documents_document_id_documents_id_fk": { + "name": "issue_documents_document_id_documents_id_fk", + "tableFrom": "issue_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_inbox_archives": { + "name": "issue_inbox_archives", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_inbox_archives_company_issue_idx": { + "name": "issue_inbox_archives_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_user_idx": { + "name": "issue_inbox_archives_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_issue_user_idx": { + "name": "issue_inbox_archives_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_inbox_archives_company_id_companies_id_fk": { + "name": "issue_inbox_archives_company_id_companies_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_inbox_archives_issue_id_issues_id_fk": { + "name": "issue_inbox_archives_issue_id_issues_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_labels": { + "name": "issue_labels", + "schema": "", + "columns": { + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label_id": { + "name": "label_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_labels_issue_idx": { + "name": "issue_labels_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_label_idx": { + "name": "issue_labels_label_idx", + "columns": [ + { + "expression": "label_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_company_idx": { + "name": "issue_labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_labels_issue_id_issues_id_fk": { + "name": "issue_labels_issue_id_issues_id_fk", + "tableFrom": "issue_labels", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_label_id_labels_id_fk": { + "name": "issue_labels_label_id_labels_id_fk", + "tableFrom": "issue_labels", + "tableTo": "labels", + "columnsFrom": [ + "label_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_company_id_companies_id_fk": { + "name": "issue_labels_company_id_companies_id_fk", + "tableFrom": "issue_labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_labels_pk": { + "name": "issue_labels_pk", + "columns": [ + "issue_id", + "label_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_read_states": { + "name": "issue_read_states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_read_at": { + "name": "last_read_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_read_states_company_issue_idx": { + "name": "issue_read_states_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_user_idx": { + "name": "issue_read_states_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_issue_user_idx": { + "name": "issue_read_states_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_read_states_company_id_companies_id_fk": { + "name": "issue_read_states_company_id_companies_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_read_states_issue_id_issues_id_fk": { + "name": "issue_read_states_issue_id_issues_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_relations": { + "name": "issue_relations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "related_issue_id": { + "name": "related_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_relations_company_issue_idx": { + "name": "issue_relations_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_related_issue_idx": { + "name": "issue_relations_company_related_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "related_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_type_idx": { + "name": "issue_relations_company_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_edge_uq": { + "name": "issue_relations_company_edge_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "related_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_relations_company_id_companies_id_fk": { + "name": "issue_relations_company_id_companies_id_fk", + "tableFrom": "issue_relations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_relations_issue_id_issues_id_fk": { + "name": "issue_relations_issue_id_issues_id_fk", + "tableFrom": "issue_relations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_relations_related_issue_id_issues_id_fk": { + "name": "issue_relations_related_issue_id_issues_id_fk", + "tableFrom": "issue_relations", + "tableTo": "issues", + "columnsFrom": [ + "related_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_relations_created_by_agent_id_agents_id_fk": { + "name": "issue_relations_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_relations", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_work_products": { + "name": "issue_work_products", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "runtime_service_id": { + "name": "runtime_service_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "review_state": { + "name": "review_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_work_products_company_issue_type_idx": { + "name": "issue_work_products_company_issue_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_execution_workspace_type_idx": { + "name": "issue_work_products_company_execution_workspace_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_provider_external_id_idx": { + "name": "issue_work_products_company_provider_external_id_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_updated_idx": { + "name": "issue_work_products_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_work_products_company_id_companies_id_fk": { + "name": "issue_work_products_company_id_companies_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_work_products_project_id_projects_id_fk": { + "name": "issue_work_products_project_id_projects_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_issue_id_issues_id_fk": { + "name": "issue_work_products_issue_id_issues_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_work_products_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issue_work_products_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk": { + "name": "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "workspace_runtime_services", + "columnsFrom": [ + "runtime_service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_work_products_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issues": { + "name": "issues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assignee_user_id": { + "name": "assignee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "checkout_run_id": { + "name": "checkout_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_run_id": { + "name": "execution_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_agent_name_key": { + "name": "execution_agent_name_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_locked_at": { + "name": "execution_locked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_number": { + "name": "issue_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_kind": { + "name": "origin_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "origin_id": { + "name": "origin_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_depth": { + "name": "request_depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_adapter_overrides": { + "name": "assignee_adapter_overrides", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_preference": { + "name": "execution_workspace_preference", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_settings": { + "name": "execution_workspace_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "hidden_at": { + "name": "hidden_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issues_company_status_idx": { + "name": "issues_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_status_idx": { + "name": "issues_company_assignee_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_user_status_idx": { + "name": "issues_company_assignee_user_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_parent_idx": { + "name": "issues_company_parent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_idx": { + "name": "issues_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_origin_idx": { + "name": "issues_company_origin_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_workspace_idx": { + "name": "issues_company_project_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_execution_workspace_idx": { + "name": "issues_company_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_identifier_idx": { + "name": "issues_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_open_routine_execution_uq": { + "name": "issues_open_routine_execution_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'routine_execution'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"execution_run_id\" is not null\n and \"issues\".\"status\" in ('backlog', 'todo', 'in_progress', 'in_review', 'blocked')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issues_company_id_companies_id_fk": { + "name": "issues_company_id_companies_id_fk", + "tableFrom": "issues", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_id_projects_id_fk": { + "name": "issues_project_id_projects_id_fk", + "tableFrom": "issues", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_workspace_id_project_workspaces_id_fk": { + "name": "issues_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_goal_id_goals_id_fk": { + "name": "issues_goal_id_goals_id_fk", + "tableFrom": "issues", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_parent_id_issues_id_fk": { + "name": "issues_parent_id_issues_id_fk", + "tableFrom": "issues", + "tableTo": "issues", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_assignee_agent_id_agents_id_fk": { + "name": "issues_assignee_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_checkout_run_id_heartbeat_runs_id_fk": { + "name": "issues_checkout_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "checkout_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_execution_run_id_heartbeat_runs_id_fk": { + "name": "issues_execution_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "execution_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_created_by_agent_id_agents_id_fk": { + "name": "issues_created_by_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issues_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.join_requests": { + "name": "join_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "invite_id": { + "name": "invite_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_type": { + "name": "request_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending_approval'" + }, + "request_ip": { + "name": "request_ip", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requesting_user_id": { + "name": "requesting_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_email_snapshot": { + "name": "request_email_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_name": { + "name": "agent_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_defaults_payload": { + "name": "agent_defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "claim_secret_hash": { + "name": "claim_secret_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_secret_expires_at": { + "name": "claim_secret_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claim_secret_consumed_at": { + "name": "claim_secret_consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_agent_id": { + "name": "created_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "rejected_by_user_id": { + "name": "rejected_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejected_at": { + "name": "rejected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "join_requests_invite_unique_idx": { + "name": "join_requests_invite_unique_idx", + "columns": [ + { + "expression": "invite_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_company_status_type_created_idx": { + "name": "join_requests_company_status_type_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "join_requests_invite_id_invites_id_fk": { + "name": "join_requests_invite_id_invites_id_fk", + "tableFrom": "join_requests", + "tableTo": "invites", + "columnsFrom": [ + "invite_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_company_id_companies_id_fk": { + "name": "join_requests_company_id_companies_id_fk", + "tableFrom": "join_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_created_agent_id_agents_id_fk": { + "name": "join_requests_created_agent_id_agents_id_fk", + "tableFrom": "join_requests", + "tableTo": "agents", + "columnsFrom": [ + "created_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.labels": { + "name": "labels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "labels_company_idx": { + "name": "labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "labels_company_name_idx": { + "name": "labels_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "labels_company_id_companies_id_fk": { + "name": "labels_company_id_companies_id_fk", + "tableFrom": "labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_company_settings": { + "name": "plugin_company_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "settings_json": { + "name": "settings_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_company_settings_company_idx": { + "name": "plugin_company_settings_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_plugin_idx": { + "name": "plugin_company_settings_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_company_plugin_uq": { + "name": "plugin_company_settings_company_plugin_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_company_settings_company_id_companies_id_fk": { + "name": "plugin_company_settings_company_id_companies_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_company_settings_plugin_id_plugins_id_fk": { + "name": "plugin_company_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_config": { + "name": "plugin_config", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "config_json": { + "name": "config_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_config_plugin_id_idx": { + "name": "plugin_config_plugin_id_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_config_plugin_id_plugins_id_fk": { + "name": "plugin_config_plugin_id_plugins_id_fk", + "tableFrom": "plugin_config", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_entities": { + "name": "plugin_entities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_entities_plugin_idx": { + "name": "plugin_entities_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_type_idx": { + "name": "plugin_entities_type_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_scope_idx": { + "name": "plugin_entities_scope_idx", + "columns": [ + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_external_idx": { + "name": "plugin_entities_external_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_entities_plugin_id_plugins_id_fk": { + "name": "plugin_entities_plugin_id_plugins_id_fk", + "tableFrom": "plugin_entities", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_job_runs": { + "name": "plugin_job_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "job_id": { + "name": "job_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logs": { + "name": "logs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_job_runs_job_idx": { + "name": "plugin_job_runs_job_idx", + "columns": [ + { + "expression": "job_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_plugin_idx": { + "name": "plugin_job_runs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_status_idx": { + "name": "plugin_job_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_job_runs_job_id_plugin_jobs_id_fk": { + "name": "plugin_job_runs_job_id_plugin_jobs_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugin_jobs", + "columnsFrom": [ + "job_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_job_runs_plugin_id_plugins_id_fk": { + "name": "plugin_job_runs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_jobs": { + "name": "plugin_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "job_key": { + "name": "job_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_jobs_plugin_idx": { + "name": "plugin_jobs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_next_run_idx": { + "name": "plugin_jobs_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_unique_idx": { + "name": "plugin_jobs_unique_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "job_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_jobs_plugin_id_plugins_id_fk": { + "name": "plugin_jobs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_jobs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_logs": { + "name": "plugin_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'info'" + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "meta": { + "name": "meta", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_logs_plugin_time_idx": { + "name": "plugin_logs_plugin_time_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_logs_level_idx": { + "name": "plugin_logs_level_idx", + "columns": [ + { + "expression": "level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_logs_plugin_id_plugins_id_fk": { + "name": "plugin_logs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_logs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_state": { + "name": "plugin_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "namespace": { + "name": "namespace", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "state_key": { + "name": "state_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value_json": { + "name": "value_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_state_plugin_scope_idx": { + "name": "plugin_state_plugin_scope_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_state_plugin_id_plugins_id_fk": { + "name": "plugin_state_plugin_id_plugins_id_fk", + "tableFrom": "plugin_state", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "plugin_state_unique_entry_idx": { + "name": "plugin_state_unique_entry_idx", + "nullsNotDistinct": true, + "columns": [ + "plugin_id", + "scope_kind", + "scope_id", + "namespace", + "state_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_webhook_deliveries": { + "name": "plugin_webhook_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "webhook_key": { + "name": "webhook_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "headers": { + "name": "headers", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_webhook_deliveries_plugin_idx": { + "name": "plugin_webhook_deliveries_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_status_idx": { + "name": "plugin_webhook_deliveries_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_key_idx": { + "name": "plugin_webhook_deliveries_key_idx", + "columns": [ + { + "expression": "webhook_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_webhook_deliveries_plugin_id_plugins_id_fk": { + "name": "plugin_webhook_deliveries_plugin_id_plugins_id_fk", + "tableFrom": "plugin_webhook_deliveries", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugins": { + "name": "plugins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "package_name": { + "name": "package_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "api_version": { + "name": "api_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "manifest_json": { + "name": "manifest_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'installed'" + }, + "install_order": { + "name": "install_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "package_path": { + "name": "package_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_at": { + "name": "installed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugins_plugin_key_idx": { + "name": "plugins_plugin_key_idx", + "columns": [ + { + "expression": "plugin_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugins_status_idx": { + "name": "plugins_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.principal_permission_grants": { + "name": "principal_permission_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_key": { + "name": "permission_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "granted_by_user_id": { + "name": "granted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "principal_permission_grants_unique_idx": { + "name": "principal_permission_grants_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "principal_permission_grants_company_permission_idx": { + "name": "principal_permission_grants_company_permission_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "principal_permission_grants_company_id_companies_id_fk": { + "name": "principal_permission_grants_company_id_companies_id_fk", + "tableFrom": "principal_permission_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_goals": { + "name": "project_goals", + "schema": "", + "columns": { + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_goals_project_idx": { + "name": "project_goals_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_goal_idx": { + "name": "project_goals_goal_idx", + "columns": [ + { + "expression": "goal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_company_idx": { + "name": "project_goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_goals_project_id_projects_id_fk": { + "name": "project_goals_project_id_projects_id_fk", + "tableFrom": "project_goals", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_goal_id_goals_id_fk": { + "name": "project_goals_goal_id_goals_id_fk", + "tableFrom": "project_goals", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_company_id_companies_id_fk": { + "name": "project_goals_company_id_companies_id_fk", + "tableFrom": "project_goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "project_goals_project_id_goal_id_pk": { + "name": "project_goals_project_id_goal_id_pk", + "columns": [ + "project_id", + "goal_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_workspaces": { + "name": "project_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_ref": { + "name": "repo_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_ref": { + "name": "default_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "setup_command": { + "name": "setup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cleanup_command": { + "name": "cleanup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_provider": { + "name": "remote_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_workspace_ref": { + "name": "remote_workspace_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_workspace_key": { + "name": "shared_workspace_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_workspaces_company_project_idx": { + "name": "project_workspaces_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_primary_idx": { + "name": "project_workspaces_project_primary_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_primary", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_source_type_idx": { + "name": "project_workspaces_project_source_type_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_company_shared_key_idx": { + "name": "project_workspaces_company_shared_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "shared_workspace_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_remote_ref_idx": { + "name": "project_workspaces_project_remote_ref_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_workspace_ref", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_workspaces_company_id_companies_id_fk": { + "name": "project_workspaces_company_id_companies_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "project_workspaces_project_id_projects_id_fk": { + "name": "project_workspaces_project_id_projects_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "lead_agent_id": { + "name": "lead_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_date": { + "name": "target_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_policy": { + "name": "execution_workspace_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "projects_company_idx": { + "name": "projects_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "projects_company_id_companies_id_fk": { + "name": "projects_company_id_companies_id_fk", + "tableFrom": "projects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_goal_id_goals_id_fk": { + "name": "projects_goal_id_goals_id_fk", + "tableFrom": "projects", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_lead_agent_id_agents_id_fk": { + "name": "projects_lead_agent_id_agents_id_fk", + "tableFrom": "projects", + "tableTo": "agents", + "columnsFrom": [ + "lead_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_runs": { + "name": "routine_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger_id": { + "name": "trigger_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "triggered_at": { + "name": "triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_payload": { + "name": "trigger_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "linked_issue_id": { + "name": "linked_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "coalesced_into_run_id": { + "name": "coalesced_into_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_runs_company_routine_idx": { + "name": "routine_runs_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idx": { + "name": "routine_runs_trigger_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_linked_issue_idx": { + "name": "routine_runs_linked_issue_idx", + "columns": [ + { + "expression": "linked_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idempotency_idx": { + "name": "routine_runs_trigger_idempotency_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_runs_company_id_companies_id_fk": { + "name": "routine_runs_company_id_companies_id_fk", + "tableFrom": "routine_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_routine_id_routines_id_fk": { + "name": "routine_runs_routine_id_routines_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_trigger_id_routine_triggers_id_fk": { + "name": "routine_runs_trigger_id_routine_triggers_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routine_triggers", + "columnsFrom": [ + "trigger_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_runs_linked_issue_id_issues_id_fk": { + "name": "routine_runs_linked_issue_id_issues_id_fk", + "tableFrom": "routine_runs", + "tableTo": "issues", + "columnsFrom": [ + "linked_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_triggers": { + "name": "routine_triggers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "public_id": { + "name": "public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "signing_mode": { + "name": "signing_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "replay_window_sec": { + "name": "replay_window_sec", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_rotated_at": { + "name": "last_rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_result": { + "name": "last_result", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_triggers_company_routine_idx": { + "name": "routine_triggers_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_company_kind_idx": { + "name": "routine_triggers_company_kind_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_next_run_idx": { + "name": "routine_triggers_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_idx": { + "name": "routine_triggers_public_id_idx", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_uq": { + "name": "routine_triggers_public_id_uq", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_triggers_company_id_companies_id_fk": { + "name": "routine_triggers_company_id_companies_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_routine_id_routines_id_fk": { + "name": "routine_triggers_routine_id_routines_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_secret_id_company_secrets_id_fk": { + "name": "routine_triggers_secret_id_company_secrets_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_created_by_agent_id_agents_id_fk": { + "name": "routine_triggers_created_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_updated_by_agent_id_agents_id_fk": { + "name": "routine_triggers_updated_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routines": { + "name": "routines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_issue_id": { + "name": "parent_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "concurrency_policy": { + "name": "concurrency_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'coalesce_if_active'" + }, + "catch_up_policy": { + "name": "catch_up_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'skip_missed'" + }, + "variables": { + "name": "variables", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_triggered_at": { + "name": "last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_enqueued_at": { + "name": "last_enqueued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routines_company_status_idx": { + "name": "routines_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_assignee_idx": { + "name": "routines_company_assignee_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_project_idx": { + "name": "routines_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routines_company_id_companies_id_fk": { + "name": "routines_company_id_companies_id_fk", + "tableFrom": "routines", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_project_id_projects_id_fk": { + "name": "routines_project_id_projects_id_fk", + "tableFrom": "routines", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_goal_id_goals_id_fk": { + "name": "routines_goal_id_goals_id_fk", + "tableFrom": "routines", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_parent_issue_id_issues_id_fk": { + "name": "routines_parent_issue_id_issues_id_fk", + "tableFrom": "routines", + "tableTo": "issues", + "columnsFrom": [ + "parent_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_assignee_agent_id_agents_id_fk": { + "name": "routines_assignee_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "routines_created_by_agent_id_agents_id_fk": { + "name": "routines_created_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_updated_by_agent_id_agents_id_fk": { + "name": "routines_updated_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_operations": { + "name": "workspace_operations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_operations_company_run_started_idx": { + "name": "workspace_operations_company_run_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_operations_company_workspace_started_idx": { + "name": "workspace_operations_company_workspace_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_operations_company_id_companies_id_fk": { + "name": "workspace_operations_company_id_companies_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_operations_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_operations_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_runtime_services": { + "name": "workspace_runtime_services", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "service_name": { + "name": "service_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reuse_key": { + "name": "reuse_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "started_by_run_id": { + "name": "started_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stop_policy": { + "name": "stop_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_runtime_services_company_workspace_status_idx": { + "name": "workspace_runtime_services_company_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_execution_workspace_status_idx": { + "name": "workspace_runtime_services_company_execution_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_project_status_idx": { + "name": "workspace_runtime_services_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_run_idx": { + "name": "workspace_runtime_services_run_idx", + "columns": [ + { + "expression": "started_by_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_updated_idx": { + "name": "workspace_runtime_services_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_runtime_services_company_id_companies_id_fk": { + "name": "workspace_runtime_services_company_id_companies_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_id_projects_id_fk": { + "name": "workspace_runtime_services_project_id_projects_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk": { + "name": "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_issue_id_issues_id_fk": { + "name": "workspace_runtime_services_issue_id_issues_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_owner_agent_id_agents_id_fk": { + "name": "workspace_runtime_services_owner_agent_id_agents_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk": { + "name": "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "started_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/src/migrations/meta/0050_snapshot.json b/packages/db/src/migrations/meta/0050_snapshot.json new file mode 100644 index 00000000000..9c16c2c14b2 --- /dev/null +++ b/packages/db/src/migrations/meta/0050_snapshot.json @@ -0,0 +1,12772 @@ +{ + "id": "536b076f-aa3e-4c85-bca3-0685df608c6d", + "prevId": "08a85437-3008-49a3-af78-d7a1c10a0437", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.activity_log": { + "name": "activity_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "activity_log_company_created_idx": { + "name": "activity_log_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_run_id_idx": { + "name": "activity_log_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_entity_type_id_idx": { + "name": "activity_log_entity_type_id_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "activity_log_company_id_companies_id_fk": { + "name": "activity_log_company_id_companies_id_fk", + "tableFrom": "activity_log", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_agent_id_agents_id_fk": { + "name": "activity_log_agent_id_agents_id_fk", + "tableFrom": "activity_log", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_run_id_heartbeat_runs_id_fk": { + "name": "activity_log_run_id_heartbeat_runs_id_fk", + "tableFrom": "activity_log", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_api_keys": { + "name": "agent_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_api_keys_key_hash_idx": { + "name": "agent_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_api_keys_company_agent_idx": { + "name": "agent_api_keys_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_api_keys_agent_id_agents_id_fk": { + "name": "agent_api_keys_agent_id_agents_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_api_keys_company_id_companies_id_fk": { + "name": "agent_api_keys_company_id_companies_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_config_revisions": { + "name": "agent_config_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'patch'" + }, + "rolled_back_from_revision_id": { + "name": "rolled_back_from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "changed_keys": { + "name": "changed_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "before_config": { + "name": "before_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "after_config": { + "name": "after_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_config_revisions_company_agent_created_idx": { + "name": "agent_config_revisions_company_agent_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_config_revisions_agent_created_idx": { + "name": "agent_config_revisions_agent_created_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_config_revisions_company_id_companies_id_fk": { + "name": "agent_config_revisions_company_id_companies_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_config_revisions_agent_id_agents_id_fk": { + "name": "agent_config_revisions_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_config_revisions_created_by_agent_id_agents_id_fk": { + "name": "agent_config_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_runtime_state": { + "name": "agent_runtime_state", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_json": { + "name": "state_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_run_status": { + "name": "last_run_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total_input_tokens": { + "name": "total_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_output_tokens": { + "name": "total_output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cached_input_tokens": { + "name": "total_cached_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost_cents": { + "name": "total_cost_cents", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_runtime_state_company_agent_idx": { + "name": "agent_runtime_state_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_runtime_state_company_updated_idx": { + "name": "agent_runtime_state_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_runtime_state_agent_id_agents_id_fk": { + "name": "agent_runtime_state_agent_id_agents_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_runtime_state_company_id_companies_id_fk": { + "name": "agent_runtime_state_company_id_companies_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_task_sessions": { + "name": "agent_task_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "task_key": { + "name": "task_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_params_json": { + "name": "session_params_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_display_id": { + "name": "session_display_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_task_sessions_company_agent_adapter_task_uniq": { + "name": "agent_task_sessions_company_agent_adapter_task_uniq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "adapter_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_agent_updated_idx": { + "name": "agent_task_sessions_company_agent_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_task_updated_idx": { + "name": "agent_task_sessions_company_task_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_task_sessions_company_id_companies_id_fk": { + "name": "agent_task_sessions_company_id_companies_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_agent_id_agents_id_fk": { + "name": "agent_task_sessions_agent_id_agents_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_last_run_id_heartbeat_runs_id_fk": { + "name": "agent_task_sessions_last_run_id_heartbeat_runs_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "last_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_wakeup_requests": { + "name": "agent_wakeup_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "coalesced_count": { + "name": "coalesced_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "requested_by_actor_type": { + "name": "requested_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by_actor_id": { + "name": "requested_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_wakeup_requests_company_agent_status_idx": { + "name": "agent_wakeup_requests_company_agent_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_company_requested_idx": { + "name": "agent_wakeup_requests_company_requested_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_agent_requested_idx": { + "name": "agent_wakeup_requests_agent_requested_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_wakeup_requests_company_id_companies_id_fk": { + "name": "agent_wakeup_requests_company_id_companies_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_wakeup_requests_agent_id_agents_id_fk": { + "name": "agent_wakeup_requests_agent_id_agents_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "reports_to": { + "name": "reports_to", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'process'" + }, + "adapter_config": { + "name": "adapter_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "runtime_config": { + "name": "runtime_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_heartbeat_at": { + "name": "last_heartbeat_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agents_company_status_idx": { + "name": "agents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_company_reports_to_idx": { + "name": "agents_company_reports_to_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reports_to", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agents_company_id_companies_id_fk": { + "name": "agents_company_id_companies_id_fk", + "tableFrom": "agents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agents_reports_to_agents_id_fk": { + "name": "agents_reports_to_agents_id_fk", + "tableFrom": "agents", + "tableTo": "agents", + "columnsFrom": [ + "reports_to" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approval_comments": { + "name": "approval_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approval_comments_company_idx": { + "name": "approval_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_idx": { + "name": "approval_comments_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_created_idx": { + "name": "approval_comments_approval_created_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approval_comments_company_id_companies_id_fk": { + "name": "approval_comments_company_id_companies_id_fk", + "tableFrom": "approval_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_approval_id_approvals_id_fk": { + "name": "approval_comments_approval_id_approvals_id_fk", + "tableFrom": "approval_comments", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_author_agent_id_agents_id_fk": { + "name": "approval_comments_author_agent_id_agents_id_fk", + "tableFrom": "approval_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approvals": { + "name": "approvals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_by_agent_id": { + "name": "requested_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_by_user_id": { + "name": "requested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "decision_note": { + "name": "decision_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approvals_company_status_type_idx": { + "name": "approvals_company_status_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approvals_company_id_companies_id_fk": { + "name": "approvals_company_id_companies_id_fk", + "tableFrom": "approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approvals_requested_by_agent_id_agents_id_fk": { + "name": "approvals_requested_by_agent_id_agents_id_fk", + "tableFrom": "approvals", + "tableTo": "agents", + "columnsFrom": [ + "requested_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.assets": { + "name": "assets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "object_key": { + "name": "object_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "original_filename": { + "name": "original_filename", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "assets_company_created_idx": { + "name": "assets_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_provider_idx": { + "name": "assets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_object_key_uq": { + "name": "assets_company_object_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "assets_company_id_companies_id_fk": { + "name": "assets_company_id_companies_id_fk", + "tableFrom": "assets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "assets_created_by_agent_id_agents_id_fk": { + "name": "assets_created_by_agent_id_agents_id_fk", + "tableFrom": "assets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.board_api_keys": { + "name": "board_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "board_api_keys_key_hash_idx": { + "name": "board_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "board_api_keys_user_idx": { + "name": "board_api_keys_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "board_api_keys_user_id_user_id_fk": { + "name": "board_api_keys_user_id_user_id_fk", + "tableFrom": "board_api_keys", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_incidents": { + "name": "budget_incidents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "policy_id": { + "name": "policy_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_end": { + "name": "window_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "threshold_type": { + "name": "threshold_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_limit": { + "name": "amount_limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "amount_observed": { + "name": "amount_observed", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_incidents_company_status_idx": { + "name": "budget_incidents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_company_scope_idx": { + "name": "budget_incidents_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_policy_window_threshold_idx": { + "name": "budget_incidents_policy_window_threshold_idx", + "columns": [ + { + "expression": "policy_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "threshold_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"budget_incidents\".\"status\" <> 'dismissed'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_incidents_company_id_companies_id_fk": { + "name": "budget_incidents_company_id_companies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_policy_id_budget_policies_id_fk": { + "name": "budget_incidents_policy_id_budget_policies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "budget_policies", + "columnsFrom": [ + "policy_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_approval_id_approvals_id_fk": { + "name": "budget_incidents_approval_id_approvals_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_policies": { + "name": "budget_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'billed_cents'" + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "warn_percent": { + "name": "warn_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 80 + }, + "hard_stop_enabled": { + "name": "hard_stop_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "notify_enabled": { + "name": "notify_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_policies_company_scope_active_idx": { + "name": "budget_policies_company_scope_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_window_idx": { + "name": "budget_policies_company_window_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_scope_metric_unique_idx": { + "name": "budget_policies_company_scope_metric_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_policies_company_id_companies_id_fk": { + "name": "budget_policies_company_id_companies_id_fk", + "tableFrom": "budget_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cli_auth_challenges": { + "name": "cli_auth_challenges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_hash": { + "name": "secret_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_access": { + "name": "requested_access", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'board'" + }, + "requested_company_id": { + "name": "requested_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "pending_key_hash": { + "name": "pending_key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pending_key_name": { + "name": "pending_key_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "board_api_key_id": { + "name": "board_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cli_auth_challenges_secret_hash_idx": { + "name": "cli_auth_challenges_secret_hash_idx", + "columns": [ + { + "expression": "secret_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_approved_by_idx": { + "name": "cli_auth_challenges_approved_by_idx", + "columns": [ + { + "expression": "approved_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_requested_company_idx": { + "name": "cli_auth_challenges_requested_company_idx", + "columns": [ + { + "expression": "requested_company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cli_auth_challenges_requested_company_id_companies_id_fk": { + "name": "cli_auth_challenges_requested_company_id_companies_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "companies", + "columnsFrom": [ + "requested_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_approved_by_user_id_user_id_fk": { + "name": "cli_auth_challenges_approved_by_user_id_user_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "user", + "columnsFrom": [ + "approved_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk": { + "name": "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "board_api_keys", + "columnsFrom": [ + "board_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.companies": { + "name": "companies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "issue_prefix": { + "name": "issue_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'PAP'" + }, + "issue_counter": { + "name": "issue_counter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "require_board_approval_for_new_agents": { + "name": "require_board_approval_for_new_agents", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "feedback_data_sharing_enabled": { + "name": "feedback_data_sharing_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "feedback_data_sharing_consent_at": { + "name": "feedback_data_sharing_consent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "feedback_data_sharing_consent_by_user_id": { + "name": "feedback_data_sharing_consent_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feedback_data_sharing_terms_version": { + "name": "feedback_data_sharing_terms_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "brand_color": { + "name": "brand_color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "companies_issue_prefix_idx": { + "name": "companies_issue_prefix_idx", + "columns": [ + { + "expression": "issue_prefix", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_logos": { + "name": "company_logos", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_logos_company_uq": { + "name": "company_logos_company_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_logos_asset_uq": { + "name": "company_logos_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_logos_company_id_companies_id_fk": { + "name": "company_logos_company_id_companies_id_fk", + "tableFrom": "company_logos", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_logos_asset_id_assets_id_fk": { + "name": "company_logos_asset_id_assets_id_fk", + "tableFrom": "company_logos", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_memberships": { + "name": "company_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "membership_role": { + "name": "membership_role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_memberships_company_principal_unique_idx": { + "name": "company_memberships_company_principal_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_principal_status_idx": { + "name": "company_memberships_principal_status_idx", + "columns": [ + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_company_status_idx": { + "name": "company_memberships_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_memberships_company_id_companies_id_fk": { + "name": "company_memberships_company_id_companies_id_fk", + "tableFrom": "company_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_versions": { + "name": "company_secret_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "material": { + "name": "material", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "value_sha256": { + "name": "value_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "company_secret_versions_secret_idx": { + "name": "company_secret_versions_secret_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_value_sha256_idx": { + "name": "company_secret_versions_value_sha256_idx", + "columns": [ + { + "expression": "value_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_secret_version_uq": { + "name": "company_secret_versions_secret_version_uq", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_versions_secret_id_company_secrets_id_fk": { + "name": "company_secret_versions_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_versions_created_by_agent_id_agents_id_fk": { + "name": "company_secret_versions_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secrets": { + "name": "company_secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_encrypted'" + }, + "external_ref": { + "name": "external_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "latest_version": { + "name": "latest_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secrets_company_idx": { + "name": "company_secrets_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_provider_idx": { + "name": "company_secrets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_name_uq": { + "name": "company_secrets_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secrets_company_id_companies_id_fk": { + "name": "company_secrets_company_id_companies_id_fk", + "tableFrom": "company_secrets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secrets_created_by_agent_id_agents_id_fk": { + "name": "company_secrets_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secrets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skills": { + "name": "company_skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "markdown": { + "name": "markdown", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "source_locator": { + "name": "source_locator", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_ref": { + "name": "source_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trust_level": { + "name": "trust_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown_only'" + }, + "compatibility": { + "name": "compatibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'compatible'" + }, + "file_inventory": { + "name": "file_inventory", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skills_company_key_idx": { + "name": "company_skills_company_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_name_idx": { + "name": "company_skills_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skills_company_id_companies_id_fk": { + "name": "company_skills_company_id_companies_id_fk", + "tableFrom": "company_skills", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cost_events": { + "name": "cost_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "billing_type": { + "name": "billing_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cached_input_tokens": { + "name": "cached_input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cost_cents": { + "name": "cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cost_events_company_occurred_idx": { + "name": "cost_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_agent_occurred_idx": { + "name": "cost_events_company_agent_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_provider_occurred_idx": { + "name": "cost_events_company_provider_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_biller_occurred_idx": { + "name": "cost_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_heartbeat_run_idx": { + "name": "cost_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cost_events_company_id_companies_id_fk": { + "name": "cost_events_company_id_companies_id_fk", + "tableFrom": "cost_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_agent_id_agents_id_fk": { + "name": "cost_events_agent_id_agents_id_fk", + "tableFrom": "cost_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_issue_id_issues_id_fk": { + "name": "cost_events_issue_id_issues_id_fk", + "tableFrom": "cost_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_project_id_projects_id_fk": { + "name": "cost_events_project_id_projects_id_fk", + "tableFrom": "cost_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_goal_id_goals_id_fk": { + "name": "cost_events_goal_id_goals_id_fk", + "tableFrom": "cost_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "cost_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "cost_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_revisions": { + "name": "document_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_revisions_document_revision_uq": { + "name": "document_revisions_document_revision_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_revisions_company_document_created_idx": { + "name": "document_revisions_company_document_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_revisions_company_id_companies_id_fk": { + "name": "document_revisions_company_id_companies_id_fk", + "tableFrom": "document_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_revisions_document_id_documents_id_fk": { + "name": "document_revisions_document_id_documents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_revisions_created_by_agent_id_agents_id_fk": { + "name": "document_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_revisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "document_revisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "document_revisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.documents": { + "name": "documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "latest_body": { + "name": "latest_body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "latest_revision_id": { + "name": "latest_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "latest_revision_number": { + "name": "latest_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "documents_company_updated_idx": { + "name": "documents_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_company_created_idx": { + "name": "documents_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "documents_company_id_companies_id_fk": { + "name": "documents_company_id_companies_id_fk", + "tableFrom": "documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "documents_created_by_agent_id_agents_id_fk": { + "name": "documents_created_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "documents_updated_by_agent_id_agents_id_fk": { + "name": "documents_updated_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_workspaces": { + "name": "execution_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "strategy_type": { + "name": "strategy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_ref": { + "name": "base_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch_name": { + "name": "branch_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_fs'" + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "derived_from_execution_workspace_id": { + "name": "derived_from_execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "opened_at": { + "name": "opened_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_eligible_at": { + "name": "cleanup_eligible_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_reason": { + "name": "cleanup_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_workspaces_company_project_status_idx": { + "name": "execution_workspaces_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_project_workspace_status_idx": { + "name": "execution_workspaces_company_project_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_source_issue_idx": { + "name": "execution_workspaces_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_last_used_idx": { + "name": "execution_workspaces_company_last_used_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_branch_idx": { + "name": "execution_workspaces_company_branch_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspaces_company_id_companies_id_fk": { + "name": "execution_workspaces_company_id_companies_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "execution_workspaces_project_id_projects_id_fk": { + "name": "execution_workspaces_project_id_projects_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspaces_project_workspace_id_project_workspaces_id_fk": { + "name": "execution_workspaces_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_source_issue_id_issues_id_fk": { + "name": "execution_workspaces_source_issue_id_issues_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk": { + "name": "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "derived_from_execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feedback_exports": { + "name": "feedback_exports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "feedback_vote_id": { + "name": "feedback_vote_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_only'" + }, + "destination": { + "name": "destination", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "export_id": { + "name": "export_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consent_version": { + "name": "consent_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "schema_version": { + "name": "schema_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-envelope-v2'" + }, + "bundle_version": { + "name": "bundle_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-bundle-v2'" + }, + "payload_version": { + "name": "payload_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-v1'" + }, + "payload_digest": { + "name": "payload_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload_snapshot": { + "name": "payload_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "target_summary": { + "name": "target_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "redaction_summary": { + "name": "redaction_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempted_at": { + "name": "last_attempted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "exported_at": { + "name": "exported_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_exports_feedback_vote_idx": { + "name": "feedback_exports_feedback_vote_idx", + "columns": [ + { + "expression": "feedback_vote_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_created_idx": { + "name": "feedback_exports_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_status_idx": { + "name": "feedback_exports_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_issue_idx": { + "name": "feedback_exports_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_project_idx": { + "name": "feedback_exports_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_author_idx": { + "name": "feedback_exports_company_author_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_exports_company_id_companies_id_fk": { + "name": "feedback_exports_company_id_companies_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "feedback_exports_feedback_vote_id_feedback_votes_id_fk": { + "name": "feedback_exports_feedback_vote_id_feedback_votes_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "feedback_votes", + "columnsFrom": [ + "feedback_vote_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_exports_issue_id_issues_id_fk": { + "name": "feedback_exports_issue_id_issues_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_exports_project_id_projects_id_fk": { + "name": "feedback_exports_project_id_projects_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feedback_votes": { + "name": "feedback_votes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_with_labs": { + "name": "shared_with_labs", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "shared_at": { + "name": "shared_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "consent_version": { + "name": "consent_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redaction_summary": { + "name": "redaction_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_votes_company_issue_idx": { + "name": "feedback_votes_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_issue_target_idx": { + "name": "feedback_votes_issue_target_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_author_idx": { + "name": "feedback_votes_author_idx", + "columns": [ + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_company_target_author_idx": { + "name": "feedback_votes_company_target_author_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_votes_company_id_companies_id_fk": { + "name": "feedback_votes_company_id_companies_id_fk", + "tableFrom": "feedback_votes", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "feedback_votes_issue_id_issues_id_fk": { + "name": "feedback_votes_issue_id_issues_id_fk", + "tableFrom": "feedback_votes", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.finance_events": { + "name": "finance_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cost_event_id": { + "name": "cost_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "event_kind": { + "name": "event_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'debit'" + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_adapter_type": { + "name": "execution_adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pricing_tier": { + "name": "pricing_tier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "estimated": { + "name": "estimated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "external_invoice_id": { + "name": "external_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata_json": { + "name": "metadata_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "finance_events_company_occurred_idx": { + "name": "finance_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_biller_occurred_idx": { + "name": "finance_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_kind_occurred_idx": { + "name": "finance_events_company_kind_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_direction_occurred_idx": { + "name": "finance_events_company_direction_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "direction", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_heartbeat_run_idx": { + "name": "finance_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_cost_event_idx": { + "name": "finance_events_company_cost_event_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "finance_events_company_id_companies_id_fk": { + "name": "finance_events_company_id_companies_id_fk", + "tableFrom": "finance_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_agent_id_agents_id_fk": { + "name": "finance_events_agent_id_agents_id_fk", + "tableFrom": "finance_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_issue_id_issues_id_fk": { + "name": "finance_events_issue_id_issues_id_fk", + "tableFrom": "finance_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_project_id_projects_id_fk": { + "name": "finance_events_project_id_projects_id_fk", + "tableFrom": "finance_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_goal_id_goals_id_fk": { + "name": "finance_events_goal_id_goals_id_fk", + "tableFrom": "finance_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "finance_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "finance_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_cost_event_id_cost_events_id_fk": { + "name": "finance_events_cost_event_id_cost_events_id_fk", + "tableFrom": "finance_events", + "tableTo": "cost_events", + "columnsFrom": [ + "cost_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.goals": { + "name": "goals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'task'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'planned'" + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "goals_company_idx": { + "name": "goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "goals_company_id_companies_id_fk": { + "name": "goals_company_id_companies_id_fk", + "tableFrom": "goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_parent_id_goals_id_fk": { + "name": "goals_parent_id_goals_id_fk", + "tableFrom": "goals", + "tableTo": "goals", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_owner_agent_id_agents_id_fk": { + "name": "goals_owner_agent_id_agents_id_fk", + "tableFrom": "goals", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_run_events": { + "name": "heartbeat_run_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stream": { + "name": "stream", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_run_events_run_seq_idx": { + "name": "heartbeat_run_events_run_seq_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_run_idx": { + "name": "heartbeat_run_events_company_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_created_idx": { + "name": "heartbeat_run_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_run_events_company_id_companies_id_fk": { + "name": "heartbeat_run_events_company_id_companies_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_events_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_agent_id_agents_id_fk": { + "name": "heartbeat_run_events_agent_id_agents_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_runs": { + "name": "heartbeat_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invocation_source": { + "name": "invocation_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'on_demand'" + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wakeup_request_id": { + "name": "wakeup_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "signal": { + "name": "signal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "usage_json": { + "name": "usage_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_json": { + "name": "result_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_id_before": { + "name": "session_id_before", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id_after": { + "name": "session_id_after", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_run_id": { + "name": "external_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "process_pid": { + "name": "process_pid", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "process_started_at": { + "name": "process_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "retry_of_run_id": { + "name": "retry_of_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "process_loss_retry_count": { + "name": "process_loss_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "context_snapshot": { + "name": "context_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_runs_company_agent_started_idx": { + "name": "heartbeat_runs_company_agent_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_runs_company_id_companies_id_fk": { + "name": "heartbeat_runs_company_id_companies_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_agent_id_agents_id_fk": { + "name": "heartbeat_runs_agent_id_agents_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk": { + "name": "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agent_wakeup_requests", + "columnsFrom": [ + "wakeup_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "retry_of_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_settings": { + "name": "instance_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "singleton_key": { + "name": "singleton_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "general": { + "name": "general", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "experimental": { + "name": "experimental", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_settings_singleton_key_idx": { + "name": "instance_settings_singleton_key_idx", + "columns": [ + { + "expression": "singleton_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_user_roles": { + "name": "instance_user_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'instance_admin'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_user_roles_user_role_unique_idx": { + "name": "instance_user_roles_user_role_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "instance_user_roles_role_idx": { + "name": "instance_user_roles_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invites": { + "name": "invites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "invite_type": { + "name": "invite_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company_join'" + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allowed_join_types": { + "name": "allowed_join_types", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'both'" + }, + "defaults_payload": { + "name": "defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "invited_by_user_id": { + "name": "invited_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invites_token_hash_unique_idx": { + "name": "invites_token_hash_unique_idx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invites_company_invite_state_idx": { + "name": "invites_company_invite_state_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invite_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revoked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invites_company_id_companies_id_fk": { + "name": "invites_company_id_companies_id_fk", + "tableFrom": "invites", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_approvals": { + "name": "issue_approvals", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "linked_by_agent_id": { + "name": "linked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "linked_by_user_id": { + "name": "linked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_approvals_issue_idx": { + "name": "issue_approvals_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_approval_idx": { + "name": "issue_approvals_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_company_idx": { + "name": "issue_approvals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_approvals_company_id_companies_id_fk": { + "name": "issue_approvals_company_id_companies_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_approvals_issue_id_issues_id_fk": { + "name": "issue_approvals_issue_id_issues_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_approval_id_approvals_id_fk": { + "name": "issue_approvals_approval_id_approvals_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_linked_by_agent_id_agents_id_fk": { + "name": "issue_approvals_linked_by_agent_id_agents_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "agents", + "columnsFrom": [ + "linked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_approvals_pk": { + "name": "issue_approvals_pk", + "columns": [ + "issue_id", + "approval_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_attachments": { + "name": "issue_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_comment_id": { + "name": "issue_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_attachments_company_issue_idx": { + "name": "issue_attachments_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_issue_comment_idx": { + "name": "issue_attachments_issue_comment_idx", + "columns": [ + { + "expression": "issue_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_asset_uq": { + "name": "issue_attachments_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_attachments_company_id_companies_id_fk": { + "name": "issue_attachments_company_id_companies_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_attachments_issue_id_issues_id_fk": { + "name": "issue_attachments_issue_id_issues_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_asset_id_assets_id_fk": { + "name": "issue_attachments_asset_id_assets_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_issue_comment_id_issue_comments_id_fk": { + "name": "issue_attachments_issue_comment_id_issue_comments_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issue_comments", + "columnsFrom": [ + "issue_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_comments": { + "name": "issue_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_comments_issue_idx": { + "name": "issue_comments_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_idx": { + "name": "issue_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_issue_created_at_idx": { + "name": "issue_comments_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_author_issue_created_at_idx": { + "name": "issue_comments_company_author_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_comments_company_id_companies_id_fk": { + "name": "issue_comments_company_id_companies_id_fk", + "tableFrom": "issue_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_issue_id_issues_id_fk": { + "name": "issue_comments_issue_id_issues_id_fk", + "tableFrom": "issue_comments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_author_agent_id_agents_id_fk": { + "name": "issue_comments_author_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_comments_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_documents": { + "name": "issue_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_documents_company_issue_key_uq": { + "name": "issue_documents_company_issue_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_document_uq": { + "name": "issue_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_company_issue_updated_idx": { + "name": "issue_documents_company_issue_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_documents_company_id_companies_id_fk": { + "name": "issue_documents_company_id_companies_id_fk", + "tableFrom": "issue_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_documents_issue_id_issues_id_fk": { + "name": "issue_documents_issue_id_issues_id_fk", + "tableFrom": "issue_documents", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_documents_document_id_documents_id_fk": { + "name": "issue_documents_document_id_documents_id_fk", + "tableFrom": "issue_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_inbox_archives": { + "name": "issue_inbox_archives", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_inbox_archives_company_issue_idx": { + "name": "issue_inbox_archives_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_user_idx": { + "name": "issue_inbox_archives_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_issue_user_idx": { + "name": "issue_inbox_archives_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_inbox_archives_company_id_companies_id_fk": { + "name": "issue_inbox_archives_company_id_companies_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_inbox_archives_issue_id_issues_id_fk": { + "name": "issue_inbox_archives_issue_id_issues_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_labels": { + "name": "issue_labels", + "schema": "", + "columns": { + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label_id": { + "name": "label_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_labels_issue_idx": { + "name": "issue_labels_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_label_idx": { + "name": "issue_labels_label_idx", + "columns": [ + { + "expression": "label_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_company_idx": { + "name": "issue_labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_labels_issue_id_issues_id_fk": { + "name": "issue_labels_issue_id_issues_id_fk", + "tableFrom": "issue_labels", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_label_id_labels_id_fk": { + "name": "issue_labels_label_id_labels_id_fk", + "tableFrom": "issue_labels", + "tableTo": "labels", + "columnsFrom": [ + "label_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_company_id_companies_id_fk": { + "name": "issue_labels_company_id_companies_id_fk", + "tableFrom": "issue_labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_labels_pk": { + "name": "issue_labels_pk", + "columns": [ + "issue_id", + "label_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_read_states": { + "name": "issue_read_states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_read_at": { + "name": "last_read_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_read_states_company_issue_idx": { + "name": "issue_read_states_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_user_idx": { + "name": "issue_read_states_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_issue_user_idx": { + "name": "issue_read_states_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_read_states_company_id_companies_id_fk": { + "name": "issue_read_states_company_id_companies_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_read_states_issue_id_issues_id_fk": { + "name": "issue_read_states_issue_id_issues_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_relations": { + "name": "issue_relations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "related_issue_id": { + "name": "related_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_relations_company_issue_idx": { + "name": "issue_relations_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_related_issue_idx": { + "name": "issue_relations_company_related_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "related_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_type_idx": { + "name": "issue_relations_company_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_edge_uq": { + "name": "issue_relations_company_edge_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "related_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_relations_company_id_companies_id_fk": { + "name": "issue_relations_company_id_companies_id_fk", + "tableFrom": "issue_relations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_relations_issue_id_issues_id_fk": { + "name": "issue_relations_issue_id_issues_id_fk", + "tableFrom": "issue_relations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_relations_related_issue_id_issues_id_fk": { + "name": "issue_relations_related_issue_id_issues_id_fk", + "tableFrom": "issue_relations", + "tableTo": "issues", + "columnsFrom": [ + "related_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_relations_created_by_agent_id_agents_id_fk": { + "name": "issue_relations_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_relations", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_work_products": { + "name": "issue_work_products", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "runtime_service_id": { + "name": "runtime_service_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "review_state": { + "name": "review_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_work_products_company_issue_type_idx": { + "name": "issue_work_products_company_issue_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_execution_workspace_type_idx": { + "name": "issue_work_products_company_execution_workspace_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_provider_external_id_idx": { + "name": "issue_work_products_company_provider_external_id_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_updated_idx": { + "name": "issue_work_products_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_work_products_company_id_companies_id_fk": { + "name": "issue_work_products_company_id_companies_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_work_products_project_id_projects_id_fk": { + "name": "issue_work_products_project_id_projects_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_issue_id_issues_id_fk": { + "name": "issue_work_products_issue_id_issues_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_work_products_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issue_work_products_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk": { + "name": "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "workspace_runtime_services", + "columnsFrom": [ + "runtime_service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_work_products_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issues": { + "name": "issues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assignee_user_id": { + "name": "assignee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "checkout_run_id": { + "name": "checkout_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_run_id": { + "name": "execution_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_agent_name_key": { + "name": "execution_agent_name_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_locked_at": { + "name": "execution_locked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_number": { + "name": "issue_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_kind": { + "name": "origin_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "origin_id": { + "name": "origin_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_depth": { + "name": "request_depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_adapter_overrides": { + "name": "assignee_adapter_overrides", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_preference": { + "name": "execution_workspace_preference", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_settings": { + "name": "execution_workspace_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "hidden_at": { + "name": "hidden_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issues_company_status_idx": { + "name": "issues_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_status_idx": { + "name": "issues_company_assignee_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_user_status_idx": { + "name": "issues_company_assignee_user_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_parent_idx": { + "name": "issues_company_parent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_idx": { + "name": "issues_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_origin_idx": { + "name": "issues_company_origin_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_workspace_idx": { + "name": "issues_company_project_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_execution_workspace_idx": { + "name": "issues_company_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_identifier_idx": { + "name": "issues_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_open_routine_execution_uq": { + "name": "issues_open_routine_execution_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'routine_execution'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"execution_run_id\" is not null\n and \"issues\".\"status\" in ('backlog', 'todo', 'in_progress', 'in_review', 'blocked')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issues_company_id_companies_id_fk": { + "name": "issues_company_id_companies_id_fk", + "tableFrom": "issues", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_id_projects_id_fk": { + "name": "issues_project_id_projects_id_fk", + "tableFrom": "issues", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_workspace_id_project_workspaces_id_fk": { + "name": "issues_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_goal_id_goals_id_fk": { + "name": "issues_goal_id_goals_id_fk", + "tableFrom": "issues", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_parent_id_issues_id_fk": { + "name": "issues_parent_id_issues_id_fk", + "tableFrom": "issues", + "tableTo": "issues", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_assignee_agent_id_agents_id_fk": { + "name": "issues_assignee_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_checkout_run_id_heartbeat_runs_id_fk": { + "name": "issues_checkout_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "checkout_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_execution_run_id_heartbeat_runs_id_fk": { + "name": "issues_execution_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "execution_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_created_by_agent_id_agents_id_fk": { + "name": "issues_created_by_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issues_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.join_requests": { + "name": "join_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "invite_id": { + "name": "invite_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_type": { + "name": "request_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending_approval'" + }, + "request_ip": { + "name": "request_ip", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requesting_user_id": { + "name": "requesting_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_email_snapshot": { + "name": "request_email_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_name": { + "name": "agent_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_defaults_payload": { + "name": "agent_defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "claim_secret_hash": { + "name": "claim_secret_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_secret_expires_at": { + "name": "claim_secret_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claim_secret_consumed_at": { + "name": "claim_secret_consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_agent_id": { + "name": "created_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "rejected_by_user_id": { + "name": "rejected_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejected_at": { + "name": "rejected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "join_requests_invite_unique_idx": { + "name": "join_requests_invite_unique_idx", + "columns": [ + { + "expression": "invite_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_company_status_type_created_idx": { + "name": "join_requests_company_status_type_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "join_requests_invite_id_invites_id_fk": { + "name": "join_requests_invite_id_invites_id_fk", + "tableFrom": "join_requests", + "tableTo": "invites", + "columnsFrom": [ + "invite_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_company_id_companies_id_fk": { + "name": "join_requests_company_id_companies_id_fk", + "tableFrom": "join_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_created_agent_id_agents_id_fk": { + "name": "join_requests_created_agent_id_agents_id_fk", + "tableFrom": "join_requests", + "tableTo": "agents", + "columnsFrom": [ + "created_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.labels": { + "name": "labels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "labels_company_idx": { + "name": "labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "labels_company_name_idx": { + "name": "labels_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "labels_company_id_companies_id_fk": { + "name": "labels_company_id_companies_id_fk", + "tableFrom": "labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_company_settings": { + "name": "plugin_company_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "settings_json": { + "name": "settings_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_company_settings_company_idx": { + "name": "plugin_company_settings_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_plugin_idx": { + "name": "plugin_company_settings_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_company_plugin_uq": { + "name": "plugin_company_settings_company_plugin_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_company_settings_company_id_companies_id_fk": { + "name": "plugin_company_settings_company_id_companies_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_company_settings_plugin_id_plugins_id_fk": { + "name": "plugin_company_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_config": { + "name": "plugin_config", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "config_json": { + "name": "config_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_config_plugin_id_idx": { + "name": "plugin_config_plugin_id_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_config_plugin_id_plugins_id_fk": { + "name": "plugin_config_plugin_id_plugins_id_fk", + "tableFrom": "plugin_config", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_entities": { + "name": "plugin_entities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_entities_plugin_idx": { + "name": "plugin_entities_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_type_idx": { + "name": "plugin_entities_type_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_scope_idx": { + "name": "plugin_entities_scope_idx", + "columns": [ + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_external_idx": { + "name": "plugin_entities_external_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_entities_plugin_id_plugins_id_fk": { + "name": "plugin_entities_plugin_id_plugins_id_fk", + "tableFrom": "plugin_entities", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_job_runs": { + "name": "plugin_job_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "job_id": { + "name": "job_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logs": { + "name": "logs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_job_runs_job_idx": { + "name": "plugin_job_runs_job_idx", + "columns": [ + { + "expression": "job_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_plugin_idx": { + "name": "plugin_job_runs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_status_idx": { + "name": "plugin_job_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_job_runs_job_id_plugin_jobs_id_fk": { + "name": "plugin_job_runs_job_id_plugin_jobs_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugin_jobs", + "columnsFrom": [ + "job_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_job_runs_plugin_id_plugins_id_fk": { + "name": "plugin_job_runs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_jobs": { + "name": "plugin_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "job_key": { + "name": "job_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_jobs_plugin_idx": { + "name": "plugin_jobs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_next_run_idx": { + "name": "plugin_jobs_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_unique_idx": { + "name": "plugin_jobs_unique_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "job_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_jobs_plugin_id_plugins_id_fk": { + "name": "plugin_jobs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_jobs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_logs": { + "name": "plugin_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'info'" + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "meta": { + "name": "meta", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_logs_plugin_time_idx": { + "name": "plugin_logs_plugin_time_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_logs_level_idx": { + "name": "plugin_logs_level_idx", + "columns": [ + { + "expression": "level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_logs_plugin_id_plugins_id_fk": { + "name": "plugin_logs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_logs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_state": { + "name": "plugin_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "namespace": { + "name": "namespace", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "state_key": { + "name": "state_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value_json": { + "name": "value_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_state_plugin_scope_idx": { + "name": "plugin_state_plugin_scope_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_state_plugin_id_plugins_id_fk": { + "name": "plugin_state_plugin_id_plugins_id_fk", + "tableFrom": "plugin_state", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "plugin_state_unique_entry_idx": { + "name": "plugin_state_unique_entry_idx", + "nullsNotDistinct": true, + "columns": [ + "plugin_id", + "scope_kind", + "scope_id", + "namespace", + "state_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_webhook_deliveries": { + "name": "plugin_webhook_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "webhook_key": { + "name": "webhook_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "headers": { + "name": "headers", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_webhook_deliveries_plugin_idx": { + "name": "plugin_webhook_deliveries_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_status_idx": { + "name": "plugin_webhook_deliveries_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_key_idx": { + "name": "plugin_webhook_deliveries_key_idx", + "columns": [ + { + "expression": "webhook_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_webhook_deliveries_plugin_id_plugins_id_fk": { + "name": "plugin_webhook_deliveries_plugin_id_plugins_id_fk", + "tableFrom": "plugin_webhook_deliveries", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugins": { + "name": "plugins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "package_name": { + "name": "package_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "api_version": { + "name": "api_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "manifest_json": { + "name": "manifest_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'installed'" + }, + "install_order": { + "name": "install_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "package_path": { + "name": "package_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_at": { + "name": "installed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugins_plugin_key_idx": { + "name": "plugins_plugin_key_idx", + "columns": [ + { + "expression": "plugin_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugins_status_idx": { + "name": "plugins_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.principal_permission_grants": { + "name": "principal_permission_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_key": { + "name": "permission_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "granted_by_user_id": { + "name": "granted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "principal_permission_grants_unique_idx": { + "name": "principal_permission_grants_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "principal_permission_grants_company_permission_idx": { + "name": "principal_permission_grants_company_permission_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "principal_permission_grants_company_id_companies_id_fk": { + "name": "principal_permission_grants_company_id_companies_id_fk", + "tableFrom": "principal_permission_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_goals": { + "name": "project_goals", + "schema": "", + "columns": { + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_goals_project_idx": { + "name": "project_goals_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_goal_idx": { + "name": "project_goals_goal_idx", + "columns": [ + { + "expression": "goal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_company_idx": { + "name": "project_goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_goals_project_id_projects_id_fk": { + "name": "project_goals_project_id_projects_id_fk", + "tableFrom": "project_goals", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_goal_id_goals_id_fk": { + "name": "project_goals_goal_id_goals_id_fk", + "tableFrom": "project_goals", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_company_id_companies_id_fk": { + "name": "project_goals_company_id_companies_id_fk", + "tableFrom": "project_goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "project_goals_project_id_goal_id_pk": { + "name": "project_goals_project_id_goal_id_pk", + "columns": [ + "project_id", + "goal_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_workspaces": { + "name": "project_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_ref": { + "name": "repo_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_ref": { + "name": "default_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "setup_command": { + "name": "setup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cleanup_command": { + "name": "cleanup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_provider": { + "name": "remote_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_workspace_ref": { + "name": "remote_workspace_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_workspace_key": { + "name": "shared_workspace_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_workspaces_company_project_idx": { + "name": "project_workspaces_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_primary_idx": { + "name": "project_workspaces_project_primary_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_primary", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_source_type_idx": { + "name": "project_workspaces_project_source_type_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_company_shared_key_idx": { + "name": "project_workspaces_company_shared_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "shared_workspace_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_remote_ref_idx": { + "name": "project_workspaces_project_remote_ref_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_workspace_ref", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_workspaces_company_id_companies_id_fk": { + "name": "project_workspaces_company_id_companies_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "project_workspaces_project_id_projects_id_fk": { + "name": "project_workspaces_project_id_projects_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "lead_agent_id": { + "name": "lead_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_date": { + "name": "target_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_policy": { + "name": "execution_workspace_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "projects_company_idx": { + "name": "projects_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "projects_company_id_companies_id_fk": { + "name": "projects_company_id_companies_id_fk", + "tableFrom": "projects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_goal_id_goals_id_fk": { + "name": "projects_goal_id_goals_id_fk", + "tableFrom": "projects", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_lead_agent_id_agents_id_fk": { + "name": "projects_lead_agent_id_agents_id_fk", + "tableFrom": "projects", + "tableTo": "agents", + "columnsFrom": [ + "lead_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_runs": { + "name": "routine_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger_id": { + "name": "trigger_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "triggered_at": { + "name": "triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_payload": { + "name": "trigger_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "linked_issue_id": { + "name": "linked_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "coalesced_into_run_id": { + "name": "coalesced_into_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_runs_company_routine_idx": { + "name": "routine_runs_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idx": { + "name": "routine_runs_trigger_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_linked_issue_idx": { + "name": "routine_runs_linked_issue_idx", + "columns": [ + { + "expression": "linked_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idempotency_idx": { + "name": "routine_runs_trigger_idempotency_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_runs_company_id_companies_id_fk": { + "name": "routine_runs_company_id_companies_id_fk", + "tableFrom": "routine_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_routine_id_routines_id_fk": { + "name": "routine_runs_routine_id_routines_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_trigger_id_routine_triggers_id_fk": { + "name": "routine_runs_trigger_id_routine_triggers_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routine_triggers", + "columnsFrom": [ + "trigger_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_runs_linked_issue_id_issues_id_fk": { + "name": "routine_runs_linked_issue_id_issues_id_fk", + "tableFrom": "routine_runs", + "tableTo": "issues", + "columnsFrom": [ + "linked_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_triggers": { + "name": "routine_triggers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "public_id": { + "name": "public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "signing_mode": { + "name": "signing_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "replay_window_sec": { + "name": "replay_window_sec", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_rotated_at": { + "name": "last_rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_result": { + "name": "last_result", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_triggers_company_routine_idx": { + "name": "routine_triggers_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_company_kind_idx": { + "name": "routine_triggers_company_kind_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_next_run_idx": { + "name": "routine_triggers_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_idx": { + "name": "routine_triggers_public_id_idx", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_uq": { + "name": "routine_triggers_public_id_uq", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_triggers_company_id_companies_id_fk": { + "name": "routine_triggers_company_id_companies_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_routine_id_routines_id_fk": { + "name": "routine_triggers_routine_id_routines_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_secret_id_company_secrets_id_fk": { + "name": "routine_triggers_secret_id_company_secrets_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_created_by_agent_id_agents_id_fk": { + "name": "routine_triggers_created_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_updated_by_agent_id_agents_id_fk": { + "name": "routine_triggers_updated_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routines": { + "name": "routines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_issue_id": { + "name": "parent_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "concurrency_policy": { + "name": "concurrency_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'coalesce_if_active'" + }, + "catch_up_policy": { + "name": "catch_up_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'skip_missed'" + }, + "variables": { + "name": "variables", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_triggered_at": { + "name": "last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_enqueued_at": { + "name": "last_enqueued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routines_company_status_idx": { + "name": "routines_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_assignee_idx": { + "name": "routines_company_assignee_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_project_idx": { + "name": "routines_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routines_company_id_companies_id_fk": { + "name": "routines_company_id_companies_id_fk", + "tableFrom": "routines", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_project_id_projects_id_fk": { + "name": "routines_project_id_projects_id_fk", + "tableFrom": "routines", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_goal_id_goals_id_fk": { + "name": "routines_goal_id_goals_id_fk", + "tableFrom": "routines", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_parent_issue_id_issues_id_fk": { + "name": "routines_parent_issue_id_issues_id_fk", + "tableFrom": "routines", + "tableTo": "issues", + "columnsFrom": [ + "parent_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_assignee_agent_id_agents_id_fk": { + "name": "routines_assignee_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "routines_created_by_agent_id_agents_id_fk": { + "name": "routines_created_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_updated_by_agent_id_agents_id_fk": { + "name": "routines_updated_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_operations": { + "name": "workspace_operations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_operations_company_run_started_idx": { + "name": "workspace_operations_company_run_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_operations_company_workspace_started_idx": { + "name": "workspace_operations_company_workspace_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_operations_company_id_companies_id_fk": { + "name": "workspace_operations_company_id_companies_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_operations_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_operations_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_runtime_services": { + "name": "workspace_runtime_services", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "service_name": { + "name": "service_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reuse_key": { + "name": "reuse_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "started_by_run_id": { + "name": "started_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stop_policy": { + "name": "stop_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_runtime_services_company_workspace_status_idx": { + "name": "workspace_runtime_services_company_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_execution_workspace_status_idx": { + "name": "workspace_runtime_services_company_execution_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_project_status_idx": { + "name": "workspace_runtime_services_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_run_idx": { + "name": "workspace_runtime_services_run_idx", + "columns": [ + { + "expression": "started_by_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_updated_idx": { + "name": "workspace_runtime_services_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_runtime_services_company_id_companies_id_fk": { + "name": "workspace_runtime_services_company_id_companies_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_id_projects_id_fk": { + "name": "workspace_runtime_services_project_id_projects_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk": { + "name": "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_issue_id_issues_id_fk": { + "name": "workspace_runtime_services_issue_id_issues_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_owner_agent_id_agents_id_fk": { + "name": "workspace_runtime_services_owner_agent_id_agents_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk": { + "name": "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "started_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/src/migrations/meta/0051_snapshot.json b/packages/db/src/migrations/meta/0051_snapshot.json new file mode 100644 index 00000000000..d70ecb3a19c --- /dev/null +++ b/packages/db/src/migrations/meta/0051_snapshot.json @@ -0,0 +1,12836 @@ +{ + "id": "fb74b316-b3b6-43f5-a6d6-a7d310064a68", + "prevId": "536b076f-aa3e-4c85-bca3-0685df608c6d", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.activity_log": { + "name": "activity_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "activity_log_company_created_idx": { + "name": "activity_log_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_run_id_idx": { + "name": "activity_log_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_entity_type_id_idx": { + "name": "activity_log_entity_type_id_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "activity_log_company_id_companies_id_fk": { + "name": "activity_log_company_id_companies_id_fk", + "tableFrom": "activity_log", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_agent_id_agents_id_fk": { + "name": "activity_log_agent_id_agents_id_fk", + "tableFrom": "activity_log", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_run_id_heartbeat_runs_id_fk": { + "name": "activity_log_run_id_heartbeat_runs_id_fk", + "tableFrom": "activity_log", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_api_keys": { + "name": "agent_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_api_keys_key_hash_idx": { + "name": "agent_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_api_keys_company_agent_idx": { + "name": "agent_api_keys_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_api_keys_agent_id_agents_id_fk": { + "name": "agent_api_keys_agent_id_agents_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_api_keys_company_id_companies_id_fk": { + "name": "agent_api_keys_company_id_companies_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_config_revisions": { + "name": "agent_config_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'patch'" + }, + "rolled_back_from_revision_id": { + "name": "rolled_back_from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "changed_keys": { + "name": "changed_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "before_config": { + "name": "before_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "after_config": { + "name": "after_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_config_revisions_company_agent_created_idx": { + "name": "agent_config_revisions_company_agent_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_config_revisions_agent_created_idx": { + "name": "agent_config_revisions_agent_created_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_config_revisions_company_id_companies_id_fk": { + "name": "agent_config_revisions_company_id_companies_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_config_revisions_agent_id_agents_id_fk": { + "name": "agent_config_revisions_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_config_revisions_created_by_agent_id_agents_id_fk": { + "name": "agent_config_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_runtime_state": { + "name": "agent_runtime_state", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_json": { + "name": "state_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_run_status": { + "name": "last_run_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total_input_tokens": { + "name": "total_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_output_tokens": { + "name": "total_output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cached_input_tokens": { + "name": "total_cached_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost_cents": { + "name": "total_cost_cents", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_runtime_state_company_agent_idx": { + "name": "agent_runtime_state_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_runtime_state_company_updated_idx": { + "name": "agent_runtime_state_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_runtime_state_agent_id_agents_id_fk": { + "name": "agent_runtime_state_agent_id_agents_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_runtime_state_company_id_companies_id_fk": { + "name": "agent_runtime_state_company_id_companies_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_task_sessions": { + "name": "agent_task_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "task_key": { + "name": "task_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_params_json": { + "name": "session_params_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_display_id": { + "name": "session_display_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_task_sessions_company_agent_adapter_task_uniq": { + "name": "agent_task_sessions_company_agent_adapter_task_uniq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "adapter_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_agent_updated_idx": { + "name": "agent_task_sessions_company_agent_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_task_updated_idx": { + "name": "agent_task_sessions_company_task_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_task_sessions_company_id_companies_id_fk": { + "name": "agent_task_sessions_company_id_companies_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_agent_id_agents_id_fk": { + "name": "agent_task_sessions_agent_id_agents_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_last_run_id_heartbeat_runs_id_fk": { + "name": "agent_task_sessions_last_run_id_heartbeat_runs_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "last_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_wakeup_requests": { + "name": "agent_wakeup_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "coalesced_count": { + "name": "coalesced_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "requested_by_actor_type": { + "name": "requested_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by_actor_id": { + "name": "requested_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_wakeup_requests_company_agent_status_idx": { + "name": "agent_wakeup_requests_company_agent_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_company_requested_idx": { + "name": "agent_wakeup_requests_company_requested_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_agent_requested_idx": { + "name": "agent_wakeup_requests_agent_requested_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_wakeup_requests_company_id_companies_id_fk": { + "name": "agent_wakeup_requests_company_id_companies_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_wakeup_requests_agent_id_agents_id_fk": { + "name": "agent_wakeup_requests_agent_id_agents_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "reports_to": { + "name": "reports_to", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'process'" + }, + "adapter_config": { + "name": "adapter_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "runtime_config": { + "name": "runtime_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_heartbeat_at": { + "name": "last_heartbeat_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agents_company_status_idx": { + "name": "agents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_company_reports_to_idx": { + "name": "agents_company_reports_to_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reports_to", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agents_company_id_companies_id_fk": { + "name": "agents_company_id_companies_id_fk", + "tableFrom": "agents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agents_reports_to_agents_id_fk": { + "name": "agents_reports_to_agents_id_fk", + "tableFrom": "agents", + "tableTo": "agents", + "columnsFrom": [ + "reports_to" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approval_comments": { + "name": "approval_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approval_comments_company_idx": { + "name": "approval_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_idx": { + "name": "approval_comments_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_created_idx": { + "name": "approval_comments_approval_created_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approval_comments_company_id_companies_id_fk": { + "name": "approval_comments_company_id_companies_id_fk", + "tableFrom": "approval_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_approval_id_approvals_id_fk": { + "name": "approval_comments_approval_id_approvals_id_fk", + "tableFrom": "approval_comments", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_author_agent_id_agents_id_fk": { + "name": "approval_comments_author_agent_id_agents_id_fk", + "tableFrom": "approval_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approvals": { + "name": "approvals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_by_agent_id": { + "name": "requested_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_by_user_id": { + "name": "requested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "decision_note": { + "name": "decision_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approvals_company_status_type_idx": { + "name": "approvals_company_status_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approvals_company_id_companies_id_fk": { + "name": "approvals_company_id_companies_id_fk", + "tableFrom": "approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approvals_requested_by_agent_id_agents_id_fk": { + "name": "approvals_requested_by_agent_id_agents_id_fk", + "tableFrom": "approvals", + "tableTo": "agents", + "columnsFrom": [ + "requested_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.assets": { + "name": "assets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "object_key": { + "name": "object_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "original_filename": { + "name": "original_filename", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "assets_company_created_idx": { + "name": "assets_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_provider_idx": { + "name": "assets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_object_key_uq": { + "name": "assets_company_object_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "assets_company_id_companies_id_fk": { + "name": "assets_company_id_companies_id_fk", + "tableFrom": "assets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "assets_created_by_agent_id_agents_id_fk": { + "name": "assets_created_by_agent_id_agents_id_fk", + "tableFrom": "assets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.board_api_keys": { + "name": "board_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "board_api_keys_key_hash_idx": { + "name": "board_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "board_api_keys_user_idx": { + "name": "board_api_keys_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "board_api_keys_user_id_user_id_fk": { + "name": "board_api_keys_user_id_user_id_fk", + "tableFrom": "board_api_keys", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_incidents": { + "name": "budget_incidents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "policy_id": { + "name": "policy_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_end": { + "name": "window_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "threshold_type": { + "name": "threshold_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_limit": { + "name": "amount_limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "amount_observed": { + "name": "amount_observed", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_incidents_company_status_idx": { + "name": "budget_incidents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_company_scope_idx": { + "name": "budget_incidents_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_policy_window_threshold_idx": { + "name": "budget_incidents_policy_window_threshold_idx", + "columns": [ + { + "expression": "policy_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "threshold_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"budget_incidents\".\"status\" <> 'dismissed'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_incidents_company_id_companies_id_fk": { + "name": "budget_incidents_company_id_companies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_policy_id_budget_policies_id_fk": { + "name": "budget_incidents_policy_id_budget_policies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "budget_policies", + "columnsFrom": [ + "policy_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_approval_id_approvals_id_fk": { + "name": "budget_incidents_approval_id_approvals_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_policies": { + "name": "budget_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'billed_cents'" + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "warn_percent": { + "name": "warn_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 80 + }, + "hard_stop_enabled": { + "name": "hard_stop_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "notify_enabled": { + "name": "notify_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_policies_company_scope_active_idx": { + "name": "budget_policies_company_scope_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_window_idx": { + "name": "budget_policies_company_window_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_scope_metric_unique_idx": { + "name": "budget_policies_company_scope_metric_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_policies_company_id_companies_id_fk": { + "name": "budget_policies_company_id_companies_id_fk", + "tableFrom": "budget_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cli_auth_challenges": { + "name": "cli_auth_challenges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_hash": { + "name": "secret_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_access": { + "name": "requested_access", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'board'" + }, + "requested_company_id": { + "name": "requested_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "pending_key_hash": { + "name": "pending_key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pending_key_name": { + "name": "pending_key_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "board_api_key_id": { + "name": "board_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cli_auth_challenges_secret_hash_idx": { + "name": "cli_auth_challenges_secret_hash_idx", + "columns": [ + { + "expression": "secret_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_approved_by_idx": { + "name": "cli_auth_challenges_approved_by_idx", + "columns": [ + { + "expression": "approved_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_requested_company_idx": { + "name": "cli_auth_challenges_requested_company_idx", + "columns": [ + { + "expression": "requested_company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cli_auth_challenges_requested_company_id_companies_id_fk": { + "name": "cli_auth_challenges_requested_company_id_companies_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "companies", + "columnsFrom": [ + "requested_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_approved_by_user_id_user_id_fk": { + "name": "cli_auth_challenges_approved_by_user_id_user_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "user", + "columnsFrom": [ + "approved_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk": { + "name": "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "board_api_keys", + "columnsFrom": [ + "board_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.companies": { + "name": "companies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "issue_prefix": { + "name": "issue_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'PAP'" + }, + "issue_counter": { + "name": "issue_counter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "require_board_approval_for_new_agents": { + "name": "require_board_approval_for_new_agents", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "feedback_data_sharing_enabled": { + "name": "feedback_data_sharing_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "feedback_data_sharing_consent_at": { + "name": "feedback_data_sharing_consent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "feedback_data_sharing_consent_by_user_id": { + "name": "feedback_data_sharing_consent_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feedback_data_sharing_terms_version": { + "name": "feedback_data_sharing_terms_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "brand_color": { + "name": "brand_color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "companies_issue_prefix_idx": { + "name": "companies_issue_prefix_idx", + "columns": [ + { + "expression": "issue_prefix", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_logos": { + "name": "company_logos", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_logos_company_uq": { + "name": "company_logos_company_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_logos_asset_uq": { + "name": "company_logos_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_logos_company_id_companies_id_fk": { + "name": "company_logos_company_id_companies_id_fk", + "tableFrom": "company_logos", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_logos_asset_id_assets_id_fk": { + "name": "company_logos_asset_id_assets_id_fk", + "tableFrom": "company_logos", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_memberships": { + "name": "company_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "membership_role": { + "name": "membership_role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_memberships_company_principal_unique_idx": { + "name": "company_memberships_company_principal_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_principal_status_idx": { + "name": "company_memberships_principal_status_idx", + "columns": [ + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_company_status_idx": { + "name": "company_memberships_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_memberships_company_id_companies_id_fk": { + "name": "company_memberships_company_id_companies_id_fk", + "tableFrom": "company_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_versions": { + "name": "company_secret_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "material": { + "name": "material", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "value_sha256": { + "name": "value_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "company_secret_versions_secret_idx": { + "name": "company_secret_versions_secret_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_value_sha256_idx": { + "name": "company_secret_versions_value_sha256_idx", + "columns": [ + { + "expression": "value_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_secret_version_uq": { + "name": "company_secret_versions_secret_version_uq", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_versions_secret_id_company_secrets_id_fk": { + "name": "company_secret_versions_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_versions_created_by_agent_id_agents_id_fk": { + "name": "company_secret_versions_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secrets": { + "name": "company_secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_encrypted'" + }, + "external_ref": { + "name": "external_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "latest_version": { + "name": "latest_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secrets_company_idx": { + "name": "company_secrets_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_provider_idx": { + "name": "company_secrets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_name_uq": { + "name": "company_secrets_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secrets_company_id_companies_id_fk": { + "name": "company_secrets_company_id_companies_id_fk", + "tableFrom": "company_secrets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secrets_created_by_agent_id_agents_id_fk": { + "name": "company_secrets_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secrets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skills": { + "name": "company_skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "markdown": { + "name": "markdown", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "source_locator": { + "name": "source_locator", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_ref": { + "name": "source_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trust_level": { + "name": "trust_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown_only'" + }, + "compatibility": { + "name": "compatibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'compatible'" + }, + "file_inventory": { + "name": "file_inventory", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skills_company_key_idx": { + "name": "company_skills_company_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_name_idx": { + "name": "company_skills_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skills_company_id_companies_id_fk": { + "name": "company_skills_company_id_companies_id_fk", + "tableFrom": "company_skills", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cost_events": { + "name": "cost_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "billing_type": { + "name": "billing_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cached_input_tokens": { + "name": "cached_input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cost_cents": { + "name": "cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cost_events_company_occurred_idx": { + "name": "cost_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_agent_occurred_idx": { + "name": "cost_events_company_agent_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_provider_occurred_idx": { + "name": "cost_events_company_provider_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_biller_occurred_idx": { + "name": "cost_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_heartbeat_run_idx": { + "name": "cost_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cost_events_company_id_companies_id_fk": { + "name": "cost_events_company_id_companies_id_fk", + "tableFrom": "cost_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_agent_id_agents_id_fk": { + "name": "cost_events_agent_id_agents_id_fk", + "tableFrom": "cost_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_issue_id_issues_id_fk": { + "name": "cost_events_issue_id_issues_id_fk", + "tableFrom": "cost_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_project_id_projects_id_fk": { + "name": "cost_events_project_id_projects_id_fk", + "tableFrom": "cost_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_goal_id_goals_id_fk": { + "name": "cost_events_goal_id_goals_id_fk", + "tableFrom": "cost_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "cost_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "cost_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_revisions": { + "name": "document_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_revisions_document_revision_uq": { + "name": "document_revisions_document_revision_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_revisions_company_document_created_idx": { + "name": "document_revisions_company_document_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_revisions_company_id_companies_id_fk": { + "name": "document_revisions_company_id_companies_id_fk", + "tableFrom": "document_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_revisions_document_id_documents_id_fk": { + "name": "document_revisions_document_id_documents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_revisions_created_by_agent_id_agents_id_fk": { + "name": "document_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_revisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "document_revisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "document_revisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.documents": { + "name": "documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "latest_body": { + "name": "latest_body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "latest_revision_id": { + "name": "latest_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "latest_revision_number": { + "name": "latest_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "documents_company_updated_idx": { + "name": "documents_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_company_created_idx": { + "name": "documents_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "documents_company_id_companies_id_fk": { + "name": "documents_company_id_companies_id_fk", + "tableFrom": "documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "documents_created_by_agent_id_agents_id_fk": { + "name": "documents_created_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "documents_updated_by_agent_id_agents_id_fk": { + "name": "documents_updated_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_workspaces": { + "name": "execution_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "strategy_type": { + "name": "strategy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_ref": { + "name": "base_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch_name": { + "name": "branch_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_fs'" + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "derived_from_execution_workspace_id": { + "name": "derived_from_execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "opened_at": { + "name": "opened_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_eligible_at": { + "name": "cleanup_eligible_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_reason": { + "name": "cleanup_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_workspaces_company_project_status_idx": { + "name": "execution_workspaces_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_project_workspace_status_idx": { + "name": "execution_workspaces_company_project_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_source_issue_idx": { + "name": "execution_workspaces_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_last_used_idx": { + "name": "execution_workspaces_company_last_used_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_branch_idx": { + "name": "execution_workspaces_company_branch_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspaces_company_id_companies_id_fk": { + "name": "execution_workspaces_company_id_companies_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "execution_workspaces_project_id_projects_id_fk": { + "name": "execution_workspaces_project_id_projects_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspaces_project_workspace_id_project_workspaces_id_fk": { + "name": "execution_workspaces_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_source_issue_id_issues_id_fk": { + "name": "execution_workspaces_source_issue_id_issues_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk": { + "name": "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "derived_from_execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feedback_exports": { + "name": "feedback_exports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "feedback_vote_id": { + "name": "feedback_vote_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_only'" + }, + "destination": { + "name": "destination", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "export_id": { + "name": "export_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consent_version": { + "name": "consent_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "schema_version": { + "name": "schema_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-envelope-v2'" + }, + "bundle_version": { + "name": "bundle_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-bundle-v2'" + }, + "payload_version": { + "name": "payload_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-v1'" + }, + "payload_digest": { + "name": "payload_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload_snapshot": { + "name": "payload_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "target_summary": { + "name": "target_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "redaction_summary": { + "name": "redaction_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempted_at": { + "name": "last_attempted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "exported_at": { + "name": "exported_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_exports_feedback_vote_idx": { + "name": "feedback_exports_feedback_vote_idx", + "columns": [ + { + "expression": "feedback_vote_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_created_idx": { + "name": "feedback_exports_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_status_idx": { + "name": "feedback_exports_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_issue_idx": { + "name": "feedback_exports_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_project_idx": { + "name": "feedback_exports_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_author_idx": { + "name": "feedback_exports_company_author_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_exports_company_id_companies_id_fk": { + "name": "feedback_exports_company_id_companies_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "feedback_exports_feedback_vote_id_feedback_votes_id_fk": { + "name": "feedback_exports_feedback_vote_id_feedback_votes_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "feedback_votes", + "columnsFrom": [ + "feedback_vote_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_exports_issue_id_issues_id_fk": { + "name": "feedback_exports_issue_id_issues_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_exports_project_id_projects_id_fk": { + "name": "feedback_exports_project_id_projects_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feedback_votes": { + "name": "feedback_votes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_with_labs": { + "name": "shared_with_labs", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "shared_at": { + "name": "shared_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "consent_version": { + "name": "consent_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redaction_summary": { + "name": "redaction_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_votes_company_issue_idx": { + "name": "feedback_votes_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_issue_target_idx": { + "name": "feedback_votes_issue_target_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_author_idx": { + "name": "feedback_votes_author_idx", + "columns": [ + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_company_target_author_idx": { + "name": "feedback_votes_company_target_author_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_votes_company_id_companies_id_fk": { + "name": "feedback_votes_company_id_companies_id_fk", + "tableFrom": "feedback_votes", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "feedback_votes_issue_id_issues_id_fk": { + "name": "feedback_votes_issue_id_issues_id_fk", + "tableFrom": "feedback_votes", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.finance_events": { + "name": "finance_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cost_event_id": { + "name": "cost_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "event_kind": { + "name": "event_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'debit'" + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_adapter_type": { + "name": "execution_adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pricing_tier": { + "name": "pricing_tier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "estimated": { + "name": "estimated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "external_invoice_id": { + "name": "external_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata_json": { + "name": "metadata_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "finance_events_company_occurred_idx": { + "name": "finance_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_biller_occurred_idx": { + "name": "finance_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_kind_occurred_idx": { + "name": "finance_events_company_kind_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_direction_occurred_idx": { + "name": "finance_events_company_direction_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "direction", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_heartbeat_run_idx": { + "name": "finance_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_cost_event_idx": { + "name": "finance_events_company_cost_event_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "finance_events_company_id_companies_id_fk": { + "name": "finance_events_company_id_companies_id_fk", + "tableFrom": "finance_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_agent_id_agents_id_fk": { + "name": "finance_events_agent_id_agents_id_fk", + "tableFrom": "finance_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_issue_id_issues_id_fk": { + "name": "finance_events_issue_id_issues_id_fk", + "tableFrom": "finance_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_project_id_projects_id_fk": { + "name": "finance_events_project_id_projects_id_fk", + "tableFrom": "finance_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_goal_id_goals_id_fk": { + "name": "finance_events_goal_id_goals_id_fk", + "tableFrom": "finance_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "finance_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "finance_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_cost_event_id_cost_events_id_fk": { + "name": "finance_events_cost_event_id_cost_events_id_fk", + "tableFrom": "finance_events", + "tableTo": "cost_events", + "columnsFrom": [ + "cost_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.goals": { + "name": "goals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'task'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'planned'" + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "goals_company_idx": { + "name": "goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "goals_company_id_companies_id_fk": { + "name": "goals_company_id_companies_id_fk", + "tableFrom": "goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_parent_id_goals_id_fk": { + "name": "goals_parent_id_goals_id_fk", + "tableFrom": "goals", + "tableTo": "goals", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_owner_agent_id_agents_id_fk": { + "name": "goals_owner_agent_id_agents_id_fk", + "tableFrom": "goals", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_run_events": { + "name": "heartbeat_run_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stream": { + "name": "stream", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_run_events_run_seq_idx": { + "name": "heartbeat_run_events_run_seq_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_run_idx": { + "name": "heartbeat_run_events_company_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_created_idx": { + "name": "heartbeat_run_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_run_events_company_id_companies_id_fk": { + "name": "heartbeat_run_events_company_id_companies_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_events_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_agent_id_agents_id_fk": { + "name": "heartbeat_run_events_agent_id_agents_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_runs": { + "name": "heartbeat_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invocation_source": { + "name": "invocation_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'on_demand'" + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wakeup_request_id": { + "name": "wakeup_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "signal": { + "name": "signal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "usage_json": { + "name": "usage_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_json": { + "name": "result_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_id_before": { + "name": "session_id_before", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id_after": { + "name": "session_id_after", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_run_id": { + "name": "external_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "process_pid": { + "name": "process_pid", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "process_started_at": { + "name": "process_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "retry_of_run_id": { + "name": "retry_of_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "process_loss_retry_count": { + "name": "process_loss_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "context_snapshot": { + "name": "context_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_runs_company_agent_started_idx": { + "name": "heartbeat_runs_company_agent_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_runs_company_id_companies_id_fk": { + "name": "heartbeat_runs_company_id_companies_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_agent_id_agents_id_fk": { + "name": "heartbeat_runs_agent_id_agents_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk": { + "name": "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agent_wakeup_requests", + "columnsFrom": [ + "wakeup_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "retry_of_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_settings": { + "name": "instance_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "singleton_key": { + "name": "singleton_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "general": { + "name": "general", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "experimental": { + "name": "experimental", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_settings_singleton_key_idx": { + "name": "instance_settings_singleton_key_idx", + "columns": [ + { + "expression": "singleton_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_user_roles": { + "name": "instance_user_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'instance_admin'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_user_roles_user_role_unique_idx": { + "name": "instance_user_roles_user_role_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "instance_user_roles_role_idx": { + "name": "instance_user_roles_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invites": { + "name": "invites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "invite_type": { + "name": "invite_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company_join'" + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allowed_join_types": { + "name": "allowed_join_types", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'both'" + }, + "defaults_payload": { + "name": "defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "invited_by_user_id": { + "name": "invited_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invites_token_hash_unique_idx": { + "name": "invites_token_hash_unique_idx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invites_company_invite_state_idx": { + "name": "invites_company_invite_state_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invite_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revoked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invites_company_id_companies_id_fk": { + "name": "invites_company_id_companies_id_fk", + "tableFrom": "invites", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_approvals": { + "name": "issue_approvals", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "linked_by_agent_id": { + "name": "linked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "linked_by_user_id": { + "name": "linked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_approvals_issue_idx": { + "name": "issue_approvals_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_approval_idx": { + "name": "issue_approvals_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_company_idx": { + "name": "issue_approvals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_approvals_company_id_companies_id_fk": { + "name": "issue_approvals_company_id_companies_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_approvals_issue_id_issues_id_fk": { + "name": "issue_approvals_issue_id_issues_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_approval_id_approvals_id_fk": { + "name": "issue_approvals_approval_id_approvals_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_linked_by_agent_id_agents_id_fk": { + "name": "issue_approvals_linked_by_agent_id_agents_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "agents", + "columnsFrom": [ + "linked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_approvals_pk": { + "name": "issue_approvals_pk", + "columns": [ + "issue_id", + "approval_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_attachments": { + "name": "issue_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_comment_id": { + "name": "issue_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_attachments_company_issue_idx": { + "name": "issue_attachments_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_issue_comment_idx": { + "name": "issue_attachments_issue_comment_idx", + "columns": [ + { + "expression": "issue_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_asset_uq": { + "name": "issue_attachments_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_attachments_company_id_companies_id_fk": { + "name": "issue_attachments_company_id_companies_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_attachments_issue_id_issues_id_fk": { + "name": "issue_attachments_issue_id_issues_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_asset_id_assets_id_fk": { + "name": "issue_attachments_asset_id_assets_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_issue_comment_id_issue_comments_id_fk": { + "name": "issue_attachments_issue_comment_id_issue_comments_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issue_comments", + "columnsFrom": [ + "issue_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_comments": { + "name": "issue_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_comments_issue_idx": { + "name": "issue_comments_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_idx": { + "name": "issue_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_issue_created_at_idx": { + "name": "issue_comments_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_author_issue_created_at_idx": { + "name": "issue_comments_company_author_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_body_search_idx": { + "name": "issue_comments_body_search_idx", + "columns": [ + { + "expression": "body", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "issue_comments_company_id_companies_id_fk": { + "name": "issue_comments_company_id_companies_id_fk", + "tableFrom": "issue_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_issue_id_issues_id_fk": { + "name": "issue_comments_issue_id_issues_id_fk", + "tableFrom": "issue_comments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_author_agent_id_agents_id_fk": { + "name": "issue_comments_author_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_comments_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_documents": { + "name": "issue_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_documents_company_issue_key_uq": { + "name": "issue_documents_company_issue_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_document_uq": { + "name": "issue_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_company_issue_updated_idx": { + "name": "issue_documents_company_issue_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_documents_company_id_companies_id_fk": { + "name": "issue_documents_company_id_companies_id_fk", + "tableFrom": "issue_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_documents_issue_id_issues_id_fk": { + "name": "issue_documents_issue_id_issues_id_fk", + "tableFrom": "issue_documents", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_documents_document_id_documents_id_fk": { + "name": "issue_documents_document_id_documents_id_fk", + "tableFrom": "issue_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_inbox_archives": { + "name": "issue_inbox_archives", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_inbox_archives_company_issue_idx": { + "name": "issue_inbox_archives_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_user_idx": { + "name": "issue_inbox_archives_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_issue_user_idx": { + "name": "issue_inbox_archives_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_inbox_archives_company_id_companies_id_fk": { + "name": "issue_inbox_archives_company_id_companies_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_inbox_archives_issue_id_issues_id_fk": { + "name": "issue_inbox_archives_issue_id_issues_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_labels": { + "name": "issue_labels", + "schema": "", + "columns": { + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label_id": { + "name": "label_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_labels_issue_idx": { + "name": "issue_labels_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_label_idx": { + "name": "issue_labels_label_idx", + "columns": [ + { + "expression": "label_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_company_idx": { + "name": "issue_labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_labels_issue_id_issues_id_fk": { + "name": "issue_labels_issue_id_issues_id_fk", + "tableFrom": "issue_labels", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_label_id_labels_id_fk": { + "name": "issue_labels_label_id_labels_id_fk", + "tableFrom": "issue_labels", + "tableTo": "labels", + "columnsFrom": [ + "label_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_company_id_companies_id_fk": { + "name": "issue_labels_company_id_companies_id_fk", + "tableFrom": "issue_labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_labels_pk": { + "name": "issue_labels_pk", + "columns": [ + "issue_id", + "label_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_read_states": { + "name": "issue_read_states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_read_at": { + "name": "last_read_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_read_states_company_issue_idx": { + "name": "issue_read_states_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_user_idx": { + "name": "issue_read_states_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_issue_user_idx": { + "name": "issue_read_states_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_read_states_company_id_companies_id_fk": { + "name": "issue_read_states_company_id_companies_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_read_states_issue_id_issues_id_fk": { + "name": "issue_read_states_issue_id_issues_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_relations": { + "name": "issue_relations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "related_issue_id": { + "name": "related_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_relations_company_issue_idx": { + "name": "issue_relations_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_related_issue_idx": { + "name": "issue_relations_company_related_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "related_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_type_idx": { + "name": "issue_relations_company_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_edge_uq": { + "name": "issue_relations_company_edge_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "related_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_relations_company_id_companies_id_fk": { + "name": "issue_relations_company_id_companies_id_fk", + "tableFrom": "issue_relations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_relations_issue_id_issues_id_fk": { + "name": "issue_relations_issue_id_issues_id_fk", + "tableFrom": "issue_relations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_relations_related_issue_id_issues_id_fk": { + "name": "issue_relations_related_issue_id_issues_id_fk", + "tableFrom": "issue_relations", + "tableTo": "issues", + "columnsFrom": [ + "related_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_relations_created_by_agent_id_agents_id_fk": { + "name": "issue_relations_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_relations", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_work_products": { + "name": "issue_work_products", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "runtime_service_id": { + "name": "runtime_service_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "review_state": { + "name": "review_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_work_products_company_issue_type_idx": { + "name": "issue_work_products_company_issue_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_execution_workspace_type_idx": { + "name": "issue_work_products_company_execution_workspace_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_provider_external_id_idx": { + "name": "issue_work_products_company_provider_external_id_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_updated_idx": { + "name": "issue_work_products_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_work_products_company_id_companies_id_fk": { + "name": "issue_work_products_company_id_companies_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_work_products_project_id_projects_id_fk": { + "name": "issue_work_products_project_id_projects_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_issue_id_issues_id_fk": { + "name": "issue_work_products_issue_id_issues_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_work_products_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issue_work_products_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk": { + "name": "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "workspace_runtime_services", + "columnsFrom": [ + "runtime_service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_work_products_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issues": { + "name": "issues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assignee_user_id": { + "name": "assignee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "checkout_run_id": { + "name": "checkout_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_run_id": { + "name": "execution_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_agent_name_key": { + "name": "execution_agent_name_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_locked_at": { + "name": "execution_locked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_number": { + "name": "issue_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_kind": { + "name": "origin_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "origin_id": { + "name": "origin_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_depth": { + "name": "request_depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_adapter_overrides": { + "name": "assignee_adapter_overrides", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_preference": { + "name": "execution_workspace_preference", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_settings": { + "name": "execution_workspace_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "hidden_at": { + "name": "hidden_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issues_company_status_idx": { + "name": "issues_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_status_idx": { + "name": "issues_company_assignee_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_user_status_idx": { + "name": "issues_company_assignee_user_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_parent_idx": { + "name": "issues_company_parent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_idx": { + "name": "issues_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_origin_idx": { + "name": "issues_company_origin_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_workspace_idx": { + "name": "issues_company_project_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_execution_workspace_idx": { + "name": "issues_company_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_identifier_idx": { + "name": "issues_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_title_search_idx": { + "name": "issues_title_search_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_identifier_search_idx": { + "name": "issues_identifier_search_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_description_search_idx": { + "name": "issues_description_search_idx", + "columns": [ + { + "expression": "description", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_open_routine_execution_uq": { + "name": "issues_open_routine_execution_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'routine_execution'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"execution_run_id\" is not null\n and \"issues\".\"status\" in ('backlog', 'todo', 'in_progress', 'in_review', 'blocked')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issues_company_id_companies_id_fk": { + "name": "issues_company_id_companies_id_fk", + "tableFrom": "issues", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_id_projects_id_fk": { + "name": "issues_project_id_projects_id_fk", + "tableFrom": "issues", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_workspace_id_project_workspaces_id_fk": { + "name": "issues_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_goal_id_goals_id_fk": { + "name": "issues_goal_id_goals_id_fk", + "tableFrom": "issues", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_parent_id_issues_id_fk": { + "name": "issues_parent_id_issues_id_fk", + "tableFrom": "issues", + "tableTo": "issues", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_assignee_agent_id_agents_id_fk": { + "name": "issues_assignee_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_checkout_run_id_heartbeat_runs_id_fk": { + "name": "issues_checkout_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "checkout_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_execution_run_id_heartbeat_runs_id_fk": { + "name": "issues_execution_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "execution_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_created_by_agent_id_agents_id_fk": { + "name": "issues_created_by_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issues_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.join_requests": { + "name": "join_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "invite_id": { + "name": "invite_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_type": { + "name": "request_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending_approval'" + }, + "request_ip": { + "name": "request_ip", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requesting_user_id": { + "name": "requesting_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_email_snapshot": { + "name": "request_email_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_name": { + "name": "agent_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_defaults_payload": { + "name": "agent_defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "claim_secret_hash": { + "name": "claim_secret_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_secret_expires_at": { + "name": "claim_secret_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claim_secret_consumed_at": { + "name": "claim_secret_consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_agent_id": { + "name": "created_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "rejected_by_user_id": { + "name": "rejected_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejected_at": { + "name": "rejected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "join_requests_invite_unique_idx": { + "name": "join_requests_invite_unique_idx", + "columns": [ + { + "expression": "invite_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_company_status_type_created_idx": { + "name": "join_requests_company_status_type_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "join_requests_invite_id_invites_id_fk": { + "name": "join_requests_invite_id_invites_id_fk", + "tableFrom": "join_requests", + "tableTo": "invites", + "columnsFrom": [ + "invite_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_company_id_companies_id_fk": { + "name": "join_requests_company_id_companies_id_fk", + "tableFrom": "join_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_created_agent_id_agents_id_fk": { + "name": "join_requests_created_agent_id_agents_id_fk", + "tableFrom": "join_requests", + "tableTo": "agents", + "columnsFrom": [ + "created_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.labels": { + "name": "labels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "labels_company_idx": { + "name": "labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "labels_company_name_idx": { + "name": "labels_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "labels_company_id_companies_id_fk": { + "name": "labels_company_id_companies_id_fk", + "tableFrom": "labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_company_settings": { + "name": "plugin_company_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "settings_json": { + "name": "settings_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_company_settings_company_idx": { + "name": "plugin_company_settings_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_plugin_idx": { + "name": "plugin_company_settings_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_company_plugin_uq": { + "name": "plugin_company_settings_company_plugin_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_company_settings_company_id_companies_id_fk": { + "name": "plugin_company_settings_company_id_companies_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_company_settings_plugin_id_plugins_id_fk": { + "name": "plugin_company_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_config": { + "name": "plugin_config", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "config_json": { + "name": "config_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_config_plugin_id_idx": { + "name": "plugin_config_plugin_id_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_config_plugin_id_plugins_id_fk": { + "name": "plugin_config_plugin_id_plugins_id_fk", + "tableFrom": "plugin_config", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_entities": { + "name": "plugin_entities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_entities_plugin_idx": { + "name": "plugin_entities_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_type_idx": { + "name": "plugin_entities_type_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_scope_idx": { + "name": "plugin_entities_scope_idx", + "columns": [ + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_external_idx": { + "name": "plugin_entities_external_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_entities_plugin_id_plugins_id_fk": { + "name": "plugin_entities_plugin_id_plugins_id_fk", + "tableFrom": "plugin_entities", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_job_runs": { + "name": "plugin_job_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "job_id": { + "name": "job_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logs": { + "name": "logs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_job_runs_job_idx": { + "name": "plugin_job_runs_job_idx", + "columns": [ + { + "expression": "job_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_plugin_idx": { + "name": "plugin_job_runs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_status_idx": { + "name": "plugin_job_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_job_runs_job_id_plugin_jobs_id_fk": { + "name": "plugin_job_runs_job_id_plugin_jobs_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugin_jobs", + "columnsFrom": [ + "job_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_job_runs_plugin_id_plugins_id_fk": { + "name": "plugin_job_runs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_jobs": { + "name": "plugin_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "job_key": { + "name": "job_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_jobs_plugin_idx": { + "name": "plugin_jobs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_next_run_idx": { + "name": "plugin_jobs_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_unique_idx": { + "name": "plugin_jobs_unique_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "job_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_jobs_plugin_id_plugins_id_fk": { + "name": "plugin_jobs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_jobs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_logs": { + "name": "plugin_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'info'" + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "meta": { + "name": "meta", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_logs_plugin_time_idx": { + "name": "plugin_logs_plugin_time_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_logs_level_idx": { + "name": "plugin_logs_level_idx", + "columns": [ + { + "expression": "level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_logs_plugin_id_plugins_id_fk": { + "name": "plugin_logs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_logs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_state": { + "name": "plugin_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "namespace": { + "name": "namespace", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "state_key": { + "name": "state_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value_json": { + "name": "value_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_state_plugin_scope_idx": { + "name": "plugin_state_plugin_scope_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_state_plugin_id_plugins_id_fk": { + "name": "plugin_state_plugin_id_plugins_id_fk", + "tableFrom": "plugin_state", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "plugin_state_unique_entry_idx": { + "name": "plugin_state_unique_entry_idx", + "nullsNotDistinct": true, + "columns": [ + "plugin_id", + "scope_kind", + "scope_id", + "namespace", + "state_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_webhook_deliveries": { + "name": "plugin_webhook_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "webhook_key": { + "name": "webhook_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "headers": { + "name": "headers", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_webhook_deliveries_plugin_idx": { + "name": "plugin_webhook_deliveries_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_status_idx": { + "name": "plugin_webhook_deliveries_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_key_idx": { + "name": "plugin_webhook_deliveries_key_idx", + "columns": [ + { + "expression": "webhook_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_webhook_deliveries_plugin_id_plugins_id_fk": { + "name": "plugin_webhook_deliveries_plugin_id_plugins_id_fk", + "tableFrom": "plugin_webhook_deliveries", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugins": { + "name": "plugins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "package_name": { + "name": "package_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "api_version": { + "name": "api_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "manifest_json": { + "name": "manifest_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'installed'" + }, + "install_order": { + "name": "install_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "package_path": { + "name": "package_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_at": { + "name": "installed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugins_plugin_key_idx": { + "name": "plugins_plugin_key_idx", + "columns": [ + { + "expression": "plugin_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugins_status_idx": { + "name": "plugins_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.principal_permission_grants": { + "name": "principal_permission_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_key": { + "name": "permission_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "granted_by_user_id": { + "name": "granted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "principal_permission_grants_unique_idx": { + "name": "principal_permission_grants_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "principal_permission_grants_company_permission_idx": { + "name": "principal_permission_grants_company_permission_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "principal_permission_grants_company_id_companies_id_fk": { + "name": "principal_permission_grants_company_id_companies_id_fk", + "tableFrom": "principal_permission_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_goals": { + "name": "project_goals", + "schema": "", + "columns": { + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_goals_project_idx": { + "name": "project_goals_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_goal_idx": { + "name": "project_goals_goal_idx", + "columns": [ + { + "expression": "goal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_company_idx": { + "name": "project_goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_goals_project_id_projects_id_fk": { + "name": "project_goals_project_id_projects_id_fk", + "tableFrom": "project_goals", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_goal_id_goals_id_fk": { + "name": "project_goals_goal_id_goals_id_fk", + "tableFrom": "project_goals", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_company_id_companies_id_fk": { + "name": "project_goals_company_id_companies_id_fk", + "tableFrom": "project_goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "project_goals_project_id_goal_id_pk": { + "name": "project_goals_project_id_goal_id_pk", + "columns": [ + "project_id", + "goal_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_workspaces": { + "name": "project_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_ref": { + "name": "repo_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_ref": { + "name": "default_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "setup_command": { + "name": "setup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cleanup_command": { + "name": "cleanup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_provider": { + "name": "remote_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_workspace_ref": { + "name": "remote_workspace_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_workspace_key": { + "name": "shared_workspace_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_workspaces_company_project_idx": { + "name": "project_workspaces_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_primary_idx": { + "name": "project_workspaces_project_primary_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_primary", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_source_type_idx": { + "name": "project_workspaces_project_source_type_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_company_shared_key_idx": { + "name": "project_workspaces_company_shared_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "shared_workspace_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_remote_ref_idx": { + "name": "project_workspaces_project_remote_ref_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_workspace_ref", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_workspaces_company_id_companies_id_fk": { + "name": "project_workspaces_company_id_companies_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "project_workspaces_project_id_projects_id_fk": { + "name": "project_workspaces_project_id_projects_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "lead_agent_id": { + "name": "lead_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_date": { + "name": "target_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_policy": { + "name": "execution_workspace_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "projects_company_idx": { + "name": "projects_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "projects_company_id_companies_id_fk": { + "name": "projects_company_id_companies_id_fk", + "tableFrom": "projects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_goal_id_goals_id_fk": { + "name": "projects_goal_id_goals_id_fk", + "tableFrom": "projects", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_lead_agent_id_agents_id_fk": { + "name": "projects_lead_agent_id_agents_id_fk", + "tableFrom": "projects", + "tableTo": "agents", + "columnsFrom": [ + "lead_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_runs": { + "name": "routine_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger_id": { + "name": "trigger_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "triggered_at": { + "name": "triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_payload": { + "name": "trigger_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "linked_issue_id": { + "name": "linked_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "coalesced_into_run_id": { + "name": "coalesced_into_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_runs_company_routine_idx": { + "name": "routine_runs_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idx": { + "name": "routine_runs_trigger_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_linked_issue_idx": { + "name": "routine_runs_linked_issue_idx", + "columns": [ + { + "expression": "linked_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idempotency_idx": { + "name": "routine_runs_trigger_idempotency_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_runs_company_id_companies_id_fk": { + "name": "routine_runs_company_id_companies_id_fk", + "tableFrom": "routine_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_routine_id_routines_id_fk": { + "name": "routine_runs_routine_id_routines_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_trigger_id_routine_triggers_id_fk": { + "name": "routine_runs_trigger_id_routine_triggers_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routine_triggers", + "columnsFrom": [ + "trigger_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_runs_linked_issue_id_issues_id_fk": { + "name": "routine_runs_linked_issue_id_issues_id_fk", + "tableFrom": "routine_runs", + "tableTo": "issues", + "columnsFrom": [ + "linked_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_triggers": { + "name": "routine_triggers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "public_id": { + "name": "public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "signing_mode": { + "name": "signing_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "replay_window_sec": { + "name": "replay_window_sec", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_rotated_at": { + "name": "last_rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_result": { + "name": "last_result", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_triggers_company_routine_idx": { + "name": "routine_triggers_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_company_kind_idx": { + "name": "routine_triggers_company_kind_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_next_run_idx": { + "name": "routine_triggers_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_idx": { + "name": "routine_triggers_public_id_idx", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_uq": { + "name": "routine_triggers_public_id_uq", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_triggers_company_id_companies_id_fk": { + "name": "routine_triggers_company_id_companies_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_routine_id_routines_id_fk": { + "name": "routine_triggers_routine_id_routines_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_secret_id_company_secrets_id_fk": { + "name": "routine_triggers_secret_id_company_secrets_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_created_by_agent_id_agents_id_fk": { + "name": "routine_triggers_created_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_updated_by_agent_id_agents_id_fk": { + "name": "routine_triggers_updated_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routines": { + "name": "routines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_issue_id": { + "name": "parent_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "concurrency_policy": { + "name": "concurrency_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'coalesce_if_active'" + }, + "catch_up_policy": { + "name": "catch_up_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'skip_missed'" + }, + "variables": { + "name": "variables", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_triggered_at": { + "name": "last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_enqueued_at": { + "name": "last_enqueued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routines_company_status_idx": { + "name": "routines_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_assignee_idx": { + "name": "routines_company_assignee_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_project_idx": { + "name": "routines_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routines_company_id_companies_id_fk": { + "name": "routines_company_id_companies_id_fk", + "tableFrom": "routines", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_project_id_projects_id_fk": { + "name": "routines_project_id_projects_id_fk", + "tableFrom": "routines", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_goal_id_goals_id_fk": { + "name": "routines_goal_id_goals_id_fk", + "tableFrom": "routines", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_parent_issue_id_issues_id_fk": { + "name": "routines_parent_issue_id_issues_id_fk", + "tableFrom": "routines", + "tableTo": "issues", + "columnsFrom": [ + "parent_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_assignee_agent_id_agents_id_fk": { + "name": "routines_assignee_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "routines_created_by_agent_id_agents_id_fk": { + "name": "routines_created_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_updated_by_agent_id_agents_id_fk": { + "name": "routines_updated_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_operations": { + "name": "workspace_operations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_operations_company_run_started_idx": { + "name": "workspace_operations_company_run_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_operations_company_workspace_started_idx": { + "name": "workspace_operations_company_workspace_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_operations_company_id_companies_id_fk": { + "name": "workspace_operations_company_id_companies_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_operations_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_operations_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_runtime_services": { + "name": "workspace_runtime_services", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "service_name": { + "name": "service_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reuse_key": { + "name": "reuse_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "started_by_run_id": { + "name": "started_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stop_policy": { + "name": "stop_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_runtime_services_company_workspace_status_idx": { + "name": "workspace_runtime_services_company_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_execution_workspace_status_idx": { + "name": "workspace_runtime_services_company_execution_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_project_status_idx": { + "name": "workspace_runtime_services_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_run_idx": { + "name": "workspace_runtime_services_run_idx", + "columns": [ + { + "expression": "started_by_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_updated_idx": { + "name": "workspace_runtime_services_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_runtime_services_company_id_companies_id_fk": { + "name": "workspace_runtime_services_company_id_companies_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_id_projects_id_fk": { + "name": "workspace_runtime_services_project_id_projects_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk": { + "name": "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_issue_id_issues_id_fk": { + "name": "workspace_runtime_services_issue_id_issues_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_owner_agent_id_agents_id_fk": { + "name": "workspace_runtime_services_owner_agent_id_agents_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk": { + "name": "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "started_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/src/migrations/meta/0052_snapshot.json b/packages/db/src/migrations/meta/0052_snapshot.json new file mode 100644 index 00000000000..7df4fda104f --- /dev/null +++ b/packages/db/src/migrations/meta/0052_snapshot.json @@ -0,0 +1,13057 @@ +{ + "id": "90165bd7-c2f6-45a5-83ea-ba357b060428", + "prevId": "fb74b316-b3b6-43f5-a6d6-a7d310064a68", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.activity_log": { + "name": "activity_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "activity_log_company_created_idx": { + "name": "activity_log_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_run_id_idx": { + "name": "activity_log_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_entity_type_id_idx": { + "name": "activity_log_entity_type_id_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "activity_log_company_id_companies_id_fk": { + "name": "activity_log_company_id_companies_id_fk", + "tableFrom": "activity_log", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_agent_id_agents_id_fk": { + "name": "activity_log_agent_id_agents_id_fk", + "tableFrom": "activity_log", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_run_id_heartbeat_runs_id_fk": { + "name": "activity_log_run_id_heartbeat_runs_id_fk", + "tableFrom": "activity_log", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_api_keys": { + "name": "agent_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_api_keys_key_hash_idx": { + "name": "agent_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_api_keys_company_agent_idx": { + "name": "agent_api_keys_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_api_keys_agent_id_agents_id_fk": { + "name": "agent_api_keys_agent_id_agents_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_api_keys_company_id_companies_id_fk": { + "name": "agent_api_keys_company_id_companies_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_config_revisions": { + "name": "agent_config_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'patch'" + }, + "rolled_back_from_revision_id": { + "name": "rolled_back_from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "changed_keys": { + "name": "changed_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "before_config": { + "name": "before_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "after_config": { + "name": "after_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_config_revisions_company_agent_created_idx": { + "name": "agent_config_revisions_company_agent_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_config_revisions_agent_created_idx": { + "name": "agent_config_revisions_agent_created_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_config_revisions_company_id_companies_id_fk": { + "name": "agent_config_revisions_company_id_companies_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_config_revisions_agent_id_agents_id_fk": { + "name": "agent_config_revisions_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_config_revisions_created_by_agent_id_agents_id_fk": { + "name": "agent_config_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_runtime_state": { + "name": "agent_runtime_state", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_json": { + "name": "state_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_run_status": { + "name": "last_run_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total_input_tokens": { + "name": "total_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_output_tokens": { + "name": "total_output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cached_input_tokens": { + "name": "total_cached_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost_cents": { + "name": "total_cost_cents", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_runtime_state_company_agent_idx": { + "name": "agent_runtime_state_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_runtime_state_company_updated_idx": { + "name": "agent_runtime_state_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_runtime_state_agent_id_agents_id_fk": { + "name": "agent_runtime_state_agent_id_agents_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_runtime_state_company_id_companies_id_fk": { + "name": "agent_runtime_state_company_id_companies_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_task_sessions": { + "name": "agent_task_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "task_key": { + "name": "task_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_params_json": { + "name": "session_params_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_display_id": { + "name": "session_display_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_task_sessions_company_agent_adapter_task_uniq": { + "name": "agent_task_sessions_company_agent_adapter_task_uniq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "adapter_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_agent_updated_idx": { + "name": "agent_task_sessions_company_agent_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_task_updated_idx": { + "name": "agent_task_sessions_company_task_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_task_sessions_company_id_companies_id_fk": { + "name": "agent_task_sessions_company_id_companies_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_agent_id_agents_id_fk": { + "name": "agent_task_sessions_agent_id_agents_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_last_run_id_heartbeat_runs_id_fk": { + "name": "agent_task_sessions_last_run_id_heartbeat_runs_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "last_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_wakeup_requests": { + "name": "agent_wakeup_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "coalesced_count": { + "name": "coalesced_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "requested_by_actor_type": { + "name": "requested_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by_actor_id": { + "name": "requested_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_wakeup_requests_company_agent_status_idx": { + "name": "agent_wakeup_requests_company_agent_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_company_requested_idx": { + "name": "agent_wakeup_requests_company_requested_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_agent_requested_idx": { + "name": "agent_wakeup_requests_agent_requested_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_wakeup_requests_company_id_companies_id_fk": { + "name": "agent_wakeup_requests_company_id_companies_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_wakeup_requests_agent_id_agents_id_fk": { + "name": "agent_wakeup_requests_agent_id_agents_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "reports_to": { + "name": "reports_to", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'process'" + }, + "adapter_config": { + "name": "adapter_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "runtime_config": { + "name": "runtime_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_heartbeat_at": { + "name": "last_heartbeat_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agents_company_status_idx": { + "name": "agents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_company_reports_to_idx": { + "name": "agents_company_reports_to_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reports_to", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agents_company_id_companies_id_fk": { + "name": "agents_company_id_companies_id_fk", + "tableFrom": "agents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agents_reports_to_agents_id_fk": { + "name": "agents_reports_to_agents_id_fk", + "tableFrom": "agents", + "tableTo": "agents", + "columnsFrom": [ + "reports_to" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approval_comments": { + "name": "approval_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approval_comments_company_idx": { + "name": "approval_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_idx": { + "name": "approval_comments_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_created_idx": { + "name": "approval_comments_approval_created_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approval_comments_company_id_companies_id_fk": { + "name": "approval_comments_company_id_companies_id_fk", + "tableFrom": "approval_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_approval_id_approvals_id_fk": { + "name": "approval_comments_approval_id_approvals_id_fk", + "tableFrom": "approval_comments", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_author_agent_id_agents_id_fk": { + "name": "approval_comments_author_agent_id_agents_id_fk", + "tableFrom": "approval_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approvals": { + "name": "approvals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_by_agent_id": { + "name": "requested_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_by_user_id": { + "name": "requested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "decision_note": { + "name": "decision_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approvals_company_status_type_idx": { + "name": "approvals_company_status_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approvals_company_id_companies_id_fk": { + "name": "approvals_company_id_companies_id_fk", + "tableFrom": "approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approvals_requested_by_agent_id_agents_id_fk": { + "name": "approvals_requested_by_agent_id_agents_id_fk", + "tableFrom": "approvals", + "tableTo": "agents", + "columnsFrom": [ + "requested_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.assets": { + "name": "assets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "object_key": { + "name": "object_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "original_filename": { + "name": "original_filename", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "assets_company_created_idx": { + "name": "assets_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_provider_idx": { + "name": "assets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_object_key_uq": { + "name": "assets_company_object_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "assets_company_id_companies_id_fk": { + "name": "assets_company_id_companies_id_fk", + "tableFrom": "assets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "assets_created_by_agent_id_agents_id_fk": { + "name": "assets_created_by_agent_id_agents_id_fk", + "tableFrom": "assets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.board_api_keys": { + "name": "board_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "board_api_keys_key_hash_idx": { + "name": "board_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "board_api_keys_user_idx": { + "name": "board_api_keys_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "board_api_keys_user_id_user_id_fk": { + "name": "board_api_keys_user_id_user_id_fk", + "tableFrom": "board_api_keys", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_incidents": { + "name": "budget_incidents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "policy_id": { + "name": "policy_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_end": { + "name": "window_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "threshold_type": { + "name": "threshold_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_limit": { + "name": "amount_limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "amount_observed": { + "name": "amount_observed", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_incidents_company_status_idx": { + "name": "budget_incidents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_company_scope_idx": { + "name": "budget_incidents_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_policy_window_threshold_idx": { + "name": "budget_incidents_policy_window_threshold_idx", + "columns": [ + { + "expression": "policy_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "threshold_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"budget_incidents\".\"status\" <> 'dismissed'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_incidents_company_id_companies_id_fk": { + "name": "budget_incidents_company_id_companies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_policy_id_budget_policies_id_fk": { + "name": "budget_incidents_policy_id_budget_policies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "budget_policies", + "columnsFrom": [ + "policy_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_approval_id_approvals_id_fk": { + "name": "budget_incidents_approval_id_approvals_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_policies": { + "name": "budget_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'billed_cents'" + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "warn_percent": { + "name": "warn_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 80 + }, + "hard_stop_enabled": { + "name": "hard_stop_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "notify_enabled": { + "name": "notify_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_policies_company_scope_active_idx": { + "name": "budget_policies_company_scope_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_window_idx": { + "name": "budget_policies_company_window_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_scope_metric_unique_idx": { + "name": "budget_policies_company_scope_metric_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_policies_company_id_companies_id_fk": { + "name": "budget_policies_company_id_companies_id_fk", + "tableFrom": "budget_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cli_auth_challenges": { + "name": "cli_auth_challenges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_hash": { + "name": "secret_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_access": { + "name": "requested_access", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'board'" + }, + "requested_company_id": { + "name": "requested_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "pending_key_hash": { + "name": "pending_key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pending_key_name": { + "name": "pending_key_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "board_api_key_id": { + "name": "board_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cli_auth_challenges_secret_hash_idx": { + "name": "cli_auth_challenges_secret_hash_idx", + "columns": [ + { + "expression": "secret_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_approved_by_idx": { + "name": "cli_auth_challenges_approved_by_idx", + "columns": [ + { + "expression": "approved_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_requested_company_idx": { + "name": "cli_auth_challenges_requested_company_idx", + "columns": [ + { + "expression": "requested_company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cli_auth_challenges_requested_company_id_companies_id_fk": { + "name": "cli_auth_challenges_requested_company_id_companies_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "companies", + "columnsFrom": [ + "requested_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_approved_by_user_id_user_id_fk": { + "name": "cli_auth_challenges_approved_by_user_id_user_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "user", + "columnsFrom": [ + "approved_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk": { + "name": "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "board_api_keys", + "columnsFrom": [ + "board_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.companies": { + "name": "companies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "issue_prefix": { + "name": "issue_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'PAP'" + }, + "issue_counter": { + "name": "issue_counter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "require_board_approval_for_new_agents": { + "name": "require_board_approval_for_new_agents", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "feedback_data_sharing_enabled": { + "name": "feedback_data_sharing_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "feedback_data_sharing_consent_at": { + "name": "feedback_data_sharing_consent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "feedback_data_sharing_consent_by_user_id": { + "name": "feedback_data_sharing_consent_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feedback_data_sharing_terms_version": { + "name": "feedback_data_sharing_terms_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "brand_color": { + "name": "brand_color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "companies_issue_prefix_idx": { + "name": "companies_issue_prefix_idx", + "columns": [ + { + "expression": "issue_prefix", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_logos": { + "name": "company_logos", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_logos_company_uq": { + "name": "company_logos_company_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_logos_asset_uq": { + "name": "company_logos_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_logos_company_id_companies_id_fk": { + "name": "company_logos_company_id_companies_id_fk", + "tableFrom": "company_logos", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_logos_asset_id_assets_id_fk": { + "name": "company_logos_asset_id_assets_id_fk", + "tableFrom": "company_logos", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_memberships": { + "name": "company_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "membership_role": { + "name": "membership_role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_memberships_company_principal_unique_idx": { + "name": "company_memberships_company_principal_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_principal_status_idx": { + "name": "company_memberships_principal_status_idx", + "columns": [ + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_company_status_idx": { + "name": "company_memberships_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_memberships_company_id_companies_id_fk": { + "name": "company_memberships_company_id_companies_id_fk", + "tableFrom": "company_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_versions": { + "name": "company_secret_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "material": { + "name": "material", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "value_sha256": { + "name": "value_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "company_secret_versions_secret_idx": { + "name": "company_secret_versions_secret_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_value_sha256_idx": { + "name": "company_secret_versions_value_sha256_idx", + "columns": [ + { + "expression": "value_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_secret_version_uq": { + "name": "company_secret_versions_secret_version_uq", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_versions_secret_id_company_secrets_id_fk": { + "name": "company_secret_versions_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_versions_created_by_agent_id_agents_id_fk": { + "name": "company_secret_versions_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secrets": { + "name": "company_secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_encrypted'" + }, + "external_ref": { + "name": "external_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "latest_version": { + "name": "latest_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secrets_company_idx": { + "name": "company_secrets_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_provider_idx": { + "name": "company_secrets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_name_uq": { + "name": "company_secrets_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secrets_company_id_companies_id_fk": { + "name": "company_secrets_company_id_companies_id_fk", + "tableFrom": "company_secrets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secrets_created_by_agent_id_agents_id_fk": { + "name": "company_secrets_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secrets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skills": { + "name": "company_skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "markdown": { + "name": "markdown", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "source_locator": { + "name": "source_locator", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_ref": { + "name": "source_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trust_level": { + "name": "trust_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown_only'" + }, + "compatibility": { + "name": "compatibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'compatible'" + }, + "file_inventory": { + "name": "file_inventory", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skills_company_key_idx": { + "name": "company_skills_company_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_name_idx": { + "name": "company_skills_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skills_company_id_companies_id_fk": { + "name": "company_skills_company_id_companies_id_fk", + "tableFrom": "company_skills", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cost_events": { + "name": "cost_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "billing_type": { + "name": "billing_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cached_input_tokens": { + "name": "cached_input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cost_cents": { + "name": "cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cost_events_company_occurred_idx": { + "name": "cost_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_agent_occurred_idx": { + "name": "cost_events_company_agent_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_provider_occurred_idx": { + "name": "cost_events_company_provider_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_biller_occurred_idx": { + "name": "cost_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_heartbeat_run_idx": { + "name": "cost_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cost_events_company_id_companies_id_fk": { + "name": "cost_events_company_id_companies_id_fk", + "tableFrom": "cost_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_agent_id_agents_id_fk": { + "name": "cost_events_agent_id_agents_id_fk", + "tableFrom": "cost_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_issue_id_issues_id_fk": { + "name": "cost_events_issue_id_issues_id_fk", + "tableFrom": "cost_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_project_id_projects_id_fk": { + "name": "cost_events_project_id_projects_id_fk", + "tableFrom": "cost_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_goal_id_goals_id_fk": { + "name": "cost_events_goal_id_goals_id_fk", + "tableFrom": "cost_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "cost_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "cost_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_revisions": { + "name": "document_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_revisions_document_revision_uq": { + "name": "document_revisions_document_revision_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_revisions_company_document_created_idx": { + "name": "document_revisions_company_document_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_revisions_company_id_companies_id_fk": { + "name": "document_revisions_company_id_companies_id_fk", + "tableFrom": "document_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_revisions_document_id_documents_id_fk": { + "name": "document_revisions_document_id_documents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_revisions_created_by_agent_id_agents_id_fk": { + "name": "document_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_revisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "document_revisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "document_revisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.documents": { + "name": "documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "latest_body": { + "name": "latest_body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "latest_revision_id": { + "name": "latest_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "latest_revision_number": { + "name": "latest_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "documents_company_updated_idx": { + "name": "documents_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_company_created_idx": { + "name": "documents_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "documents_company_id_companies_id_fk": { + "name": "documents_company_id_companies_id_fk", + "tableFrom": "documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "documents_created_by_agent_id_agents_id_fk": { + "name": "documents_created_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "documents_updated_by_agent_id_agents_id_fk": { + "name": "documents_updated_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_workspaces": { + "name": "execution_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "strategy_type": { + "name": "strategy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_ref": { + "name": "base_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch_name": { + "name": "branch_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_fs'" + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "derived_from_execution_workspace_id": { + "name": "derived_from_execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "opened_at": { + "name": "opened_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_eligible_at": { + "name": "cleanup_eligible_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_reason": { + "name": "cleanup_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_workspaces_company_project_status_idx": { + "name": "execution_workspaces_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_project_workspace_status_idx": { + "name": "execution_workspaces_company_project_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_source_issue_idx": { + "name": "execution_workspaces_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_last_used_idx": { + "name": "execution_workspaces_company_last_used_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_branch_idx": { + "name": "execution_workspaces_company_branch_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspaces_company_id_companies_id_fk": { + "name": "execution_workspaces_company_id_companies_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "execution_workspaces_project_id_projects_id_fk": { + "name": "execution_workspaces_project_id_projects_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspaces_project_workspace_id_project_workspaces_id_fk": { + "name": "execution_workspaces_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_source_issue_id_issues_id_fk": { + "name": "execution_workspaces_source_issue_id_issues_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk": { + "name": "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "derived_from_execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feedback_exports": { + "name": "feedback_exports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "feedback_vote_id": { + "name": "feedback_vote_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_only'" + }, + "destination": { + "name": "destination", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "export_id": { + "name": "export_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consent_version": { + "name": "consent_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "schema_version": { + "name": "schema_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-envelope-v2'" + }, + "bundle_version": { + "name": "bundle_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-bundle-v2'" + }, + "payload_version": { + "name": "payload_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-v1'" + }, + "payload_digest": { + "name": "payload_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload_snapshot": { + "name": "payload_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "target_summary": { + "name": "target_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "redaction_summary": { + "name": "redaction_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempted_at": { + "name": "last_attempted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "exported_at": { + "name": "exported_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_exports_feedback_vote_idx": { + "name": "feedback_exports_feedback_vote_idx", + "columns": [ + { + "expression": "feedback_vote_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_created_idx": { + "name": "feedback_exports_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_status_idx": { + "name": "feedback_exports_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_issue_idx": { + "name": "feedback_exports_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_project_idx": { + "name": "feedback_exports_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_author_idx": { + "name": "feedback_exports_company_author_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_exports_company_id_companies_id_fk": { + "name": "feedback_exports_company_id_companies_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "feedback_exports_feedback_vote_id_feedback_votes_id_fk": { + "name": "feedback_exports_feedback_vote_id_feedback_votes_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "feedback_votes", + "columnsFrom": [ + "feedback_vote_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_exports_issue_id_issues_id_fk": { + "name": "feedback_exports_issue_id_issues_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_exports_project_id_projects_id_fk": { + "name": "feedback_exports_project_id_projects_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feedback_votes": { + "name": "feedback_votes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_with_labs": { + "name": "shared_with_labs", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "shared_at": { + "name": "shared_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "consent_version": { + "name": "consent_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redaction_summary": { + "name": "redaction_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_votes_company_issue_idx": { + "name": "feedback_votes_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_issue_target_idx": { + "name": "feedback_votes_issue_target_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_author_idx": { + "name": "feedback_votes_author_idx", + "columns": [ + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_company_target_author_idx": { + "name": "feedback_votes_company_target_author_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_votes_company_id_companies_id_fk": { + "name": "feedback_votes_company_id_companies_id_fk", + "tableFrom": "feedback_votes", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "feedback_votes_issue_id_issues_id_fk": { + "name": "feedback_votes_issue_id_issues_id_fk", + "tableFrom": "feedback_votes", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.finance_events": { + "name": "finance_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cost_event_id": { + "name": "cost_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "event_kind": { + "name": "event_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'debit'" + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_adapter_type": { + "name": "execution_adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pricing_tier": { + "name": "pricing_tier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "estimated": { + "name": "estimated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "external_invoice_id": { + "name": "external_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata_json": { + "name": "metadata_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "finance_events_company_occurred_idx": { + "name": "finance_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_biller_occurred_idx": { + "name": "finance_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_kind_occurred_idx": { + "name": "finance_events_company_kind_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_direction_occurred_idx": { + "name": "finance_events_company_direction_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "direction", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_heartbeat_run_idx": { + "name": "finance_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_cost_event_idx": { + "name": "finance_events_company_cost_event_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "finance_events_company_id_companies_id_fk": { + "name": "finance_events_company_id_companies_id_fk", + "tableFrom": "finance_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_agent_id_agents_id_fk": { + "name": "finance_events_agent_id_agents_id_fk", + "tableFrom": "finance_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_issue_id_issues_id_fk": { + "name": "finance_events_issue_id_issues_id_fk", + "tableFrom": "finance_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_project_id_projects_id_fk": { + "name": "finance_events_project_id_projects_id_fk", + "tableFrom": "finance_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_goal_id_goals_id_fk": { + "name": "finance_events_goal_id_goals_id_fk", + "tableFrom": "finance_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "finance_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "finance_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_cost_event_id_cost_events_id_fk": { + "name": "finance_events_cost_event_id_cost_events_id_fk", + "tableFrom": "finance_events", + "tableTo": "cost_events", + "columnsFrom": [ + "cost_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.goals": { + "name": "goals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'task'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'planned'" + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "goals_company_idx": { + "name": "goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "goals_company_id_companies_id_fk": { + "name": "goals_company_id_companies_id_fk", + "tableFrom": "goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_parent_id_goals_id_fk": { + "name": "goals_parent_id_goals_id_fk", + "tableFrom": "goals", + "tableTo": "goals", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_owner_agent_id_agents_id_fk": { + "name": "goals_owner_agent_id_agents_id_fk", + "tableFrom": "goals", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_run_events": { + "name": "heartbeat_run_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stream": { + "name": "stream", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_run_events_run_seq_idx": { + "name": "heartbeat_run_events_run_seq_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_run_idx": { + "name": "heartbeat_run_events_company_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_created_idx": { + "name": "heartbeat_run_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_run_events_company_id_companies_id_fk": { + "name": "heartbeat_run_events_company_id_companies_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_events_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_agent_id_agents_id_fk": { + "name": "heartbeat_run_events_agent_id_agents_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_runs": { + "name": "heartbeat_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invocation_source": { + "name": "invocation_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'on_demand'" + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wakeup_request_id": { + "name": "wakeup_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "signal": { + "name": "signal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "usage_json": { + "name": "usage_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_json": { + "name": "result_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_id_before": { + "name": "session_id_before", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id_after": { + "name": "session_id_after", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_run_id": { + "name": "external_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "process_pid": { + "name": "process_pid", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "process_started_at": { + "name": "process_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "retry_of_run_id": { + "name": "retry_of_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "process_loss_retry_count": { + "name": "process_loss_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "issue_comment_status": { + "name": "issue_comment_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'not_applicable'" + }, + "issue_comment_satisfied_by_comment_id": { + "name": "issue_comment_satisfied_by_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_comment_retry_queued_at": { + "name": "issue_comment_retry_queued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "context_snapshot": { + "name": "context_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_runs_company_agent_started_idx": { + "name": "heartbeat_runs_company_agent_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_runs_company_id_companies_id_fk": { + "name": "heartbeat_runs_company_id_companies_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_agent_id_agents_id_fk": { + "name": "heartbeat_runs_agent_id_agents_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk": { + "name": "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agent_wakeup_requests", + "columnsFrom": [ + "wakeup_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "retry_of_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_settings": { + "name": "instance_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "singleton_key": { + "name": "singleton_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "general": { + "name": "general", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "experimental": { + "name": "experimental", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_settings_singleton_key_idx": { + "name": "instance_settings_singleton_key_idx", + "columns": [ + { + "expression": "singleton_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_user_roles": { + "name": "instance_user_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'instance_admin'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_user_roles_user_role_unique_idx": { + "name": "instance_user_roles_user_role_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "instance_user_roles_role_idx": { + "name": "instance_user_roles_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invites": { + "name": "invites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "invite_type": { + "name": "invite_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company_join'" + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allowed_join_types": { + "name": "allowed_join_types", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'both'" + }, + "defaults_payload": { + "name": "defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "invited_by_user_id": { + "name": "invited_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invites_token_hash_unique_idx": { + "name": "invites_token_hash_unique_idx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invites_company_invite_state_idx": { + "name": "invites_company_invite_state_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invite_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revoked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invites_company_id_companies_id_fk": { + "name": "invites_company_id_companies_id_fk", + "tableFrom": "invites", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_approvals": { + "name": "issue_approvals", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "linked_by_agent_id": { + "name": "linked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "linked_by_user_id": { + "name": "linked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_approvals_issue_idx": { + "name": "issue_approvals_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_approval_idx": { + "name": "issue_approvals_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_company_idx": { + "name": "issue_approvals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_approvals_company_id_companies_id_fk": { + "name": "issue_approvals_company_id_companies_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_approvals_issue_id_issues_id_fk": { + "name": "issue_approvals_issue_id_issues_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_approval_id_approvals_id_fk": { + "name": "issue_approvals_approval_id_approvals_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_linked_by_agent_id_agents_id_fk": { + "name": "issue_approvals_linked_by_agent_id_agents_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "agents", + "columnsFrom": [ + "linked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_approvals_pk": { + "name": "issue_approvals_pk", + "columns": [ + "issue_id", + "approval_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_attachments": { + "name": "issue_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_comment_id": { + "name": "issue_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_attachments_company_issue_idx": { + "name": "issue_attachments_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_issue_comment_idx": { + "name": "issue_attachments_issue_comment_idx", + "columns": [ + { + "expression": "issue_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_asset_uq": { + "name": "issue_attachments_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_attachments_company_id_companies_id_fk": { + "name": "issue_attachments_company_id_companies_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_attachments_issue_id_issues_id_fk": { + "name": "issue_attachments_issue_id_issues_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_asset_id_assets_id_fk": { + "name": "issue_attachments_asset_id_assets_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_issue_comment_id_issue_comments_id_fk": { + "name": "issue_attachments_issue_comment_id_issue_comments_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issue_comments", + "columnsFrom": [ + "issue_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_comments": { + "name": "issue_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_comments_issue_idx": { + "name": "issue_comments_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_idx": { + "name": "issue_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_issue_created_at_idx": { + "name": "issue_comments_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_author_issue_created_at_idx": { + "name": "issue_comments_company_author_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_body_search_idx": { + "name": "issue_comments_body_search_idx", + "columns": [ + { + "expression": "body", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "issue_comments_company_id_companies_id_fk": { + "name": "issue_comments_company_id_companies_id_fk", + "tableFrom": "issue_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_issue_id_issues_id_fk": { + "name": "issue_comments_issue_id_issues_id_fk", + "tableFrom": "issue_comments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_author_agent_id_agents_id_fk": { + "name": "issue_comments_author_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_comments_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_documents": { + "name": "issue_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_documents_company_issue_key_uq": { + "name": "issue_documents_company_issue_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_document_uq": { + "name": "issue_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_company_issue_updated_idx": { + "name": "issue_documents_company_issue_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_documents_company_id_companies_id_fk": { + "name": "issue_documents_company_id_companies_id_fk", + "tableFrom": "issue_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_documents_issue_id_issues_id_fk": { + "name": "issue_documents_issue_id_issues_id_fk", + "tableFrom": "issue_documents", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_documents_document_id_documents_id_fk": { + "name": "issue_documents_document_id_documents_id_fk", + "tableFrom": "issue_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_execution_decisions": { + "name": "issue_execution_decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_id": { + "name": "stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_type": { + "name": "stage_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_execution_decisions_company_issue_idx": { + "name": "issue_execution_decisions_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_execution_decisions_stage_idx": { + "name": "issue_execution_decisions_stage_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_execution_decisions_company_id_companies_id_fk": { + "name": "issue_execution_decisions_company_id_companies_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_execution_decisions_issue_id_issues_id_fk": { + "name": "issue_execution_decisions_issue_id_issues_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_execution_decisions_actor_agent_id_agents_id_fk": { + "name": "issue_execution_decisions_actor_agent_id_agents_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "agents", + "columnsFrom": [ + "actor_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_execution_decisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_execution_decisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_inbox_archives": { + "name": "issue_inbox_archives", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_inbox_archives_company_issue_idx": { + "name": "issue_inbox_archives_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_user_idx": { + "name": "issue_inbox_archives_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_issue_user_idx": { + "name": "issue_inbox_archives_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_inbox_archives_company_id_companies_id_fk": { + "name": "issue_inbox_archives_company_id_companies_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_inbox_archives_issue_id_issues_id_fk": { + "name": "issue_inbox_archives_issue_id_issues_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_labels": { + "name": "issue_labels", + "schema": "", + "columns": { + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label_id": { + "name": "label_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_labels_issue_idx": { + "name": "issue_labels_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_label_idx": { + "name": "issue_labels_label_idx", + "columns": [ + { + "expression": "label_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_company_idx": { + "name": "issue_labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_labels_issue_id_issues_id_fk": { + "name": "issue_labels_issue_id_issues_id_fk", + "tableFrom": "issue_labels", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_label_id_labels_id_fk": { + "name": "issue_labels_label_id_labels_id_fk", + "tableFrom": "issue_labels", + "tableTo": "labels", + "columnsFrom": [ + "label_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_company_id_companies_id_fk": { + "name": "issue_labels_company_id_companies_id_fk", + "tableFrom": "issue_labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_labels_pk": { + "name": "issue_labels_pk", + "columns": [ + "issue_id", + "label_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_read_states": { + "name": "issue_read_states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_read_at": { + "name": "last_read_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_read_states_company_issue_idx": { + "name": "issue_read_states_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_user_idx": { + "name": "issue_read_states_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_issue_user_idx": { + "name": "issue_read_states_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_read_states_company_id_companies_id_fk": { + "name": "issue_read_states_company_id_companies_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_read_states_issue_id_issues_id_fk": { + "name": "issue_read_states_issue_id_issues_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_relations": { + "name": "issue_relations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "related_issue_id": { + "name": "related_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_relations_company_issue_idx": { + "name": "issue_relations_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_related_issue_idx": { + "name": "issue_relations_company_related_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "related_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_type_idx": { + "name": "issue_relations_company_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_edge_uq": { + "name": "issue_relations_company_edge_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "related_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_relations_company_id_companies_id_fk": { + "name": "issue_relations_company_id_companies_id_fk", + "tableFrom": "issue_relations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_relations_issue_id_issues_id_fk": { + "name": "issue_relations_issue_id_issues_id_fk", + "tableFrom": "issue_relations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_relations_related_issue_id_issues_id_fk": { + "name": "issue_relations_related_issue_id_issues_id_fk", + "tableFrom": "issue_relations", + "tableTo": "issues", + "columnsFrom": [ + "related_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_relations_created_by_agent_id_agents_id_fk": { + "name": "issue_relations_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_relations", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_work_products": { + "name": "issue_work_products", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "runtime_service_id": { + "name": "runtime_service_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "review_state": { + "name": "review_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_work_products_company_issue_type_idx": { + "name": "issue_work_products_company_issue_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_execution_workspace_type_idx": { + "name": "issue_work_products_company_execution_workspace_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_provider_external_id_idx": { + "name": "issue_work_products_company_provider_external_id_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_updated_idx": { + "name": "issue_work_products_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_work_products_company_id_companies_id_fk": { + "name": "issue_work_products_company_id_companies_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_work_products_project_id_projects_id_fk": { + "name": "issue_work_products_project_id_projects_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_issue_id_issues_id_fk": { + "name": "issue_work_products_issue_id_issues_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_work_products_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issue_work_products_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk": { + "name": "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "workspace_runtime_services", + "columnsFrom": [ + "runtime_service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_work_products_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issues": { + "name": "issues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assignee_user_id": { + "name": "assignee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "checkout_run_id": { + "name": "checkout_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_run_id": { + "name": "execution_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_agent_name_key": { + "name": "execution_agent_name_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_locked_at": { + "name": "execution_locked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_number": { + "name": "issue_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_kind": { + "name": "origin_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "origin_id": { + "name": "origin_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_depth": { + "name": "request_depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_adapter_overrides": { + "name": "assignee_adapter_overrides", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_policy": { + "name": "execution_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_state": { + "name": "execution_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_preference": { + "name": "execution_workspace_preference", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_settings": { + "name": "execution_workspace_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "hidden_at": { + "name": "hidden_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issues_company_status_idx": { + "name": "issues_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_status_idx": { + "name": "issues_company_assignee_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_user_status_idx": { + "name": "issues_company_assignee_user_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_parent_idx": { + "name": "issues_company_parent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_idx": { + "name": "issues_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_origin_idx": { + "name": "issues_company_origin_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_workspace_idx": { + "name": "issues_company_project_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_execution_workspace_idx": { + "name": "issues_company_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_identifier_idx": { + "name": "issues_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_title_search_idx": { + "name": "issues_title_search_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_identifier_search_idx": { + "name": "issues_identifier_search_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_description_search_idx": { + "name": "issues_description_search_idx", + "columns": [ + { + "expression": "description", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_open_routine_execution_uq": { + "name": "issues_open_routine_execution_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'routine_execution'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"execution_run_id\" is not null\n and \"issues\".\"status\" in ('backlog', 'todo', 'in_progress', 'in_review', 'blocked')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issues_company_id_companies_id_fk": { + "name": "issues_company_id_companies_id_fk", + "tableFrom": "issues", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_id_projects_id_fk": { + "name": "issues_project_id_projects_id_fk", + "tableFrom": "issues", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_workspace_id_project_workspaces_id_fk": { + "name": "issues_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_goal_id_goals_id_fk": { + "name": "issues_goal_id_goals_id_fk", + "tableFrom": "issues", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_parent_id_issues_id_fk": { + "name": "issues_parent_id_issues_id_fk", + "tableFrom": "issues", + "tableTo": "issues", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_assignee_agent_id_agents_id_fk": { + "name": "issues_assignee_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_checkout_run_id_heartbeat_runs_id_fk": { + "name": "issues_checkout_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "checkout_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_execution_run_id_heartbeat_runs_id_fk": { + "name": "issues_execution_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "execution_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_created_by_agent_id_agents_id_fk": { + "name": "issues_created_by_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issues_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.join_requests": { + "name": "join_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "invite_id": { + "name": "invite_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_type": { + "name": "request_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending_approval'" + }, + "request_ip": { + "name": "request_ip", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requesting_user_id": { + "name": "requesting_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_email_snapshot": { + "name": "request_email_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_name": { + "name": "agent_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_defaults_payload": { + "name": "agent_defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "claim_secret_hash": { + "name": "claim_secret_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_secret_expires_at": { + "name": "claim_secret_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claim_secret_consumed_at": { + "name": "claim_secret_consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_agent_id": { + "name": "created_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "rejected_by_user_id": { + "name": "rejected_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejected_at": { + "name": "rejected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "join_requests_invite_unique_idx": { + "name": "join_requests_invite_unique_idx", + "columns": [ + { + "expression": "invite_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_company_status_type_created_idx": { + "name": "join_requests_company_status_type_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "join_requests_invite_id_invites_id_fk": { + "name": "join_requests_invite_id_invites_id_fk", + "tableFrom": "join_requests", + "tableTo": "invites", + "columnsFrom": [ + "invite_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_company_id_companies_id_fk": { + "name": "join_requests_company_id_companies_id_fk", + "tableFrom": "join_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_created_agent_id_agents_id_fk": { + "name": "join_requests_created_agent_id_agents_id_fk", + "tableFrom": "join_requests", + "tableTo": "agents", + "columnsFrom": [ + "created_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.labels": { + "name": "labels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "labels_company_idx": { + "name": "labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "labels_company_name_idx": { + "name": "labels_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "labels_company_id_companies_id_fk": { + "name": "labels_company_id_companies_id_fk", + "tableFrom": "labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_company_settings": { + "name": "plugin_company_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "settings_json": { + "name": "settings_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_company_settings_company_idx": { + "name": "plugin_company_settings_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_plugin_idx": { + "name": "plugin_company_settings_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_company_plugin_uq": { + "name": "plugin_company_settings_company_plugin_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_company_settings_company_id_companies_id_fk": { + "name": "plugin_company_settings_company_id_companies_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_company_settings_plugin_id_plugins_id_fk": { + "name": "plugin_company_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_config": { + "name": "plugin_config", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "config_json": { + "name": "config_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_config_plugin_id_idx": { + "name": "plugin_config_plugin_id_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_config_plugin_id_plugins_id_fk": { + "name": "plugin_config_plugin_id_plugins_id_fk", + "tableFrom": "plugin_config", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_entities": { + "name": "plugin_entities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_entities_plugin_idx": { + "name": "plugin_entities_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_type_idx": { + "name": "plugin_entities_type_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_scope_idx": { + "name": "plugin_entities_scope_idx", + "columns": [ + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_external_idx": { + "name": "plugin_entities_external_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_entities_plugin_id_plugins_id_fk": { + "name": "plugin_entities_plugin_id_plugins_id_fk", + "tableFrom": "plugin_entities", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_job_runs": { + "name": "plugin_job_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "job_id": { + "name": "job_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logs": { + "name": "logs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_job_runs_job_idx": { + "name": "plugin_job_runs_job_idx", + "columns": [ + { + "expression": "job_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_plugin_idx": { + "name": "plugin_job_runs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_status_idx": { + "name": "plugin_job_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_job_runs_job_id_plugin_jobs_id_fk": { + "name": "plugin_job_runs_job_id_plugin_jobs_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugin_jobs", + "columnsFrom": [ + "job_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_job_runs_plugin_id_plugins_id_fk": { + "name": "plugin_job_runs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_jobs": { + "name": "plugin_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "job_key": { + "name": "job_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_jobs_plugin_idx": { + "name": "plugin_jobs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_next_run_idx": { + "name": "plugin_jobs_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_unique_idx": { + "name": "plugin_jobs_unique_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "job_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_jobs_plugin_id_plugins_id_fk": { + "name": "plugin_jobs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_jobs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_logs": { + "name": "plugin_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'info'" + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "meta": { + "name": "meta", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_logs_plugin_time_idx": { + "name": "plugin_logs_plugin_time_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_logs_level_idx": { + "name": "plugin_logs_level_idx", + "columns": [ + { + "expression": "level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_logs_plugin_id_plugins_id_fk": { + "name": "plugin_logs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_logs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_state": { + "name": "plugin_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "namespace": { + "name": "namespace", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "state_key": { + "name": "state_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value_json": { + "name": "value_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_state_plugin_scope_idx": { + "name": "plugin_state_plugin_scope_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_state_plugin_id_plugins_id_fk": { + "name": "plugin_state_plugin_id_plugins_id_fk", + "tableFrom": "plugin_state", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "plugin_state_unique_entry_idx": { + "name": "plugin_state_unique_entry_idx", + "nullsNotDistinct": true, + "columns": [ + "plugin_id", + "scope_kind", + "scope_id", + "namespace", + "state_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_webhook_deliveries": { + "name": "plugin_webhook_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "webhook_key": { + "name": "webhook_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "headers": { + "name": "headers", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_webhook_deliveries_plugin_idx": { + "name": "plugin_webhook_deliveries_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_status_idx": { + "name": "plugin_webhook_deliveries_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_key_idx": { + "name": "plugin_webhook_deliveries_key_idx", + "columns": [ + { + "expression": "webhook_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_webhook_deliveries_plugin_id_plugins_id_fk": { + "name": "plugin_webhook_deliveries_plugin_id_plugins_id_fk", + "tableFrom": "plugin_webhook_deliveries", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugins": { + "name": "plugins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "package_name": { + "name": "package_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "api_version": { + "name": "api_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "manifest_json": { + "name": "manifest_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'installed'" + }, + "install_order": { + "name": "install_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "package_path": { + "name": "package_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_at": { + "name": "installed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugins_plugin_key_idx": { + "name": "plugins_plugin_key_idx", + "columns": [ + { + "expression": "plugin_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugins_status_idx": { + "name": "plugins_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.principal_permission_grants": { + "name": "principal_permission_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_key": { + "name": "permission_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "granted_by_user_id": { + "name": "granted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "principal_permission_grants_unique_idx": { + "name": "principal_permission_grants_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "principal_permission_grants_company_permission_idx": { + "name": "principal_permission_grants_company_permission_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "principal_permission_grants_company_id_companies_id_fk": { + "name": "principal_permission_grants_company_id_companies_id_fk", + "tableFrom": "principal_permission_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_goals": { + "name": "project_goals", + "schema": "", + "columns": { + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_goals_project_idx": { + "name": "project_goals_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_goal_idx": { + "name": "project_goals_goal_idx", + "columns": [ + { + "expression": "goal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_company_idx": { + "name": "project_goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_goals_project_id_projects_id_fk": { + "name": "project_goals_project_id_projects_id_fk", + "tableFrom": "project_goals", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_goal_id_goals_id_fk": { + "name": "project_goals_goal_id_goals_id_fk", + "tableFrom": "project_goals", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_company_id_companies_id_fk": { + "name": "project_goals_company_id_companies_id_fk", + "tableFrom": "project_goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "project_goals_project_id_goal_id_pk": { + "name": "project_goals_project_id_goal_id_pk", + "columns": [ + "project_id", + "goal_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_workspaces": { + "name": "project_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_ref": { + "name": "repo_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_ref": { + "name": "default_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "setup_command": { + "name": "setup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cleanup_command": { + "name": "cleanup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_provider": { + "name": "remote_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_workspace_ref": { + "name": "remote_workspace_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_workspace_key": { + "name": "shared_workspace_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_workspaces_company_project_idx": { + "name": "project_workspaces_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_primary_idx": { + "name": "project_workspaces_project_primary_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_primary", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_source_type_idx": { + "name": "project_workspaces_project_source_type_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_company_shared_key_idx": { + "name": "project_workspaces_company_shared_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "shared_workspace_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_remote_ref_idx": { + "name": "project_workspaces_project_remote_ref_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_workspace_ref", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_workspaces_company_id_companies_id_fk": { + "name": "project_workspaces_company_id_companies_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "project_workspaces_project_id_projects_id_fk": { + "name": "project_workspaces_project_id_projects_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "lead_agent_id": { + "name": "lead_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_date": { + "name": "target_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_policy": { + "name": "execution_workspace_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "projects_company_idx": { + "name": "projects_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "projects_company_id_companies_id_fk": { + "name": "projects_company_id_companies_id_fk", + "tableFrom": "projects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_goal_id_goals_id_fk": { + "name": "projects_goal_id_goals_id_fk", + "tableFrom": "projects", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_lead_agent_id_agents_id_fk": { + "name": "projects_lead_agent_id_agents_id_fk", + "tableFrom": "projects", + "tableTo": "agents", + "columnsFrom": [ + "lead_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_runs": { + "name": "routine_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger_id": { + "name": "trigger_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "triggered_at": { + "name": "triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_payload": { + "name": "trigger_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "linked_issue_id": { + "name": "linked_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "coalesced_into_run_id": { + "name": "coalesced_into_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_runs_company_routine_idx": { + "name": "routine_runs_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idx": { + "name": "routine_runs_trigger_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_linked_issue_idx": { + "name": "routine_runs_linked_issue_idx", + "columns": [ + { + "expression": "linked_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idempotency_idx": { + "name": "routine_runs_trigger_idempotency_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_runs_company_id_companies_id_fk": { + "name": "routine_runs_company_id_companies_id_fk", + "tableFrom": "routine_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_routine_id_routines_id_fk": { + "name": "routine_runs_routine_id_routines_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_trigger_id_routine_triggers_id_fk": { + "name": "routine_runs_trigger_id_routine_triggers_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routine_triggers", + "columnsFrom": [ + "trigger_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_runs_linked_issue_id_issues_id_fk": { + "name": "routine_runs_linked_issue_id_issues_id_fk", + "tableFrom": "routine_runs", + "tableTo": "issues", + "columnsFrom": [ + "linked_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_triggers": { + "name": "routine_triggers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "public_id": { + "name": "public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "signing_mode": { + "name": "signing_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "replay_window_sec": { + "name": "replay_window_sec", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_rotated_at": { + "name": "last_rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_result": { + "name": "last_result", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_triggers_company_routine_idx": { + "name": "routine_triggers_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_company_kind_idx": { + "name": "routine_triggers_company_kind_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_next_run_idx": { + "name": "routine_triggers_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_idx": { + "name": "routine_triggers_public_id_idx", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_uq": { + "name": "routine_triggers_public_id_uq", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_triggers_company_id_companies_id_fk": { + "name": "routine_triggers_company_id_companies_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_routine_id_routines_id_fk": { + "name": "routine_triggers_routine_id_routines_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_secret_id_company_secrets_id_fk": { + "name": "routine_triggers_secret_id_company_secrets_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_created_by_agent_id_agents_id_fk": { + "name": "routine_triggers_created_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_updated_by_agent_id_agents_id_fk": { + "name": "routine_triggers_updated_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routines": { + "name": "routines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_issue_id": { + "name": "parent_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "concurrency_policy": { + "name": "concurrency_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'coalesce_if_active'" + }, + "catch_up_policy": { + "name": "catch_up_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'skip_missed'" + }, + "variables": { + "name": "variables", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_triggered_at": { + "name": "last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_enqueued_at": { + "name": "last_enqueued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routines_company_status_idx": { + "name": "routines_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_assignee_idx": { + "name": "routines_company_assignee_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_project_idx": { + "name": "routines_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routines_company_id_companies_id_fk": { + "name": "routines_company_id_companies_id_fk", + "tableFrom": "routines", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_project_id_projects_id_fk": { + "name": "routines_project_id_projects_id_fk", + "tableFrom": "routines", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_goal_id_goals_id_fk": { + "name": "routines_goal_id_goals_id_fk", + "tableFrom": "routines", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_parent_issue_id_issues_id_fk": { + "name": "routines_parent_issue_id_issues_id_fk", + "tableFrom": "routines", + "tableTo": "issues", + "columnsFrom": [ + "parent_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_assignee_agent_id_agents_id_fk": { + "name": "routines_assignee_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "routines_created_by_agent_id_agents_id_fk": { + "name": "routines_created_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_updated_by_agent_id_agents_id_fk": { + "name": "routines_updated_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_operations": { + "name": "workspace_operations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_operations_company_run_started_idx": { + "name": "workspace_operations_company_run_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_operations_company_workspace_started_idx": { + "name": "workspace_operations_company_workspace_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_operations_company_id_companies_id_fk": { + "name": "workspace_operations_company_id_companies_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_operations_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_operations_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_runtime_services": { + "name": "workspace_runtime_services", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "service_name": { + "name": "service_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reuse_key": { + "name": "reuse_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "started_by_run_id": { + "name": "started_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stop_policy": { + "name": "stop_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_runtime_services_company_workspace_status_idx": { + "name": "workspace_runtime_services_company_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_execution_workspace_status_idx": { + "name": "workspace_runtime_services_company_execution_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_project_status_idx": { + "name": "workspace_runtime_services_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_run_idx": { + "name": "workspace_runtime_services_run_idx", + "columns": [ + { + "expression": "started_by_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_updated_idx": { + "name": "workspace_runtime_services_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_runtime_services_company_id_companies_id_fk": { + "name": "workspace_runtime_services_company_id_companies_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_id_projects_id_fk": { + "name": "workspace_runtime_services_project_id_projects_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk": { + "name": "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_issue_id_issues_id_fk": { + "name": "workspace_runtime_services_issue_id_issues_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_owner_agent_id_agents_id_fk": { + "name": "workspace_runtime_services_owner_agent_id_agents_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk": { + "name": "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "started_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/src/migrations/meta/0053_snapshot.json b/packages/db/src/migrations/meta/0053_snapshot.json new file mode 100644 index 00000000000..c72b4c3f628 --- /dev/null +++ b/packages/db/src/migrations/meta/0053_snapshot.json @@ -0,0 +1,12979 @@ +{ + "id": "eb8aba7f-540a-4ac6-9f58-1ed449707201", + "prevId": "90165bd7-c2f6-45a5-83ea-ba357b060428", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.activity_log": { + "name": "activity_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "activity_log_company_created_idx": { + "name": "activity_log_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_run_id_idx": { + "name": "activity_log_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_entity_type_id_idx": { + "name": "activity_log_entity_type_id_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "activity_log_company_id_companies_id_fk": { + "name": "activity_log_company_id_companies_id_fk", + "tableFrom": "activity_log", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_agent_id_agents_id_fk": { + "name": "activity_log_agent_id_agents_id_fk", + "tableFrom": "activity_log", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_run_id_heartbeat_runs_id_fk": { + "name": "activity_log_run_id_heartbeat_runs_id_fk", + "tableFrom": "activity_log", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_api_keys": { + "name": "agent_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_api_keys_key_hash_idx": { + "name": "agent_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_api_keys_company_agent_idx": { + "name": "agent_api_keys_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_api_keys_agent_id_agents_id_fk": { + "name": "agent_api_keys_agent_id_agents_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_api_keys_company_id_companies_id_fk": { + "name": "agent_api_keys_company_id_companies_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_config_revisions": { + "name": "agent_config_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'patch'" + }, + "rolled_back_from_revision_id": { + "name": "rolled_back_from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "changed_keys": { + "name": "changed_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "before_config": { + "name": "before_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "after_config": { + "name": "after_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_config_revisions_company_agent_created_idx": { + "name": "agent_config_revisions_company_agent_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_config_revisions_agent_created_idx": { + "name": "agent_config_revisions_agent_created_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_config_revisions_company_id_companies_id_fk": { + "name": "agent_config_revisions_company_id_companies_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_config_revisions_agent_id_agents_id_fk": { + "name": "agent_config_revisions_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_config_revisions_created_by_agent_id_agents_id_fk": { + "name": "agent_config_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_runtime_state": { + "name": "agent_runtime_state", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_json": { + "name": "state_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_run_status": { + "name": "last_run_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total_input_tokens": { + "name": "total_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_output_tokens": { + "name": "total_output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cached_input_tokens": { + "name": "total_cached_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost_cents": { + "name": "total_cost_cents", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_runtime_state_company_agent_idx": { + "name": "agent_runtime_state_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_runtime_state_company_updated_idx": { + "name": "agent_runtime_state_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_runtime_state_agent_id_agents_id_fk": { + "name": "agent_runtime_state_agent_id_agents_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_runtime_state_company_id_companies_id_fk": { + "name": "agent_runtime_state_company_id_companies_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_task_sessions": { + "name": "agent_task_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "task_key": { + "name": "task_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_params_json": { + "name": "session_params_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_display_id": { + "name": "session_display_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_task_sessions_company_agent_adapter_task_uniq": { + "name": "agent_task_sessions_company_agent_adapter_task_uniq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "adapter_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_agent_updated_idx": { + "name": "agent_task_sessions_company_agent_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_task_updated_idx": { + "name": "agent_task_sessions_company_task_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_task_sessions_company_id_companies_id_fk": { + "name": "agent_task_sessions_company_id_companies_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_agent_id_agents_id_fk": { + "name": "agent_task_sessions_agent_id_agents_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_last_run_id_heartbeat_runs_id_fk": { + "name": "agent_task_sessions_last_run_id_heartbeat_runs_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "last_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_wakeup_requests": { + "name": "agent_wakeup_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "coalesced_count": { + "name": "coalesced_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "requested_by_actor_type": { + "name": "requested_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by_actor_id": { + "name": "requested_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_wakeup_requests_company_agent_status_idx": { + "name": "agent_wakeup_requests_company_agent_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_company_requested_idx": { + "name": "agent_wakeup_requests_company_requested_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_agent_requested_idx": { + "name": "agent_wakeup_requests_agent_requested_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_wakeup_requests_company_id_companies_id_fk": { + "name": "agent_wakeup_requests_company_id_companies_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_wakeup_requests_agent_id_agents_id_fk": { + "name": "agent_wakeup_requests_agent_id_agents_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "reports_to": { + "name": "reports_to", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'process'" + }, + "adapter_config": { + "name": "adapter_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "runtime_config": { + "name": "runtime_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_heartbeat_at": { + "name": "last_heartbeat_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agents_company_status_idx": { + "name": "agents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_company_reports_to_idx": { + "name": "agents_company_reports_to_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reports_to", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agents_company_id_companies_id_fk": { + "name": "agents_company_id_companies_id_fk", + "tableFrom": "agents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agents_reports_to_agents_id_fk": { + "name": "agents_reports_to_agents_id_fk", + "tableFrom": "agents", + "tableTo": "agents", + "columnsFrom": [ + "reports_to" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approval_comments": { + "name": "approval_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approval_comments_company_idx": { + "name": "approval_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_idx": { + "name": "approval_comments_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_created_idx": { + "name": "approval_comments_approval_created_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approval_comments_company_id_companies_id_fk": { + "name": "approval_comments_company_id_companies_id_fk", + "tableFrom": "approval_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_approval_id_approvals_id_fk": { + "name": "approval_comments_approval_id_approvals_id_fk", + "tableFrom": "approval_comments", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_author_agent_id_agents_id_fk": { + "name": "approval_comments_author_agent_id_agents_id_fk", + "tableFrom": "approval_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approvals": { + "name": "approvals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_by_agent_id": { + "name": "requested_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_by_user_id": { + "name": "requested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "decision_note": { + "name": "decision_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approvals_company_status_type_idx": { + "name": "approvals_company_status_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approvals_company_id_companies_id_fk": { + "name": "approvals_company_id_companies_id_fk", + "tableFrom": "approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approvals_requested_by_agent_id_agents_id_fk": { + "name": "approvals_requested_by_agent_id_agents_id_fk", + "tableFrom": "approvals", + "tableTo": "agents", + "columnsFrom": [ + "requested_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.assets": { + "name": "assets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "object_key": { + "name": "object_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "original_filename": { + "name": "original_filename", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "assets_company_created_idx": { + "name": "assets_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_provider_idx": { + "name": "assets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_object_key_uq": { + "name": "assets_company_object_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "assets_company_id_companies_id_fk": { + "name": "assets_company_id_companies_id_fk", + "tableFrom": "assets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "assets_created_by_agent_id_agents_id_fk": { + "name": "assets_created_by_agent_id_agents_id_fk", + "tableFrom": "assets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.board_api_keys": { + "name": "board_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "board_api_keys_key_hash_idx": { + "name": "board_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "board_api_keys_user_idx": { + "name": "board_api_keys_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "board_api_keys_user_id_user_id_fk": { + "name": "board_api_keys_user_id_user_id_fk", + "tableFrom": "board_api_keys", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_incidents": { + "name": "budget_incidents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "policy_id": { + "name": "policy_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_end": { + "name": "window_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "threshold_type": { + "name": "threshold_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_limit": { + "name": "amount_limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "amount_observed": { + "name": "amount_observed", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_incidents_company_status_idx": { + "name": "budget_incidents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_company_scope_idx": { + "name": "budget_incidents_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_policy_window_threshold_idx": { + "name": "budget_incidents_policy_window_threshold_idx", + "columns": [ + { + "expression": "policy_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "threshold_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"budget_incidents\".\"status\" <> 'dismissed'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_incidents_company_id_companies_id_fk": { + "name": "budget_incidents_company_id_companies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_policy_id_budget_policies_id_fk": { + "name": "budget_incidents_policy_id_budget_policies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "budget_policies", + "columnsFrom": [ + "policy_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_approval_id_approvals_id_fk": { + "name": "budget_incidents_approval_id_approvals_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_policies": { + "name": "budget_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'billed_cents'" + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "warn_percent": { + "name": "warn_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 80 + }, + "hard_stop_enabled": { + "name": "hard_stop_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "notify_enabled": { + "name": "notify_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_policies_company_scope_active_idx": { + "name": "budget_policies_company_scope_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_window_idx": { + "name": "budget_policies_company_window_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_scope_metric_unique_idx": { + "name": "budget_policies_company_scope_metric_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_policies_company_id_companies_id_fk": { + "name": "budget_policies_company_id_companies_id_fk", + "tableFrom": "budget_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cli_auth_challenges": { + "name": "cli_auth_challenges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_hash": { + "name": "secret_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_access": { + "name": "requested_access", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'board'" + }, + "requested_company_id": { + "name": "requested_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "pending_key_hash": { + "name": "pending_key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pending_key_name": { + "name": "pending_key_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "board_api_key_id": { + "name": "board_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cli_auth_challenges_secret_hash_idx": { + "name": "cli_auth_challenges_secret_hash_idx", + "columns": [ + { + "expression": "secret_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_approved_by_idx": { + "name": "cli_auth_challenges_approved_by_idx", + "columns": [ + { + "expression": "approved_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_requested_company_idx": { + "name": "cli_auth_challenges_requested_company_idx", + "columns": [ + { + "expression": "requested_company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cli_auth_challenges_requested_company_id_companies_id_fk": { + "name": "cli_auth_challenges_requested_company_id_companies_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "companies", + "columnsFrom": [ + "requested_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_approved_by_user_id_user_id_fk": { + "name": "cli_auth_challenges_approved_by_user_id_user_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "user", + "columnsFrom": [ + "approved_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk": { + "name": "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "board_api_keys", + "columnsFrom": [ + "board_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.companies": { + "name": "companies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "issue_prefix": { + "name": "issue_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'PAP'" + }, + "issue_counter": { + "name": "issue_counter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "require_board_approval_for_new_agents": { + "name": "require_board_approval_for_new_agents", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "feedback_data_sharing_enabled": { + "name": "feedback_data_sharing_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "feedback_data_sharing_consent_at": { + "name": "feedback_data_sharing_consent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "feedback_data_sharing_consent_by_user_id": { + "name": "feedback_data_sharing_consent_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feedback_data_sharing_terms_version": { + "name": "feedback_data_sharing_terms_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "brand_color": { + "name": "brand_color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "companies_issue_prefix_idx": { + "name": "companies_issue_prefix_idx", + "columns": [ + { + "expression": "issue_prefix", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_logos": { + "name": "company_logos", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_logos_company_uq": { + "name": "company_logos_company_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_logos_asset_uq": { + "name": "company_logos_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_logos_company_id_companies_id_fk": { + "name": "company_logos_company_id_companies_id_fk", + "tableFrom": "company_logos", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_logos_asset_id_assets_id_fk": { + "name": "company_logos_asset_id_assets_id_fk", + "tableFrom": "company_logos", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_memberships": { + "name": "company_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "membership_role": { + "name": "membership_role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_memberships_company_principal_unique_idx": { + "name": "company_memberships_company_principal_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_principal_status_idx": { + "name": "company_memberships_principal_status_idx", + "columns": [ + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_company_status_idx": { + "name": "company_memberships_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_memberships_company_id_companies_id_fk": { + "name": "company_memberships_company_id_companies_id_fk", + "tableFrom": "company_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_versions": { + "name": "company_secret_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "material": { + "name": "material", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "value_sha256": { + "name": "value_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "company_secret_versions_secret_idx": { + "name": "company_secret_versions_secret_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_value_sha256_idx": { + "name": "company_secret_versions_value_sha256_idx", + "columns": [ + { + "expression": "value_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_secret_version_uq": { + "name": "company_secret_versions_secret_version_uq", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_versions_secret_id_company_secrets_id_fk": { + "name": "company_secret_versions_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_versions_created_by_agent_id_agents_id_fk": { + "name": "company_secret_versions_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secrets": { + "name": "company_secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_encrypted'" + }, + "external_ref": { + "name": "external_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "latest_version": { + "name": "latest_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secrets_company_idx": { + "name": "company_secrets_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_provider_idx": { + "name": "company_secrets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_name_uq": { + "name": "company_secrets_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secrets_company_id_companies_id_fk": { + "name": "company_secrets_company_id_companies_id_fk", + "tableFrom": "company_secrets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secrets_created_by_agent_id_agents_id_fk": { + "name": "company_secrets_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secrets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skills": { + "name": "company_skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "markdown": { + "name": "markdown", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "source_locator": { + "name": "source_locator", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_ref": { + "name": "source_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trust_level": { + "name": "trust_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown_only'" + }, + "compatibility": { + "name": "compatibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'compatible'" + }, + "file_inventory": { + "name": "file_inventory", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skills_company_key_idx": { + "name": "company_skills_company_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_name_idx": { + "name": "company_skills_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skills_company_id_companies_id_fk": { + "name": "company_skills_company_id_companies_id_fk", + "tableFrom": "company_skills", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cost_events": { + "name": "cost_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "billing_type": { + "name": "billing_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cached_input_tokens": { + "name": "cached_input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cost_cents": { + "name": "cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cost_events_company_occurred_idx": { + "name": "cost_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_agent_occurred_idx": { + "name": "cost_events_company_agent_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_provider_occurred_idx": { + "name": "cost_events_company_provider_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_biller_occurred_idx": { + "name": "cost_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_heartbeat_run_idx": { + "name": "cost_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cost_events_company_id_companies_id_fk": { + "name": "cost_events_company_id_companies_id_fk", + "tableFrom": "cost_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_agent_id_agents_id_fk": { + "name": "cost_events_agent_id_agents_id_fk", + "tableFrom": "cost_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_issue_id_issues_id_fk": { + "name": "cost_events_issue_id_issues_id_fk", + "tableFrom": "cost_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_project_id_projects_id_fk": { + "name": "cost_events_project_id_projects_id_fk", + "tableFrom": "cost_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_goal_id_goals_id_fk": { + "name": "cost_events_goal_id_goals_id_fk", + "tableFrom": "cost_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "cost_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "cost_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_revisions": { + "name": "document_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_revisions_document_revision_uq": { + "name": "document_revisions_document_revision_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_revisions_company_document_created_idx": { + "name": "document_revisions_company_document_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_revisions_company_id_companies_id_fk": { + "name": "document_revisions_company_id_companies_id_fk", + "tableFrom": "document_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_revisions_document_id_documents_id_fk": { + "name": "document_revisions_document_id_documents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_revisions_created_by_agent_id_agents_id_fk": { + "name": "document_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_revisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "document_revisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "document_revisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.documents": { + "name": "documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "latest_body": { + "name": "latest_body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "latest_revision_id": { + "name": "latest_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "latest_revision_number": { + "name": "latest_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "documents_company_updated_idx": { + "name": "documents_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_company_created_idx": { + "name": "documents_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "documents_company_id_companies_id_fk": { + "name": "documents_company_id_companies_id_fk", + "tableFrom": "documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "documents_created_by_agent_id_agents_id_fk": { + "name": "documents_created_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "documents_updated_by_agent_id_agents_id_fk": { + "name": "documents_updated_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_workspaces": { + "name": "execution_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "strategy_type": { + "name": "strategy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_ref": { + "name": "base_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch_name": { + "name": "branch_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_fs'" + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "derived_from_execution_workspace_id": { + "name": "derived_from_execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "opened_at": { + "name": "opened_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_eligible_at": { + "name": "cleanup_eligible_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_reason": { + "name": "cleanup_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_workspaces_company_project_status_idx": { + "name": "execution_workspaces_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_project_workspace_status_idx": { + "name": "execution_workspaces_company_project_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_source_issue_idx": { + "name": "execution_workspaces_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_last_used_idx": { + "name": "execution_workspaces_company_last_used_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_branch_idx": { + "name": "execution_workspaces_company_branch_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspaces_company_id_companies_id_fk": { + "name": "execution_workspaces_company_id_companies_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "execution_workspaces_project_id_projects_id_fk": { + "name": "execution_workspaces_project_id_projects_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspaces_project_workspace_id_project_workspaces_id_fk": { + "name": "execution_workspaces_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_source_issue_id_issues_id_fk": { + "name": "execution_workspaces_source_issue_id_issues_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk": { + "name": "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "derived_from_execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feedback_exports": { + "name": "feedback_exports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "feedback_vote_id": { + "name": "feedback_vote_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_only'" + }, + "destination": { + "name": "destination", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "export_id": { + "name": "export_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consent_version": { + "name": "consent_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "schema_version": { + "name": "schema_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-envelope-v2'" + }, + "bundle_version": { + "name": "bundle_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-bundle-v2'" + }, + "payload_version": { + "name": "payload_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-v1'" + }, + "payload_digest": { + "name": "payload_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload_snapshot": { + "name": "payload_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "target_summary": { + "name": "target_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "redaction_summary": { + "name": "redaction_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempted_at": { + "name": "last_attempted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "exported_at": { + "name": "exported_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_exports_feedback_vote_idx": { + "name": "feedback_exports_feedback_vote_idx", + "columns": [ + { + "expression": "feedback_vote_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_created_idx": { + "name": "feedback_exports_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_status_idx": { + "name": "feedback_exports_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_issue_idx": { + "name": "feedback_exports_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_project_idx": { + "name": "feedback_exports_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_author_idx": { + "name": "feedback_exports_company_author_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_exports_company_id_companies_id_fk": { + "name": "feedback_exports_company_id_companies_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "feedback_exports_feedback_vote_id_feedback_votes_id_fk": { + "name": "feedback_exports_feedback_vote_id_feedback_votes_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "feedback_votes", + "columnsFrom": [ + "feedback_vote_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_exports_issue_id_issues_id_fk": { + "name": "feedback_exports_issue_id_issues_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_exports_project_id_projects_id_fk": { + "name": "feedback_exports_project_id_projects_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feedback_votes": { + "name": "feedback_votes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_with_labs": { + "name": "shared_with_labs", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "shared_at": { + "name": "shared_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "consent_version": { + "name": "consent_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redaction_summary": { + "name": "redaction_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_votes_company_issue_idx": { + "name": "feedback_votes_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_issue_target_idx": { + "name": "feedback_votes_issue_target_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_author_idx": { + "name": "feedback_votes_author_idx", + "columns": [ + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_company_target_author_idx": { + "name": "feedback_votes_company_target_author_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_votes_company_id_companies_id_fk": { + "name": "feedback_votes_company_id_companies_id_fk", + "tableFrom": "feedback_votes", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "feedback_votes_issue_id_issues_id_fk": { + "name": "feedback_votes_issue_id_issues_id_fk", + "tableFrom": "feedback_votes", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.finance_events": { + "name": "finance_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cost_event_id": { + "name": "cost_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "event_kind": { + "name": "event_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'debit'" + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_adapter_type": { + "name": "execution_adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pricing_tier": { + "name": "pricing_tier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "estimated": { + "name": "estimated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "external_invoice_id": { + "name": "external_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata_json": { + "name": "metadata_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "finance_events_company_occurred_idx": { + "name": "finance_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_biller_occurred_idx": { + "name": "finance_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_kind_occurred_idx": { + "name": "finance_events_company_kind_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_direction_occurred_idx": { + "name": "finance_events_company_direction_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "direction", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_heartbeat_run_idx": { + "name": "finance_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_cost_event_idx": { + "name": "finance_events_company_cost_event_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "finance_events_company_id_companies_id_fk": { + "name": "finance_events_company_id_companies_id_fk", + "tableFrom": "finance_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_agent_id_agents_id_fk": { + "name": "finance_events_agent_id_agents_id_fk", + "tableFrom": "finance_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_issue_id_issues_id_fk": { + "name": "finance_events_issue_id_issues_id_fk", + "tableFrom": "finance_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_project_id_projects_id_fk": { + "name": "finance_events_project_id_projects_id_fk", + "tableFrom": "finance_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_goal_id_goals_id_fk": { + "name": "finance_events_goal_id_goals_id_fk", + "tableFrom": "finance_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "finance_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "finance_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_cost_event_id_cost_events_id_fk": { + "name": "finance_events_cost_event_id_cost_events_id_fk", + "tableFrom": "finance_events", + "tableTo": "cost_events", + "columnsFrom": [ + "cost_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.goals": { + "name": "goals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'task'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'planned'" + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "goals_company_idx": { + "name": "goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "goals_company_id_companies_id_fk": { + "name": "goals_company_id_companies_id_fk", + "tableFrom": "goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_parent_id_goals_id_fk": { + "name": "goals_parent_id_goals_id_fk", + "tableFrom": "goals", + "tableTo": "goals", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_owner_agent_id_agents_id_fk": { + "name": "goals_owner_agent_id_agents_id_fk", + "tableFrom": "goals", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_run_events": { + "name": "heartbeat_run_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stream": { + "name": "stream", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_run_events_run_seq_idx": { + "name": "heartbeat_run_events_run_seq_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_run_idx": { + "name": "heartbeat_run_events_company_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_created_idx": { + "name": "heartbeat_run_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_run_events_company_id_companies_id_fk": { + "name": "heartbeat_run_events_company_id_companies_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_events_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_agent_id_agents_id_fk": { + "name": "heartbeat_run_events_agent_id_agents_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_runs": { + "name": "heartbeat_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invocation_source": { + "name": "invocation_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'on_demand'" + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wakeup_request_id": { + "name": "wakeup_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "signal": { + "name": "signal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "usage_json": { + "name": "usage_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_json": { + "name": "result_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_id_before": { + "name": "session_id_before", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id_after": { + "name": "session_id_after", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_run_id": { + "name": "external_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "process_pid": { + "name": "process_pid", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "process_started_at": { + "name": "process_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "retry_of_run_id": { + "name": "retry_of_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "process_loss_retry_count": { + "name": "process_loss_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "context_snapshot": { + "name": "context_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_runs_company_agent_started_idx": { + "name": "heartbeat_runs_company_agent_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_runs_company_id_companies_id_fk": { + "name": "heartbeat_runs_company_id_companies_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_agent_id_agents_id_fk": { + "name": "heartbeat_runs_agent_id_agents_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk": { + "name": "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agent_wakeup_requests", + "columnsFrom": [ + "wakeup_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "retry_of_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.inbox_dismissals": { + "name": "inbox_dismissals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "item_key": { + "name": "item_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "inbox_dismissals_company_user_idx": { + "name": "inbox_dismissals_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_dismissals_company_item_idx": { + "name": "inbox_dismissals_company_item_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_dismissals_company_user_item_idx": { + "name": "inbox_dismissals_company_user_item_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "inbox_dismissals_company_id_companies_id_fk": { + "name": "inbox_dismissals_company_id_companies_id_fk", + "tableFrom": "inbox_dismissals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_settings": { + "name": "instance_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "singleton_key": { + "name": "singleton_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "general": { + "name": "general", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "experimental": { + "name": "experimental", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_settings_singleton_key_idx": { + "name": "instance_settings_singleton_key_idx", + "columns": [ + { + "expression": "singleton_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_user_roles": { + "name": "instance_user_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'instance_admin'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_user_roles_user_role_unique_idx": { + "name": "instance_user_roles_user_role_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "instance_user_roles_role_idx": { + "name": "instance_user_roles_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invites": { + "name": "invites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "invite_type": { + "name": "invite_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company_join'" + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allowed_join_types": { + "name": "allowed_join_types", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'both'" + }, + "defaults_payload": { + "name": "defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "invited_by_user_id": { + "name": "invited_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invites_token_hash_unique_idx": { + "name": "invites_token_hash_unique_idx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invites_company_invite_state_idx": { + "name": "invites_company_invite_state_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invite_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revoked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invites_company_id_companies_id_fk": { + "name": "invites_company_id_companies_id_fk", + "tableFrom": "invites", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_approvals": { + "name": "issue_approvals", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "linked_by_agent_id": { + "name": "linked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "linked_by_user_id": { + "name": "linked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_approvals_issue_idx": { + "name": "issue_approvals_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_approval_idx": { + "name": "issue_approvals_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_company_idx": { + "name": "issue_approvals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_approvals_company_id_companies_id_fk": { + "name": "issue_approvals_company_id_companies_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_approvals_issue_id_issues_id_fk": { + "name": "issue_approvals_issue_id_issues_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_approval_id_approvals_id_fk": { + "name": "issue_approvals_approval_id_approvals_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_linked_by_agent_id_agents_id_fk": { + "name": "issue_approvals_linked_by_agent_id_agents_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "agents", + "columnsFrom": [ + "linked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_approvals_pk": { + "name": "issue_approvals_pk", + "columns": [ + "issue_id", + "approval_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_attachments": { + "name": "issue_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_comment_id": { + "name": "issue_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_attachments_company_issue_idx": { + "name": "issue_attachments_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_issue_comment_idx": { + "name": "issue_attachments_issue_comment_idx", + "columns": [ + { + "expression": "issue_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_asset_uq": { + "name": "issue_attachments_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_attachments_company_id_companies_id_fk": { + "name": "issue_attachments_company_id_companies_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_attachments_issue_id_issues_id_fk": { + "name": "issue_attachments_issue_id_issues_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_asset_id_assets_id_fk": { + "name": "issue_attachments_asset_id_assets_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_issue_comment_id_issue_comments_id_fk": { + "name": "issue_attachments_issue_comment_id_issue_comments_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issue_comments", + "columnsFrom": [ + "issue_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_comments": { + "name": "issue_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_comments_issue_idx": { + "name": "issue_comments_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_idx": { + "name": "issue_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_issue_created_at_idx": { + "name": "issue_comments_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_author_issue_created_at_idx": { + "name": "issue_comments_company_author_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_body_search_idx": { + "name": "issue_comments_body_search_idx", + "columns": [ + { + "expression": "body", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "issue_comments_company_id_companies_id_fk": { + "name": "issue_comments_company_id_companies_id_fk", + "tableFrom": "issue_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_issue_id_issues_id_fk": { + "name": "issue_comments_issue_id_issues_id_fk", + "tableFrom": "issue_comments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_author_agent_id_agents_id_fk": { + "name": "issue_comments_author_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_comments_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_documents": { + "name": "issue_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_documents_company_issue_key_uq": { + "name": "issue_documents_company_issue_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_document_uq": { + "name": "issue_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_company_issue_updated_idx": { + "name": "issue_documents_company_issue_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_documents_company_id_companies_id_fk": { + "name": "issue_documents_company_id_companies_id_fk", + "tableFrom": "issue_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_documents_issue_id_issues_id_fk": { + "name": "issue_documents_issue_id_issues_id_fk", + "tableFrom": "issue_documents", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_documents_document_id_documents_id_fk": { + "name": "issue_documents_document_id_documents_id_fk", + "tableFrom": "issue_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_inbox_archives": { + "name": "issue_inbox_archives", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_inbox_archives_company_issue_idx": { + "name": "issue_inbox_archives_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_user_idx": { + "name": "issue_inbox_archives_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_issue_user_idx": { + "name": "issue_inbox_archives_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_inbox_archives_company_id_companies_id_fk": { + "name": "issue_inbox_archives_company_id_companies_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_inbox_archives_issue_id_issues_id_fk": { + "name": "issue_inbox_archives_issue_id_issues_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_labels": { + "name": "issue_labels", + "schema": "", + "columns": { + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label_id": { + "name": "label_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_labels_issue_idx": { + "name": "issue_labels_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_label_idx": { + "name": "issue_labels_label_idx", + "columns": [ + { + "expression": "label_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_company_idx": { + "name": "issue_labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_labels_issue_id_issues_id_fk": { + "name": "issue_labels_issue_id_issues_id_fk", + "tableFrom": "issue_labels", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_label_id_labels_id_fk": { + "name": "issue_labels_label_id_labels_id_fk", + "tableFrom": "issue_labels", + "tableTo": "labels", + "columnsFrom": [ + "label_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_company_id_companies_id_fk": { + "name": "issue_labels_company_id_companies_id_fk", + "tableFrom": "issue_labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_labels_pk": { + "name": "issue_labels_pk", + "columns": [ + "issue_id", + "label_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_read_states": { + "name": "issue_read_states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_read_at": { + "name": "last_read_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_read_states_company_issue_idx": { + "name": "issue_read_states_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_user_idx": { + "name": "issue_read_states_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_issue_user_idx": { + "name": "issue_read_states_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_read_states_company_id_companies_id_fk": { + "name": "issue_read_states_company_id_companies_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_read_states_issue_id_issues_id_fk": { + "name": "issue_read_states_issue_id_issues_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_relations": { + "name": "issue_relations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "related_issue_id": { + "name": "related_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_relations_company_issue_idx": { + "name": "issue_relations_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_related_issue_idx": { + "name": "issue_relations_company_related_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "related_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_type_idx": { + "name": "issue_relations_company_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_edge_uq": { + "name": "issue_relations_company_edge_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "related_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_relations_company_id_companies_id_fk": { + "name": "issue_relations_company_id_companies_id_fk", + "tableFrom": "issue_relations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_relations_issue_id_issues_id_fk": { + "name": "issue_relations_issue_id_issues_id_fk", + "tableFrom": "issue_relations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_relations_related_issue_id_issues_id_fk": { + "name": "issue_relations_related_issue_id_issues_id_fk", + "tableFrom": "issue_relations", + "tableTo": "issues", + "columnsFrom": [ + "related_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_relations_created_by_agent_id_agents_id_fk": { + "name": "issue_relations_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_relations", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_work_products": { + "name": "issue_work_products", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "runtime_service_id": { + "name": "runtime_service_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "review_state": { + "name": "review_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_work_products_company_issue_type_idx": { + "name": "issue_work_products_company_issue_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_execution_workspace_type_idx": { + "name": "issue_work_products_company_execution_workspace_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_provider_external_id_idx": { + "name": "issue_work_products_company_provider_external_id_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_updated_idx": { + "name": "issue_work_products_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_work_products_company_id_companies_id_fk": { + "name": "issue_work_products_company_id_companies_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_work_products_project_id_projects_id_fk": { + "name": "issue_work_products_project_id_projects_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_issue_id_issues_id_fk": { + "name": "issue_work_products_issue_id_issues_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_work_products_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issue_work_products_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk": { + "name": "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "workspace_runtime_services", + "columnsFrom": [ + "runtime_service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_work_products_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issues": { + "name": "issues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assignee_user_id": { + "name": "assignee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "checkout_run_id": { + "name": "checkout_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_run_id": { + "name": "execution_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_agent_name_key": { + "name": "execution_agent_name_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_locked_at": { + "name": "execution_locked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_number": { + "name": "issue_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_kind": { + "name": "origin_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "origin_id": { + "name": "origin_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_depth": { + "name": "request_depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_adapter_overrides": { + "name": "assignee_adapter_overrides", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_preference": { + "name": "execution_workspace_preference", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_settings": { + "name": "execution_workspace_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "hidden_at": { + "name": "hidden_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issues_company_status_idx": { + "name": "issues_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_status_idx": { + "name": "issues_company_assignee_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_user_status_idx": { + "name": "issues_company_assignee_user_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_parent_idx": { + "name": "issues_company_parent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_idx": { + "name": "issues_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_origin_idx": { + "name": "issues_company_origin_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_workspace_idx": { + "name": "issues_company_project_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_execution_workspace_idx": { + "name": "issues_company_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_identifier_idx": { + "name": "issues_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_title_search_idx": { + "name": "issues_title_search_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_identifier_search_idx": { + "name": "issues_identifier_search_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_description_search_idx": { + "name": "issues_description_search_idx", + "columns": [ + { + "expression": "description", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_open_routine_execution_uq": { + "name": "issues_open_routine_execution_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'routine_execution'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"execution_run_id\" is not null\n and \"issues\".\"status\" in ('backlog', 'todo', 'in_progress', 'in_review', 'blocked')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issues_company_id_companies_id_fk": { + "name": "issues_company_id_companies_id_fk", + "tableFrom": "issues", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_id_projects_id_fk": { + "name": "issues_project_id_projects_id_fk", + "tableFrom": "issues", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_workspace_id_project_workspaces_id_fk": { + "name": "issues_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_goal_id_goals_id_fk": { + "name": "issues_goal_id_goals_id_fk", + "tableFrom": "issues", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_parent_id_issues_id_fk": { + "name": "issues_parent_id_issues_id_fk", + "tableFrom": "issues", + "tableTo": "issues", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_assignee_agent_id_agents_id_fk": { + "name": "issues_assignee_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_checkout_run_id_heartbeat_runs_id_fk": { + "name": "issues_checkout_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "checkout_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_execution_run_id_heartbeat_runs_id_fk": { + "name": "issues_execution_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "execution_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_created_by_agent_id_agents_id_fk": { + "name": "issues_created_by_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issues_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.join_requests": { + "name": "join_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "invite_id": { + "name": "invite_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_type": { + "name": "request_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending_approval'" + }, + "request_ip": { + "name": "request_ip", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requesting_user_id": { + "name": "requesting_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_email_snapshot": { + "name": "request_email_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_name": { + "name": "agent_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_defaults_payload": { + "name": "agent_defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "claim_secret_hash": { + "name": "claim_secret_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_secret_expires_at": { + "name": "claim_secret_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claim_secret_consumed_at": { + "name": "claim_secret_consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_agent_id": { + "name": "created_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "rejected_by_user_id": { + "name": "rejected_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejected_at": { + "name": "rejected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "join_requests_invite_unique_idx": { + "name": "join_requests_invite_unique_idx", + "columns": [ + { + "expression": "invite_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_company_status_type_created_idx": { + "name": "join_requests_company_status_type_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "join_requests_invite_id_invites_id_fk": { + "name": "join_requests_invite_id_invites_id_fk", + "tableFrom": "join_requests", + "tableTo": "invites", + "columnsFrom": [ + "invite_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_company_id_companies_id_fk": { + "name": "join_requests_company_id_companies_id_fk", + "tableFrom": "join_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_created_agent_id_agents_id_fk": { + "name": "join_requests_created_agent_id_agents_id_fk", + "tableFrom": "join_requests", + "tableTo": "agents", + "columnsFrom": [ + "created_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.labels": { + "name": "labels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "labels_company_idx": { + "name": "labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "labels_company_name_idx": { + "name": "labels_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "labels_company_id_companies_id_fk": { + "name": "labels_company_id_companies_id_fk", + "tableFrom": "labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_company_settings": { + "name": "plugin_company_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "settings_json": { + "name": "settings_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_company_settings_company_idx": { + "name": "plugin_company_settings_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_plugin_idx": { + "name": "plugin_company_settings_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_company_plugin_uq": { + "name": "plugin_company_settings_company_plugin_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_company_settings_company_id_companies_id_fk": { + "name": "plugin_company_settings_company_id_companies_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_company_settings_plugin_id_plugins_id_fk": { + "name": "plugin_company_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_config": { + "name": "plugin_config", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "config_json": { + "name": "config_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_config_plugin_id_idx": { + "name": "plugin_config_plugin_id_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_config_plugin_id_plugins_id_fk": { + "name": "plugin_config_plugin_id_plugins_id_fk", + "tableFrom": "plugin_config", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_entities": { + "name": "plugin_entities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_entities_plugin_idx": { + "name": "plugin_entities_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_type_idx": { + "name": "plugin_entities_type_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_scope_idx": { + "name": "plugin_entities_scope_idx", + "columns": [ + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_external_idx": { + "name": "plugin_entities_external_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_entities_plugin_id_plugins_id_fk": { + "name": "plugin_entities_plugin_id_plugins_id_fk", + "tableFrom": "plugin_entities", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_job_runs": { + "name": "plugin_job_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "job_id": { + "name": "job_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logs": { + "name": "logs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_job_runs_job_idx": { + "name": "plugin_job_runs_job_idx", + "columns": [ + { + "expression": "job_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_plugin_idx": { + "name": "plugin_job_runs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_status_idx": { + "name": "plugin_job_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_job_runs_job_id_plugin_jobs_id_fk": { + "name": "plugin_job_runs_job_id_plugin_jobs_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugin_jobs", + "columnsFrom": [ + "job_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_job_runs_plugin_id_plugins_id_fk": { + "name": "plugin_job_runs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_jobs": { + "name": "plugin_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "job_key": { + "name": "job_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_jobs_plugin_idx": { + "name": "plugin_jobs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_next_run_idx": { + "name": "plugin_jobs_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_unique_idx": { + "name": "plugin_jobs_unique_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "job_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_jobs_plugin_id_plugins_id_fk": { + "name": "plugin_jobs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_jobs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_logs": { + "name": "plugin_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'info'" + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "meta": { + "name": "meta", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_logs_plugin_time_idx": { + "name": "plugin_logs_plugin_time_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_logs_level_idx": { + "name": "plugin_logs_level_idx", + "columns": [ + { + "expression": "level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_logs_plugin_id_plugins_id_fk": { + "name": "plugin_logs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_logs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_state": { + "name": "plugin_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "namespace": { + "name": "namespace", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "state_key": { + "name": "state_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value_json": { + "name": "value_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_state_plugin_scope_idx": { + "name": "plugin_state_plugin_scope_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_state_plugin_id_plugins_id_fk": { + "name": "plugin_state_plugin_id_plugins_id_fk", + "tableFrom": "plugin_state", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "plugin_state_unique_entry_idx": { + "name": "plugin_state_unique_entry_idx", + "nullsNotDistinct": true, + "columns": [ + "plugin_id", + "scope_kind", + "scope_id", + "namespace", + "state_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_webhook_deliveries": { + "name": "plugin_webhook_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "webhook_key": { + "name": "webhook_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "headers": { + "name": "headers", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_webhook_deliveries_plugin_idx": { + "name": "plugin_webhook_deliveries_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_status_idx": { + "name": "plugin_webhook_deliveries_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_key_idx": { + "name": "plugin_webhook_deliveries_key_idx", + "columns": [ + { + "expression": "webhook_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_webhook_deliveries_plugin_id_plugins_id_fk": { + "name": "plugin_webhook_deliveries_plugin_id_plugins_id_fk", + "tableFrom": "plugin_webhook_deliveries", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugins": { + "name": "plugins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "package_name": { + "name": "package_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "api_version": { + "name": "api_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "manifest_json": { + "name": "manifest_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'installed'" + }, + "install_order": { + "name": "install_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "package_path": { + "name": "package_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_at": { + "name": "installed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugins_plugin_key_idx": { + "name": "plugins_plugin_key_idx", + "columns": [ + { + "expression": "plugin_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugins_status_idx": { + "name": "plugins_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.principal_permission_grants": { + "name": "principal_permission_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_key": { + "name": "permission_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "granted_by_user_id": { + "name": "granted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "principal_permission_grants_unique_idx": { + "name": "principal_permission_grants_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "principal_permission_grants_company_permission_idx": { + "name": "principal_permission_grants_company_permission_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "principal_permission_grants_company_id_companies_id_fk": { + "name": "principal_permission_grants_company_id_companies_id_fk", + "tableFrom": "principal_permission_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_goals": { + "name": "project_goals", + "schema": "", + "columns": { + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_goals_project_idx": { + "name": "project_goals_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_goal_idx": { + "name": "project_goals_goal_idx", + "columns": [ + { + "expression": "goal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_company_idx": { + "name": "project_goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_goals_project_id_projects_id_fk": { + "name": "project_goals_project_id_projects_id_fk", + "tableFrom": "project_goals", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_goal_id_goals_id_fk": { + "name": "project_goals_goal_id_goals_id_fk", + "tableFrom": "project_goals", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_company_id_companies_id_fk": { + "name": "project_goals_company_id_companies_id_fk", + "tableFrom": "project_goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "project_goals_project_id_goal_id_pk": { + "name": "project_goals_project_id_goal_id_pk", + "columns": [ + "project_id", + "goal_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_workspaces": { + "name": "project_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_ref": { + "name": "repo_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_ref": { + "name": "default_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "setup_command": { + "name": "setup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cleanup_command": { + "name": "cleanup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_provider": { + "name": "remote_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_workspace_ref": { + "name": "remote_workspace_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_workspace_key": { + "name": "shared_workspace_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_workspaces_company_project_idx": { + "name": "project_workspaces_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_primary_idx": { + "name": "project_workspaces_project_primary_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_primary", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_source_type_idx": { + "name": "project_workspaces_project_source_type_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_company_shared_key_idx": { + "name": "project_workspaces_company_shared_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "shared_workspace_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_remote_ref_idx": { + "name": "project_workspaces_project_remote_ref_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_workspace_ref", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_workspaces_company_id_companies_id_fk": { + "name": "project_workspaces_company_id_companies_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "project_workspaces_project_id_projects_id_fk": { + "name": "project_workspaces_project_id_projects_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "lead_agent_id": { + "name": "lead_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_date": { + "name": "target_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_policy": { + "name": "execution_workspace_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "projects_company_idx": { + "name": "projects_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "projects_company_id_companies_id_fk": { + "name": "projects_company_id_companies_id_fk", + "tableFrom": "projects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_goal_id_goals_id_fk": { + "name": "projects_goal_id_goals_id_fk", + "tableFrom": "projects", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_lead_agent_id_agents_id_fk": { + "name": "projects_lead_agent_id_agents_id_fk", + "tableFrom": "projects", + "tableTo": "agents", + "columnsFrom": [ + "lead_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_runs": { + "name": "routine_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger_id": { + "name": "trigger_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "triggered_at": { + "name": "triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_payload": { + "name": "trigger_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "linked_issue_id": { + "name": "linked_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "coalesced_into_run_id": { + "name": "coalesced_into_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_runs_company_routine_idx": { + "name": "routine_runs_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idx": { + "name": "routine_runs_trigger_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_linked_issue_idx": { + "name": "routine_runs_linked_issue_idx", + "columns": [ + { + "expression": "linked_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idempotency_idx": { + "name": "routine_runs_trigger_idempotency_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_runs_company_id_companies_id_fk": { + "name": "routine_runs_company_id_companies_id_fk", + "tableFrom": "routine_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_routine_id_routines_id_fk": { + "name": "routine_runs_routine_id_routines_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_trigger_id_routine_triggers_id_fk": { + "name": "routine_runs_trigger_id_routine_triggers_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routine_triggers", + "columnsFrom": [ + "trigger_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_runs_linked_issue_id_issues_id_fk": { + "name": "routine_runs_linked_issue_id_issues_id_fk", + "tableFrom": "routine_runs", + "tableTo": "issues", + "columnsFrom": [ + "linked_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_triggers": { + "name": "routine_triggers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "public_id": { + "name": "public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "signing_mode": { + "name": "signing_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "replay_window_sec": { + "name": "replay_window_sec", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_rotated_at": { + "name": "last_rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_result": { + "name": "last_result", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_triggers_company_routine_idx": { + "name": "routine_triggers_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_company_kind_idx": { + "name": "routine_triggers_company_kind_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_next_run_idx": { + "name": "routine_triggers_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_idx": { + "name": "routine_triggers_public_id_idx", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_uq": { + "name": "routine_triggers_public_id_uq", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_triggers_company_id_companies_id_fk": { + "name": "routine_triggers_company_id_companies_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_routine_id_routines_id_fk": { + "name": "routine_triggers_routine_id_routines_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_secret_id_company_secrets_id_fk": { + "name": "routine_triggers_secret_id_company_secrets_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_created_by_agent_id_agents_id_fk": { + "name": "routine_triggers_created_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_updated_by_agent_id_agents_id_fk": { + "name": "routine_triggers_updated_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routines": { + "name": "routines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_issue_id": { + "name": "parent_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "concurrency_policy": { + "name": "concurrency_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'coalesce_if_active'" + }, + "catch_up_policy": { + "name": "catch_up_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'skip_missed'" + }, + "variables": { + "name": "variables", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_triggered_at": { + "name": "last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_enqueued_at": { + "name": "last_enqueued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routines_company_status_idx": { + "name": "routines_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_assignee_idx": { + "name": "routines_company_assignee_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_project_idx": { + "name": "routines_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routines_company_id_companies_id_fk": { + "name": "routines_company_id_companies_id_fk", + "tableFrom": "routines", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_project_id_projects_id_fk": { + "name": "routines_project_id_projects_id_fk", + "tableFrom": "routines", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_goal_id_goals_id_fk": { + "name": "routines_goal_id_goals_id_fk", + "tableFrom": "routines", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_parent_issue_id_issues_id_fk": { + "name": "routines_parent_issue_id_issues_id_fk", + "tableFrom": "routines", + "tableTo": "issues", + "columnsFrom": [ + "parent_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_assignee_agent_id_agents_id_fk": { + "name": "routines_assignee_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "routines_created_by_agent_id_agents_id_fk": { + "name": "routines_created_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_updated_by_agent_id_agents_id_fk": { + "name": "routines_updated_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_operations": { + "name": "workspace_operations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_operations_company_run_started_idx": { + "name": "workspace_operations_company_run_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_operations_company_workspace_started_idx": { + "name": "workspace_operations_company_workspace_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_operations_company_id_companies_id_fk": { + "name": "workspace_operations_company_id_companies_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_operations_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_operations_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_runtime_services": { + "name": "workspace_runtime_services", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "service_name": { + "name": "service_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reuse_key": { + "name": "reuse_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "started_by_run_id": { + "name": "started_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stop_policy": { + "name": "stop_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_runtime_services_company_workspace_status_idx": { + "name": "workspace_runtime_services_company_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_execution_workspace_status_idx": { + "name": "workspace_runtime_services_company_execution_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_project_status_idx": { + "name": "workspace_runtime_services_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_run_idx": { + "name": "workspace_runtime_services_run_idx", + "columns": [ + { + "expression": "started_by_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_updated_idx": { + "name": "workspace_runtime_services_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_runtime_services_company_id_companies_id_fk": { + "name": "workspace_runtime_services_company_id_companies_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_id_projects_id_fk": { + "name": "workspace_runtime_services_project_id_projects_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk": { + "name": "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_issue_id_issues_id_fk": { + "name": "workspace_runtime_services_issue_id_issues_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_owner_agent_id_agents_id_fk": { + "name": "workspace_runtime_services_owner_agent_id_agents_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk": { + "name": "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "started_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/src/migrations/meta/_journal.json b/packages/db/src/migrations/meta/_journal.json index 60bacd3d5ea..61d03ee4156 100644 --- a/packages/db/src/migrations/meta/_journal.json +++ b/packages/db/src/migrations/meta/_journal.json @@ -323,6 +323,69 @@ "when": 1774530504348, "tag": "0045_workable_shockwave", "breakpoints": true + }, + { + "idx": 46, + "version": "7", + "when": 1774960197878, + "tag": "0046_smooth_sentinels", + "breakpoints": true + }, + { + "idx": 47, + "version": "7", + "when": 1775137972687, + "tag": "0047_overjoyed_groot", + "breakpoints": true + }, + { + "idx": 48, + "version": "7", + "when": 1775145655557, + "tag": "0048_flashy_marrow", + "breakpoints": true + }, + { + "idx": 49, + "version": "7", + "when": 1775349863293, + "tag": "0049_flawless_abomination", + "breakpoints": true + }, + { + "idx": 50, + "version": "7", + "when": 1775487782768, + "tag": "0050_stiff_luckman", + "breakpoints": true + }, + { + "idx": 51, + "version": "7", + "when": 1775524651831, + "tag": "0051_young_korg", + "breakpoints": true + }, + { + "idx": 52, + "version": "7", + "when": 1775571715162, + "tag": "0052_mushy_trauma", + "breakpoints": true + }, + { + "idx": 53, + "version": "7", + "when": 1775604018515, + "tag": "0053_sharp_wild_child", + "breakpoints": true + }, + { + "idx": 54, + "version": "7", + "when": 1775750400000, + "tag": "0054_draft_routines", + "breakpoints": true } ] -} \ No newline at end of file +} diff --git a/packages/db/src/schema/companies.ts b/packages/db/src/schema/companies.ts index 83d6e193e04..f8566d27bb5 100644 --- a/packages/db/src/schema/companies.ts +++ b/packages/db/src/schema/companies.ts @@ -16,6 +16,12 @@ export const companies = pgTable( requireBoardApprovalForNewAgents: boolean("require_board_approval_for_new_agents") .notNull() .default(true), + feedbackDataSharingEnabled: boolean("feedback_data_sharing_enabled") + .notNull() + .default(false), + feedbackDataSharingConsentAt: timestamp("feedback_data_sharing_consent_at", { withTimezone: true }), + feedbackDataSharingConsentByUserId: text("feedback_data_sharing_consent_by_user_id"), + feedbackDataSharingTermsVersion: text("feedback_data_sharing_terms_version"), brandColor: text("brand_color"), createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), diff --git a/packages/db/src/schema/document_revisions.ts b/packages/db/src/schema/document_revisions.ts index 6e739989052..cdc8a18f537 100644 --- a/packages/db/src/schema/document_revisions.ts +++ b/packages/db/src/schema/document_revisions.ts @@ -2,6 +2,7 @@ import { pgTable, uuid, text, integer, timestamp, index, uniqueIndex } from "dri import { companies } from "./companies.js"; import { agents } from "./agents.js"; import { documents } from "./documents.js"; +import { heartbeatRuns } from "./heartbeat_runs.js"; export const documentRevisions = pgTable( "document_revisions", @@ -10,10 +11,13 @@ export const documentRevisions = pgTable( companyId: uuid("company_id").notNull().references(() => companies.id), documentId: uuid("document_id").notNull().references(() => documents.id, { onDelete: "cascade" }), revisionNumber: integer("revision_number").notNull(), + title: text("title"), + format: text("format").notNull().default("markdown"), body: text("body").notNull(), changeSummary: text("change_summary"), createdByAgentId: uuid("created_by_agent_id").references(() => agents.id, { onDelete: "set null" }), createdByUserId: text("created_by_user_id"), + createdByRunId: uuid("created_by_run_id").references(() => heartbeatRuns.id, { onDelete: "set null" }), createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), }, (table) => ({ diff --git a/packages/db/src/schema/feedback_exports.ts b/packages/db/src/schema/feedback_exports.ts new file mode 100644 index 00000000000..e4b7f1744df --- /dev/null +++ b/packages/db/src/schema/feedback_exports.ts @@ -0,0 +1,45 @@ +import { index, integer, jsonb, pgTable, text, timestamp, uniqueIndex, uuid } from "drizzle-orm/pg-core"; +import { companies } from "./companies.js"; +import { feedbackVotes } from "./feedback_votes.js"; +import { issues } from "./issues.js"; +import { projects } from "./projects.js"; + +export const feedbackExports = pgTable( + "feedback_exports", + { + id: uuid("id").primaryKey().defaultRandom(), + companyId: uuid("company_id").notNull().references(() => companies.id), + feedbackVoteId: uuid("feedback_vote_id").notNull().references(() => feedbackVotes.id, { onDelete: "cascade" }), + issueId: uuid("issue_id").notNull().references(() => issues.id, { onDelete: "cascade" }), + projectId: uuid("project_id").references(() => projects.id, { onDelete: "set null" }), + authorUserId: text("author_user_id").notNull(), + targetType: text("target_type").notNull(), + targetId: text("target_id").notNull(), + vote: text("vote").notNull(), + status: text("status").notNull().default("local_only"), + destination: text("destination"), + exportId: text("export_id"), + consentVersion: text("consent_version"), + schemaVersion: text("schema_version").notNull().default("paperclip-feedback-envelope-v2"), + bundleVersion: text("bundle_version").notNull().default("paperclip-feedback-bundle-v2"), + payloadVersion: text("payload_version").notNull().default("paperclip-feedback-v1"), + payloadDigest: text("payload_digest"), + payloadSnapshot: jsonb("payload_snapshot"), + targetSummary: jsonb("target_summary").notNull(), + redactionSummary: jsonb("redaction_summary"), + attemptCount: integer("attempt_count").notNull().default(0), + lastAttemptedAt: timestamp("last_attempted_at", { withTimezone: true }), + exportedAt: timestamp("exported_at", { withTimezone: true }), + failureReason: text("failure_reason"), + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), + }, + (table) => ({ + voteUniqueIdx: uniqueIndex("feedback_exports_feedback_vote_idx").on(table.feedbackVoteId), + companyCreatedIdx: index("feedback_exports_company_created_idx").on(table.companyId, table.createdAt), + companyStatusIdx: index("feedback_exports_company_status_idx").on(table.companyId, table.status, table.createdAt), + companyIssueIdx: index("feedback_exports_company_issue_idx").on(table.companyId, table.issueId, table.createdAt), + companyProjectIdx: index("feedback_exports_company_project_idx").on(table.companyId, table.projectId, table.createdAt), + companyAuthorIdx: index("feedback_exports_company_author_idx").on(table.companyId, table.authorUserId, table.createdAt), + }), +); diff --git a/packages/db/src/schema/feedback_votes.ts b/packages/db/src/schema/feedback_votes.ts new file mode 100644 index 00000000000..c45f1650f3b --- /dev/null +++ b/packages/db/src/schema/feedback_votes.ts @@ -0,0 +1,34 @@ +import { boolean, index, jsonb, pgTable, text, timestamp, uniqueIndex, uuid } from "drizzle-orm/pg-core"; +import { companies } from "./companies.js"; +import { issues } from "./issues.js"; + +export const feedbackVotes = pgTable( + "feedback_votes", + { + id: uuid("id").primaryKey().defaultRandom(), + companyId: uuid("company_id").notNull().references(() => companies.id), + issueId: uuid("issue_id").notNull().references(() => issues.id), + targetType: text("target_type").notNull(), + targetId: text("target_id").notNull(), + authorUserId: text("author_user_id").notNull(), + vote: text("vote").notNull(), + reason: text("reason"), + sharedWithLabs: boolean("shared_with_labs").notNull().default(false), + sharedAt: timestamp("shared_at", { withTimezone: true }), + consentVersion: text("consent_version"), + redactionSummary: jsonb("redaction_summary"), + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), + }, + (table) => ({ + companyIssueIdx: index("feedback_votes_company_issue_idx").on(table.companyId, table.issueId), + issueTargetIdx: index("feedback_votes_issue_target_idx").on(table.issueId, table.targetType, table.targetId), + authorIdx: index("feedback_votes_author_idx").on(table.authorUserId, table.createdAt), + companyTargetAuthorUniqueIdx: uniqueIndex("feedback_votes_company_target_author_idx").on( + table.companyId, + table.targetType, + table.targetId, + table.authorUserId, + ), + }), +); diff --git a/packages/db/src/schema/heartbeat_runs.ts b/packages/db/src/schema/heartbeat_runs.ts index 58a1dcdbf14..610bcb47c52 100644 --- a/packages/db/src/schema/heartbeat_runs.ts +++ b/packages/db/src/schema/heartbeat_runs.ts @@ -37,6 +37,9 @@ export const heartbeatRuns = pgTable( onDelete: "set null", }), processLossRetryCount: integer("process_loss_retry_count").notNull().default(0), + issueCommentStatus: text("issue_comment_status").notNull().default("not_applicable"), + issueCommentSatisfiedByCommentId: uuid("issue_comment_satisfied_by_comment_id"), + issueCommentRetryQueuedAt: timestamp("issue_comment_retry_queued_at", { withTimezone: true }), contextSnapshot: jsonb("context_snapshot").$type>(), createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), diff --git a/packages/db/src/schema/inbox_dismissals.ts b/packages/db/src/schema/inbox_dismissals.ts new file mode 100644 index 00000000000..22996a476f3 --- /dev/null +++ b/packages/db/src/schema/inbox_dismissals.ts @@ -0,0 +1,24 @@ +import { pgTable, uuid, text, timestamp, index, uniqueIndex } from "drizzle-orm/pg-core"; +import { companies } from "./companies.js"; + +export const inboxDismissals = pgTable( + "inbox_dismissals", + { + id: uuid("id").primaryKey().defaultRandom(), + companyId: uuid("company_id").notNull().references(() => companies.id), + userId: text("user_id").notNull(), + itemKey: text("item_key").notNull(), + dismissedAt: timestamp("dismissed_at", { withTimezone: true }).notNull().defaultNow(), + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), + }, + (table) => ({ + companyUserIdx: index("inbox_dismissals_company_user_idx").on(table.companyId, table.userId), + companyItemIdx: index("inbox_dismissals_company_item_idx").on(table.companyId, table.itemKey), + companyUserItemUnique: uniqueIndex("inbox_dismissals_company_user_item_idx").on( + table.companyId, + table.userId, + table.itemKey, + ), + }), +); diff --git a/packages/db/src/schema/index.ts b/packages/db/src/schema/index.ts index fc387334336..1f86ca67158 100644 --- a/packages/db/src/schema/index.ts +++ b/packages/db/src/schema/index.ts @@ -25,13 +25,18 @@ export { workspaceRuntimeServices } from "./workspace_runtime_services.js"; export { projectGoals } from "./project_goals.js"; export { goals } from "./goals.js"; export { issues } from "./issues.js"; +export { issueRelations } from "./issue_relations.js"; export { routines, routineTriggers, routineRuns } from "./routines.js"; export { issueWorkProducts } from "./issue_work_products.js"; export { labels } from "./labels.js"; export { issueLabels } from "./issue_labels.js"; export { issueApprovals } from "./issue_approvals.js"; export { issueComments } from "./issue_comments.js"; +export { issueExecutionDecisions } from "./issue_execution_decisions.js"; export { issueInboxArchives } from "./issue_inbox_archives.js"; +export { inboxDismissals } from "./inbox_dismissals.js"; +export { feedbackVotes } from "./feedback_votes.js"; +export { feedbackExports } from "./feedback_exports.js"; export { issueReadStates } from "./issue_read_states.js"; export { assets } from "./assets.js"; export { issueAttachments } from "./issue_attachments.js"; diff --git a/packages/db/src/schema/issue_comments.ts b/packages/db/src/schema/issue_comments.ts index fcefb830d42..a431589a3e4 100644 --- a/packages/db/src/schema/issue_comments.ts +++ b/packages/db/src/schema/issue_comments.ts @@ -2,6 +2,7 @@ import { pgTable, uuid, text, timestamp, index } from "drizzle-orm/pg-core"; import { companies } from "./companies.js"; import { issues } from "./issues.js"; import { agents } from "./agents.js"; +import { heartbeatRuns } from "./heartbeat_runs.js"; export const issueComments = pgTable( "issue_comments", @@ -11,6 +12,7 @@ export const issueComments = pgTable( issueId: uuid("issue_id").notNull().references(() => issues.id), authorAgentId: uuid("author_agent_id").references(() => agents.id), authorUserId: text("author_user_id"), + createdByRunId: uuid("created_by_run_id").references(() => heartbeatRuns.id, { onDelete: "set null" }), body: text("body").notNull(), createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), @@ -29,5 +31,6 @@ export const issueComments = pgTable( table.issueId, table.createdAt, ), + bodySearchIdx: index("issue_comments_body_search_idx").using("gin", table.body.op("gin_trgm_ops")), }), ); diff --git a/packages/db/src/schema/issue_execution_decisions.ts b/packages/db/src/schema/issue_execution_decisions.ts new file mode 100644 index 00000000000..572146398f9 --- /dev/null +++ b/packages/db/src/schema/issue_execution_decisions.ts @@ -0,0 +1,27 @@ +import { index, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; +import { companies } from "./companies.js"; +import { issues } from "./issues.js"; +import { agents } from "./agents.js"; +import { heartbeatRuns } from "./heartbeat_runs.js"; + +export const issueExecutionDecisions = pgTable( + "issue_execution_decisions", + { + id: uuid("id").primaryKey().defaultRandom(), + companyId: uuid("company_id").notNull().references(() => companies.id), + issueId: uuid("issue_id").notNull().references(() => issues.id, { onDelete: "cascade" }), + stageId: uuid("stage_id").notNull(), + stageType: text("stage_type").notNull(), + actorAgentId: uuid("actor_agent_id").references(() => agents.id), + actorUserId: text("actor_user_id"), + outcome: text("outcome").notNull(), + body: text("body").notNull(), + createdByRunId: uuid("created_by_run_id").references(() => heartbeatRuns.id, { onDelete: "set null" }), + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), + }, + (table) => ({ + companyIssueIdx: index("issue_execution_decisions_company_issue_idx").on(table.companyId, table.issueId), + stageIdx: index("issue_execution_decisions_stage_idx").on(table.issueId, table.stageId, table.createdAt), + }), +); diff --git a/packages/db/src/schema/issue_relations.ts b/packages/db/src/schema/issue_relations.ts new file mode 100644 index 00000000000..ee0150b1669 --- /dev/null +++ b/packages/db/src/schema/issue_relations.ts @@ -0,0 +1,30 @@ +import { index, pgTable, text, timestamp, uniqueIndex, uuid } from "drizzle-orm/pg-core"; +import { agents } from "./agents.js"; +import { companies } from "./companies.js"; +import { issues } from "./issues.js"; + +export const issueRelations = pgTable( + "issue_relations", + { + id: uuid("id").primaryKey().defaultRandom(), + companyId: uuid("company_id").notNull().references(() => companies.id), + issueId: uuid("issue_id").notNull().references(() => issues.id, { onDelete: "cascade" }), + relatedIssueId: uuid("related_issue_id").notNull().references(() => issues.id, { onDelete: "cascade" }), + type: text("type").$type<"blocks">().notNull(), + createdByAgentId: uuid("created_by_agent_id").references(() => agents.id, { onDelete: "set null" }), + createdByUserId: text("created_by_user_id"), + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), + }, + (table) => ({ + companyIssueIdx: index("issue_relations_company_issue_idx").on(table.companyId, table.issueId), + companyRelatedIssueIdx: index("issue_relations_company_related_issue_idx").on(table.companyId, table.relatedIssueId), + companyTypeIdx: index("issue_relations_company_type_idx").on(table.companyId, table.type), + companyEdgeUq: uniqueIndex("issue_relations_company_edge_uq").on( + table.companyId, + table.issueId, + table.relatedIssueId, + table.type, + ), + }), +); diff --git a/packages/db/src/schema/issues.ts b/packages/db/src/schema/issues.ts index e8663a25fcd..f32e292d301 100644 --- a/packages/db/src/schema/issues.ts +++ b/packages/db/src/schema/issues.ts @@ -47,6 +47,8 @@ export const issues = pgTable( requestDepth: integer("request_depth").notNull().default(0), billingCode: text("billing_code"), assigneeAdapterOverrides: jsonb("assignee_adapter_overrides").$type>(), + executionPolicy: jsonb("execution_policy").$type>(), + executionState: jsonb("execution_state").$type>(), executionWorkspaceId: uuid("execution_workspace_id") .references((): AnyPgColumn => executionWorkspaces.id, { onDelete: "set null" }), executionWorkspacePreference: text("execution_workspace_preference"), @@ -76,6 +78,9 @@ export const issues = pgTable( projectWorkspaceIdx: index("issues_company_project_workspace_idx").on(table.companyId, table.projectWorkspaceId), executionWorkspaceIdx: index("issues_company_execution_workspace_idx").on(table.companyId, table.executionWorkspaceId), identifierIdx: uniqueIndex("issues_identifier_idx").on(table.identifier), + titleSearchIdx: index("issues_title_search_idx").using("gin", table.title.op("gin_trgm_ops")), + identifierSearchIdx: index("issues_identifier_search_idx").using("gin", table.identifier.op("gin_trgm_ops")), + descriptionSearchIdx: index("issues_description_search_idx").using("gin", table.description.op("gin_trgm_ops")), openRoutineExecutionIdx: uniqueIndex("issues_open_routine_execution_uq") .on(table.companyId, table.originKind, table.originId) .where( diff --git a/packages/db/src/schema/projects.ts b/packages/db/src/schema/projects.ts index 50520a3b2be..0e6af757d65 100644 --- a/packages/db/src/schema/projects.ts +++ b/packages/db/src/schema/projects.ts @@ -1,4 +1,5 @@ import { pgTable, uuid, text, timestamp, date, index, jsonb } from "drizzle-orm/pg-core"; +import type { AgentEnvConfig } from "@paperclipai/shared"; import { companies } from "./companies.js"; import { goals } from "./goals.js"; import { agents } from "./agents.js"; @@ -15,6 +16,7 @@ export const projects = pgTable( leadAgentId: uuid("lead_agent_id").references(() => agents.id), targetDate: date("target_date"), color: text("color"), + env: jsonb("env").$type(), pauseReason: text("pause_reason"), pausedAt: timestamp("paused_at", { withTimezone: true }), executionWorkspacePolicy: jsonb("execution_workspace_policy").$type>(), diff --git a/packages/db/src/schema/routines.ts b/packages/db/src/schema/routines.ts index a6713f4bff8..211ce0b4fe8 100644 --- a/packages/db/src/schema/routines.ts +++ b/packages/db/src/schema/routines.ts @@ -15,22 +15,24 @@ import { companySecrets } from "./company_secrets.js"; import { issues } from "./issues.js"; import { projects } from "./projects.js"; import { goals } from "./goals.js"; +import type { RoutineVariable } from "@paperclipai/shared"; export const routines = pgTable( "routines", { id: uuid("id").primaryKey().defaultRandom(), companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }), - projectId: uuid("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }), + projectId: uuid("project_id").references(() => projects.id, { onDelete: "cascade" }), goalId: uuid("goal_id").references(() => goals.id, { onDelete: "set null" }), parentIssueId: uuid("parent_issue_id").references(() => issues.id, { onDelete: "set null" }), title: text("title").notNull(), description: text("description"), - assigneeAgentId: uuid("assignee_agent_id").notNull().references(() => agents.id), + assigneeAgentId: uuid("assignee_agent_id").references(() => agents.id), priority: text("priority").notNull().default("medium"), status: text("status").notNull().default("active"), concurrencyPolicy: text("concurrency_policy").notNull().default("coalesce_if_active"), catchUpPolicy: text("catch_up_policy").notNull().default("skip_missed"), + variables: jsonb("variables").$type().notNull().default([]), createdByAgentId: uuid("created_by_agent_id").references(() => agents.id, { onDelete: "set null" }), createdByUserId: text("created_by_user_id"), updatedByAgentId: uuid("updated_by_agent_id").references(() => agents.id, { onDelete: "set null" }), diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md new file mode 100644 index 00000000000..fc485f75c39 --- /dev/null +++ b/packages/mcp-server/README.md @@ -0,0 +1,77 @@ +# Paperclip MCP Server + +Model Context Protocol server for Paperclip. + +This package is a thin MCP wrapper over the existing Paperclip REST API. It does +not talk to the database directly and it does not reimplement business logic. + +## Authentication + +The server reads its configuration from environment variables: + +- `PAPERCLIP_API_URL` - Paperclip base URL, for example `http://localhost:3100` +- `PAPERCLIP_API_KEY` - bearer token used for `/api` requests +- `PAPERCLIP_COMPANY_ID` - optional default company for company-scoped tools +- `PAPERCLIP_AGENT_ID` - optional default agent for checkout helpers +- `PAPERCLIP_RUN_ID` - optional run id forwarded on mutating requests + +## Usage + +```sh +npx -y @paperclipai/mcp-server +``` + +Or locally in this repo: + +```sh +pnpm --filter @paperclipai/mcp-server build +node packages/mcp-server/dist/stdio.js +``` + +## Tool Surface + +Read tools: + +- `paperclipMe` +- `paperclipInboxLite` +- `paperclipListAgents` +- `paperclipGetAgent` +- `paperclipListIssues` +- `paperclipGetIssue` +- `paperclipGetHeartbeatContext` +- `paperclipListComments` +- `paperclipGetComment` +- `paperclipListIssueApprovals` +- `paperclipListDocuments` +- `paperclipGetDocument` +- `paperclipListDocumentRevisions` +- `paperclipListProjects` +- `paperclipGetProject` +- `paperclipListGoals` +- `paperclipGetGoal` +- `paperclipListApprovals` +- `paperclipGetApproval` +- `paperclipGetApprovalIssues` +- `paperclipListApprovalComments` + +Write tools: + +- `paperclipCreateIssue` +- `paperclipUpdateIssue` +- `paperclipCheckoutIssue` +- `paperclipReleaseIssue` +- `paperclipAddComment` +- `paperclipUpsertIssueDocument` +- `paperclipRestoreIssueDocumentRevision` +- `paperclipCreateApproval` +- `paperclipLinkIssueApproval` +- `paperclipUnlinkIssueApproval` +- `paperclipApprovalDecision` +- `paperclipAddApprovalComment` + +Escape hatch: + +- `paperclipApiRequest` + +`paperclipApiRequest` is limited to paths under `/api` and JSON bodies. It is +meant for endpoints that do not yet have a dedicated MCP tool. diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json new file mode 100644 index 00000000000..6edba34dc00 --- /dev/null +++ b/packages/mcp-server/package.json @@ -0,0 +1,55 @@ +{ + "name": "@paperclipai/mcp-server", + "version": "0.1.0", + "license": "MIT", + "homepage": "https://github.com/paperclipai/paperclip", + "bugs": { + "url": "https://github.com/paperclipai/paperclip/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/paperclipai/paperclip", + "directory": "packages/mcp-server" + }, + "type": "module", + "bin": { + "paperclip-mcp-server": "./dist/stdio.js" + }, + "exports": { + ".": "./src/index.ts" + }, + "publishConfig": { + "access": "public", + "bin": { + "paperclip-mcp-server": "./dist/stdio.js" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts" + }, + "files": [ + "dist", + "README.md" + ], + "scripts": { + "build": "tsc", + "clean": "rm -rf dist", + "typecheck": "tsc --noEmit", + "test": "vitest run" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.29.0", + "@paperclipai/shared": "workspace:*", + "zod": "^3.24.2" + }, + "devDependencies": { + "@types/node": "^24.6.0", + "typescript": "^5.7.3", + "vitest": "^3.0.5" + } +} diff --git a/packages/mcp-server/src/client.ts b/packages/mcp-server/src/client.ts new file mode 100644 index 00000000000..a2fd8e356b3 --- /dev/null +++ b/packages/mcp-server/src/client.ts @@ -0,0 +1,114 @@ +import type { PaperclipMcpConfig } from "./config.js"; + +export class PaperclipApiError extends Error { + readonly status: number; + readonly method: string; + readonly path: string; + readonly body: unknown; + + constructor(input: { + status: number; + method: string; + path: string; + body: unknown; + message: string; + }) { + super(input.message); + this.name = "PaperclipApiError"; + this.status = input.status; + this.method = input.method; + this.path = input.path; + this.body = input.body; + } +} + +export interface JsonRequestOptions { + body?: unknown; + includeRunId?: boolean; +} + +function isWriteMethod(method: string): boolean { + return !["GET", "HEAD"].includes(method.toUpperCase()); +} + +function buildErrorMessage(method: string, path: string, status: number, body: unknown): string { + if (body && typeof body === "object" && "error" in body && typeof body.error === "string") { + return `${method} ${path} failed with ${status}: ${body.error}`; + } + return `${method} ${path} failed with ${status}`; +} + +async function parseResponseBody(response: Response): Promise { + const text = await response.text(); + if (!text) return null; + try { + return JSON.parse(text) as unknown; + } catch { + return text; + } +} + +export class PaperclipApiClient { + constructor(private readonly config: PaperclipMcpConfig) {} + + get defaults() { + return { + companyId: this.config.companyId, + agentId: this.config.agentId, + runId: this.config.runId, + }; + } + + resolveCompanyId(companyId?: string | null): string { + const resolved = companyId?.trim() || this.config.companyId; + if (!resolved) { + throw new Error("companyId is required because PAPERCLIP_COMPANY_ID is not set"); + } + return resolved; + } + + resolveAgentId(agentId?: string | null): string { + const resolved = agentId?.trim() || this.config.agentId; + if (!resolved) { + throw new Error("agentId is required because PAPERCLIP_AGENT_ID is not set"); + } + return resolved; + } + + async requestJson(method: string, path: string, options: JsonRequestOptions = {}): Promise { + if (!path.startsWith("/")) { + throw new Error(`API path must start with "/": ${path}`); + } + + const url = new URL(path.slice(1), `${this.config.apiUrl}/`); + const headers: Record = { + Authorization: `Bearer ${this.config.apiKey}`, + Accept: "application/json", + }; + if (options.body !== undefined) { + headers["Content-Type"] = "application/json"; + } + if ((options.includeRunId ?? isWriteMethod(method)) && this.config.runId) { + headers["X-Paperclip-Run-Id"] = this.config.runId; + } + + const response = await fetch(url, { + method, + headers, + body: options.body === undefined ? undefined : JSON.stringify(options.body), + }); + const parsedBody = await parseResponseBody(response); + + if (!response.ok) { + throw new PaperclipApiError({ + status: response.status, + method: method.toUpperCase(), + path, + body: parsedBody, + message: buildErrorMessage(method.toUpperCase(), path, response.status, parsedBody), + }); + } + + return parsedBody as T; + } +} diff --git a/packages/mcp-server/src/config.ts b/packages/mcp-server/src/config.ts new file mode 100644 index 00000000000..c1de29eb4fc --- /dev/null +++ b/packages/mcp-server/src/config.ts @@ -0,0 +1,39 @@ +export interface PaperclipMcpConfig { + apiUrl: string; + apiKey: string; + companyId: string | null; + agentId: string | null; + runId: string | null; +} + +function nonEmpty(value: string | undefined): string | null { + return typeof value === "string" && value.trim().length > 0 ? value.trim() : null; +} + +function stripTrailingSlash(value: string): string { + return value.replace(/\/+$/, ""); +} + +export function normalizeApiUrl(apiUrl: string): string { + const trimmed = stripTrailingSlash(apiUrl.trim()); + return trimmed.endsWith("/api") ? trimmed : `${trimmed}/api`; +} + +export function readConfigFromEnv(env: NodeJS.ProcessEnv = process.env): PaperclipMcpConfig { + const apiUrl = nonEmpty(env.PAPERCLIP_API_URL); + if (!apiUrl) { + throw new Error("Missing PAPERCLIP_API_URL"); + } + const apiKey = nonEmpty(env.PAPERCLIP_API_KEY); + if (!apiKey) { + throw new Error("Missing PAPERCLIP_API_KEY"); + } + + return { + apiUrl: normalizeApiUrl(apiUrl), + apiKey, + companyId: nonEmpty(env.PAPERCLIP_COMPANY_ID), + agentId: nonEmpty(env.PAPERCLIP_AGENT_ID), + runId: nonEmpty(env.PAPERCLIP_RUN_ID), + }; +} diff --git a/packages/mcp-server/src/format.ts b/packages/mcp-server/src/format.ts new file mode 100644 index 00000000000..b2e6a4cd256 --- /dev/null +++ b/packages/mcp-server/src/format.ts @@ -0,0 +1,31 @@ +import { PaperclipApiError } from "./client.js"; + +type McpTextResponse = { + content: Array<{ type: "text"; text: string }>; +}; + +export function formatTextResponse(value: unknown): McpTextResponse { + return { + content: [ + { + type: "text", + text: typeof value === "string" ? value : JSON.stringify(value, null, 2), + }, + ], + }; +} + +export function formatErrorResponse(error: unknown): McpTextResponse { + if (error instanceof PaperclipApiError) { + return formatTextResponse({ + error: error.message, + status: error.status, + method: error.method, + path: error.path, + body: error.body, + }); + } + return formatTextResponse({ + error: error instanceof Error ? error.message : String(error), + }); +} diff --git a/packages/mcp-server/src/index.ts b/packages/mcp-server/src/index.ts new file mode 100644 index 00000000000..dc9fdc5cd7d --- /dev/null +++ b/packages/mcp-server/src/index.ts @@ -0,0 +1,30 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { PaperclipApiClient } from "./client.js"; +import { readConfigFromEnv, type PaperclipMcpConfig } from "./config.js"; +import { createToolDefinitions } from "./tools.js"; + +export function createPaperclipMcpServer(config: PaperclipMcpConfig = readConfigFromEnv()) { + const server = new McpServer({ + name: "paperclip", + version: "0.1.0", + }); + + const client = new PaperclipApiClient(config); + const tools = createToolDefinitions(client); + for (const tool of tools) { + server.tool(tool.name, tool.description, tool.schema.shape, tool.execute); + } + + return { + server, + tools, + client, + }; +} + +export async function runServer(config: PaperclipMcpConfig = readConfigFromEnv()) { + const { server } = createPaperclipMcpServer(config); + const transport = new StdioServerTransport(); + await server.connect(transport); +} diff --git a/packages/mcp-server/src/stdio.ts b/packages/mcp-server/src/stdio.ts new file mode 100644 index 00000000000..145b2362855 --- /dev/null +++ b/packages/mcp-server/src/stdio.ts @@ -0,0 +1,7 @@ +#!/usr/bin/env node +import { runServer } from "./index.js"; + +void runServer().catch((error) => { + console.error("Failed to start Paperclip MCP server:", error); + process.exit(1); +}); diff --git a/packages/mcp-server/src/tools.test.ts b/packages/mcp-server/src/tools.test.ts new file mode 100644 index 00000000000..d47347f592d --- /dev/null +++ b/packages/mcp-server/src/tools.test.ts @@ -0,0 +1,159 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { PaperclipApiClient } from "./client.js"; +import { createToolDefinitions } from "./tools.js"; + +function makeClient() { + return new PaperclipApiClient({ + apiUrl: "http://localhost:3100/api", + apiKey: "token-123", + companyId: "11111111-1111-1111-1111-111111111111", + agentId: "22222222-2222-2222-2222-222222222222", + runId: "33333333-3333-3333-3333-333333333333", + }); +} + +function getTool(name: string) { + const tool = createToolDefinitions(makeClient()).find((candidate) => candidate.name === name); + if (!tool) throw new Error(`Missing tool ${name}`); + return tool; +} + +function mockJsonResponse(body: unknown, status = 200) { + return new Response(JSON.stringify(body), { + status, + headers: { "Content-Type": "application/json" }, + }); +} + +describe("paperclip MCP tools", () => { + beforeEach(() => { + vi.restoreAllMocks(); + }); + + it("adds auth headers and run id to mutating requests", async () => { + const fetchMock = vi.fn().mockResolvedValue( + mockJsonResponse({ ok: true }), + ); + vi.stubGlobal("fetch", fetchMock); + + const tool = getTool("paperclipUpdateIssue"); + await tool.execute({ + issueId: "PAP-1135", + status: "done", + }); + + expect(fetchMock).toHaveBeenCalledTimes(1); + const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit]; + expect(String(url)).toBe("http://localhost:3100/api/issues/PAP-1135"); + expect(init.method).toBe("PATCH"); + expect((init.headers as Record)["Authorization"]).toBe("Bearer token-123"); + expect((init.headers as Record)["X-Paperclip-Run-Id"]).toBe( + "33333333-3333-3333-3333-333333333333", + ); + }); + + it("uses default company id for company-scoped list tools", async () => { + const fetchMock = vi.fn().mockResolvedValue( + mockJsonResponse([{ id: "issue-1" }]), + ); + vi.stubGlobal("fetch", fetchMock); + + const tool = getTool("paperclipListIssues"); + const response = await tool.execute({}); + + expect(fetchMock).toHaveBeenCalledTimes(1); + const [url] = fetchMock.mock.calls[0] as [string]; + expect(String(url)).toBe( + "http://localhost:3100/api/companies/11111111-1111-1111-1111-111111111111/issues", + ); + expect(response.content[0]?.text).toContain("issue-1"); + }); + + it("uses default agent id for checkout requests", async () => { + const fetchMock = vi.fn().mockResolvedValue( + mockJsonResponse({ id: "PAP-1135", status: "in_progress" }), + ); + vi.stubGlobal("fetch", fetchMock); + + const tool = getTool("paperclipCheckoutIssue"); + await tool.execute({ + issueId: "PAP-1135", + }); + + const [, init] = fetchMock.mock.calls[0] as [string, RequestInit]; + expect(JSON.parse(String(init.body))).toEqual({ + agentId: "22222222-2222-2222-2222-222222222222", + expectedStatuses: ["todo", "backlog", "blocked"], + }); + }); + + it("defaults issue document format to markdown", async () => { + const fetchMock = vi.fn().mockResolvedValue( + mockJsonResponse({ key: "plan", latestRevisionNumber: 2 }), + ); + vi.stubGlobal("fetch", fetchMock); + + const tool = getTool("paperclipUpsertIssueDocument"); + await tool.execute({ + issueId: "PAP-1135", + key: "plan", + body: "# Updated", + }); + + const [, init] = fetchMock.mock.calls[0] as [string, RequestInit]; + expect(JSON.parse(String(init.body))).toEqual({ + format: "markdown", + body: "# Updated", + }); + }); + + it("creates approvals with the expected company-scoped payload", async () => { + const fetchMock = vi.fn().mockResolvedValue( + mockJsonResponse({ id: "approval-1" }), + ); + vi.stubGlobal("fetch", fetchMock); + + const tool = getTool("paperclipCreateApproval"); + await tool.execute({ + type: "hire_agent", + payload: { branch: "pap-1167" }, + issueIds: ["44444444-4444-4444-4444-444444444444"], + }); + + expect(fetchMock).toHaveBeenCalledTimes(1); + const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit]; + expect(String(url)).toBe( + "http://localhost:3100/api/companies/11111111-1111-1111-1111-111111111111/approvals", + ); + expect(init.method).toBe("POST"); + expect(JSON.parse(String(init.body))).toEqual({ + type: "hire_agent", + payload: { branch: "pap-1167" }, + issueIds: ["44444444-4444-4444-4444-444444444444"], + }); + }); + + it("rejects invalid generic request paths", async () => { + vi.stubGlobal("fetch", vi.fn()); + + const tool = getTool("paperclipApiRequest"); + const response = await tool.execute({ + method: "GET", + path: "issues", + }); + + expect(response.content[0]?.text).toContain("path must start with /"); + }); + + it("rejects generic request paths that escape /api", async () => { + vi.stubGlobal("fetch", vi.fn()); + + const tool = getTool("paperclipApiRequest"); + const response = await tool.execute({ + method: "GET", + path: "/../../secret", + }); + + expect(response.content[0]?.text).toContain("must not contain '..'"); + }); +}); diff --git a/packages/mcp-server/src/tools.ts b/packages/mcp-server/src/tools.ts new file mode 100644 index 00000000000..b5c18dba3e8 --- /dev/null +++ b/packages/mcp-server/src/tools.ts @@ -0,0 +1,427 @@ +import { z } from "zod"; +import { + addIssueCommentSchema, + checkoutIssueSchema, + createApprovalSchema, + createIssueSchema, + updateIssueSchema, + upsertIssueDocumentSchema, + linkIssueApprovalSchema, +} from "@paperclipai/shared"; +import { PaperclipApiClient } from "./client.js"; +import { formatErrorResponse, formatTextResponse } from "./format.js"; + +export interface ToolDefinition { + name: string; + description: string; + schema: z.AnyZodObject; + execute: (input: Record) => Promise<{ + content: Array<{ type: "text"; text: string }>; + }>; +} + +function makeTool( + name: string, + description: string, + schema: z.ZodObject, + execute: (input: z.infer) => Promise, +): ToolDefinition { + return { + name, + description, + schema, + execute: async (input) => { + try { + const parsed = schema.parse(input); + return formatTextResponse(await execute(parsed)); + } catch (error) { + return formatErrorResponse(error); + } + }, + }; +} + +function parseOptionalJson(raw: string | undefined | null): unknown { + if (!raw || raw.trim().length === 0) return undefined; + return JSON.parse(raw); +} + +const companyIdOptional = z.string().uuid().optional().nullable(); +const agentIdOptional = z.string().uuid().optional().nullable(); +const issueIdSchema = z.string().min(1); +const projectIdSchema = z.string().min(1); +const goalIdSchema = z.string().uuid(); +const approvalIdSchema = z.string().uuid(); +const documentKeySchema = z.string().trim().min(1).max(64); + +const listIssuesSchema = z.object({ + companyId: companyIdOptional, + status: z.string().optional(), + projectId: z.string().uuid().optional(), + assigneeAgentId: z.string().uuid().optional(), + participantAgentId: z.string().uuid().optional(), + assigneeUserId: z.string().optional(), + touchedByUserId: z.string().optional(), + inboxArchivedByUserId: z.string().optional(), + unreadForUserId: z.string().optional(), + labelId: z.string().uuid().optional(), + executionWorkspaceId: z.string().uuid().optional(), + originKind: z.string().optional(), + originId: z.string().optional(), + includeRoutineExecutions: z.boolean().optional(), + q: z.string().optional(), +}); + +const listCommentsSchema = z.object({ + issueId: issueIdSchema, + after: z.string().uuid().optional(), + order: z.enum(["asc", "desc"]).optional(), + limit: z.number().int().positive().max(500).optional(), +}); + +const upsertDocumentToolSchema = z.object({ + issueId: issueIdSchema, + key: documentKeySchema, + title: z.string().trim().max(200).nullable().optional(), + format: z.enum(["markdown"]).default("markdown"), + body: z.string().max(524288), + changeSummary: z.string().trim().max(500).nullable().optional(), + baseRevisionId: z.string().uuid().nullable().optional(), +}); + +const createIssueToolSchema = z.object({ + companyId: companyIdOptional, +}).merge(createIssueSchema); + +const updateIssueToolSchema = z.object({ + issueId: issueIdSchema, +}).merge(updateIssueSchema); + +const checkoutIssueToolSchema = z.object({ + issueId: issueIdSchema, + agentId: agentIdOptional, + expectedStatuses: checkoutIssueSchema.shape.expectedStatuses.optional(), +}); + +const addCommentToolSchema = z.object({ + issueId: issueIdSchema, +}).merge(addIssueCommentSchema); + +const approvalDecisionSchema = z.object({ + approvalId: approvalIdSchema, + action: z.enum(["approve", "reject", "requestRevision", "resubmit"]), + decisionNote: z.string().optional(), + payloadJson: z.string().optional(), +}); + +const createApprovalToolSchema = z.object({ + companyId: companyIdOptional, +}).merge(createApprovalSchema); + +const apiRequestSchema = z.object({ + method: z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]), + path: z.string().min(1), + jsonBody: z.string().optional(), +}); + +export function createToolDefinitions(client: PaperclipApiClient): ToolDefinition[] { + return [ + makeTool( + "paperclipMe", + "Get the current authenticated Paperclip actor details", + z.object({}), + async () => client.requestJson("GET", "/agents/me"), + ), + makeTool( + "paperclipInboxLite", + "Get the current authenticated agent inbox-lite assignment list", + z.object({}), + async () => client.requestJson("GET", "/agents/me/inbox-lite"), + ), + makeTool( + "paperclipListAgents", + "List agents in a company", + z.object({ companyId: companyIdOptional }), + async ({ companyId }) => client.requestJson("GET", `/companies/${client.resolveCompanyId(companyId)}/agents`), + ), + makeTool( + "paperclipGetAgent", + "Get a single agent by id", + z.object({ agentId: z.string().min(1), companyId: companyIdOptional }), + async ({ agentId, companyId }) => { + const qs = companyId ? `?companyId=${encodeURIComponent(companyId)}` : ""; + return client.requestJson("GET", `/agents/${encodeURIComponent(agentId)}${qs}`); + }, + ), + makeTool( + "paperclipListIssues", + "List issues for a company with optional filters", + listIssuesSchema, + async (input) => { + const companyId = client.resolveCompanyId(input.companyId); + const params = new URLSearchParams(); + for (const [key, value] of Object.entries(input)) { + if (key === "companyId" || value === undefined || value === null) continue; + params.set(key, String(value)); + } + const qs = params.toString(); + return client.requestJson("GET", `/companies/${companyId}/issues${qs ? `?${qs}` : ""}`); + }, + ), + makeTool( + "paperclipGetIssue", + "Get a single issue by UUID or identifier", + z.object({ issueId: issueIdSchema }), + async ({ issueId }) => client.requestJson("GET", `/issues/${encodeURIComponent(issueId)}`), + ), + makeTool( + "paperclipGetHeartbeatContext", + "Get compact heartbeat context for an issue", + z.object({ issueId: issueIdSchema, wakeCommentId: z.string().uuid().optional() }), + async ({ issueId, wakeCommentId }) => { + const qs = wakeCommentId ? `?wakeCommentId=${encodeURIComponent(wakeCommentId)}` : ""; + return client.requestJson("GET", `/issues/${encodeURIComponent(issueId)}/heartbeat-context${qs}`); + }, + ), + makeTool( + "paperclipListComments", + "List issue comments with incremental options", + listCommentsSchema, + async ({ issueId, after, order, limit }) => { + const params = new URLSearchParams(); + if (after) params.set("after", after); + if (order) params.set("order", order); + if (limit) params.set("limit", String(limit)); + const qs = params.toString(); + return client.requestJson("GET", `/issues/${encodeURIComponent(issueId)}/comments${qs ? `?${qs}` : ""}`); + }, + ), + makeTool( + "paperclipGetComment", + "Get a specific issue comment by id", + z.object({ issueId: issueIdSchema, commentId: z.string().uuid() }), + async ({ issueId, commentId }) => + client.requestJson("GET", `/issues/${encodeURIComponent(issueId)}/comments/${encodeURIComponent(commentId)}`), + ), + makeTool( + "paperclipListIssueApprovals", + "List approvals linked to an issue", + z.object({ issueId: issueIdSchema }), + async ({ issueId }) => client.requestJson("GET", `/issues/${encodeURIComponent(issueId)}/approvals`), + ), + makeTool( + "paperclipListDocuments", + "List issue documents", + z.object({ issueId: issueIdSchema }), + async ({ issueId }) => client.requestJson("GET", `/issues/${encodeURIComponent(issueId)}/documents`), + ), + makeTool( + "paperclipGetDocument", + "Get one issue document by key", + z.object({ issueId: issueIdSchema, key: documentKeySchema }), + async ({ issueId, key }) => + client.requestJson("GET", `/issues/${encodeURIComponent(issueId)}/documents/${encodeURIComponent(key)}`), + ), + makeTool( + "paperclipListDocumentRevisions", + "List revisions for an issue document", + z.object({ issueId: issueIdSchema, key: documentKeySchema }), + async ({ issueId, key }) => + client.requestJson( + "GET", + `/issues/${encodeURIComponent(issueId)}/documents/${encodeURIComponent(key)}/revisions`, + ), + ), + makeTool( + "paperclipListProjects", + "List projects in a company", + z.object({ companyId: companyIdOptional }), + async ({ companyId }) => client.requestJson("GET", `/companies/${client.resolveCompanyId(companyId)}/projects`), + ), + makeTool( + "paperclipGetProject", + "Get a project by id or company-scoped short reference", + z.object({ projectId: projectIdSchema, companyId: companyIdOptional }), + async ({ projectId, companyId }) => { + const qs = companyId ? `?companyId=${encodeURIComponent(companyId)}` : ""; + return client.requestJson("GET", `/projects/${encodeURIComponent(projectId)}${qs}`); + }, + ), + makeTool( + "paperclipListGoals", + "List goals in a company", + z.object({ companyId: companyIdOptional }), + async ({ companyId }) => client.requestJson("GET", `/companies/${client.resolveCompanyId(companyId)}/goals`), + ), + makeTool( + "paperclipGetGoal", + "Get a goal by id", + z.object({ goalId: goalIdSchema }), + async ({ goalId }) => client.requestJson("GET", `/goals/${encodeURIComponent(goalId)}`), + ), + makeTool( + "paperclipListApprovals", + "List approvals in a company", + z.object({ companyId: companyIdOptional, status: z.string().optional() }), + async ({ companyId, status }) => { + const qs = status ? `?status=${encodeURIComponent(status)}` : ""; + return client.requestJson("GET", `/companies/${client.resolveCompanyId(companyId)}/approvals${qs}`); + }, + ), + makeTool( + "paperclipCreateApproval", + "Create a board approval request, optionally linked to one or more issues", + createApprovalToolSchema, + async ({ companyId, ...body }) => + client.requestJson("POST", `/companies/${client.resolveCompanyId(companyId)}/approvals`, { + body, + }), + ), + makeTool( + "paperclipGetApproval", + "Get an approval by id", + z.object({ approvalId: approvalIdSchema }), + async ({ approvalId }) => client.requestJson("GET", `/approvals/${encodeURIComponent(approvalId)}`), + ), + makeTool( + "paperclipGetApprovalIssues", + "List issues linked to an approval", + z.object({ approvalId: approvalIdSchema }), + async ({ approvalId }) => client.requestJson("GET", `/approvals/${encodeURIComponent(approvalId)}/issues`), + ), + makeTool( + "paperclipListApprovalComments", + "List comments for an approval", + z.object({ approvalId: approvalIdSchema }), + async ({ approvalId }) => client.requestJson("GET", `/approvals/${encodeURIComponent(approvalId)}/comments`), + ), + makeTool( + "paperclipCreateIssue", + "Create a new issue", + createIssueToolSchema, + async ({ companyId, ...body }) => + client.requestJson("POST", `/companies/${client.resolveCompanyId(companyId)}/issues`, { body }), + ), + makeTool( + "paperclipUpdateIssue", + "Patch an issue, optionally including a comment", + updateIssueToolSchema, + async ({ issueId, ...body }) => + client.requestJson("PATCH", `/issues/${encodeURIComponent(issueId)}`, { body }), + ), + makeTool( + "paperclipCheckoutIssue", + "Checkout an issue for an agent", + checkoutIssueToolSchema, + async ({ issueId, agentId, expectedStatuses }) => + client.requestJson("POST", `/issues/${encodeURIComponent(issueId)}/checkout`, { + body: { + agentId: client.resolveAgentId(agentId), + expectedStatuses: expectedStatuses ?? ["todo", "backlog", "blocked"], + }, + }), + ), + makeTool( + "paperclipReleaseIssue", + "Release an issue checkout", + z.object({ issueId: issueIdSchema }), + async ({ issueId }) => client.requestJson("POST", `/issues/${encodeURIComponent(issueId)}/release`, { body: {} }), + ), + makeTool( + "paperclipAddComment", + "Add a comment to an issue", + addCommentToolSchema, + async ({ issueId, ...body }) => + client.requestJson("POST", `/issues/${encodeURIComponent(issueId)}/comments`, { body }), + ), + makeTool( + "paperclipUpsertIssueDocument", + "Create or update an issue document", + upsertDocumentToolSchema, + async ({ issueId, key, ...body }) => + client.requestJson( + "PUT", + `/issues/${encodeURIComponent(issueId)}/documents/${encodeURIComponent(key)}`, + { body }, + ), + ), + makeTool( + "paperclipRestoreIssueDocumentRevision", + "Restore a prior revision of an issue document", + z.object({ + issueId: issueIdSchema, + key: documentKeySchema, + revisionId: z.string().uuid(), + }), + async ({ issueId, key, revisionId }) => + client.requestJson( + "POST", + `/issues/${encodeURIComponent(issueId)}/documents/${encodeURIComponent(key)}/revisions/${encodeURIComponent(revisionId)}/restore`, + { body: {} }, + ), + ), + makeTool( + "paperclipLinkIssueApproval", + "Link an approval to an issue", + z.object({ issueId: issueIdSchema }).merge(linkIssueApprovalSchema), + async ({ issueId, approvalId }) => + client.requestJson("POST", `/issues/${encodeURIComponent(issueId)}/approvals`, { + body: { approvalId }, + }), + ), + makeTool( + "paperclipUnlinkIssueApproval", + "Unlink an approval from an issue", + z.object({ issueId: issueIdSchema, approvalId: approvalIdSchema }), + async ({ issueId, approvalId }) => + client.requestJson( + "DELETE", + `/issues/${encodeURIComponent(issueId)}/approvals/${encodeURIComponent(approvalId)}`, + ), + ), + makeTool( + "paperclipApprovalDecision", + "Approve, reject, request revision, or resubmit an approval", + approvalDecisionSchema, + async ({ approvalId, action, decisionNote, payloadJson }) => { + const path = + action === "approve" + ? `/approvals/${encodeURIComponent(approvalId)}/approve` + : action === "reject" + ? `/approvals/${encodeURIComponent(approvalId)}/reject` + : action === "requestRevision" + ? `/approvals/${encodeURIComponent(approvalId)}/request-revision` + : `/approvals/${encodeURIComponent(approvalId)}/resubmit`; + + const body = + action === "resubmit" + ? { payload: parseOptionalJson(payloadJson) ?? {} } + : { decisionNote }; + + return client.requestJson("POST", path, { body }); + }, + ), + makeTool( + "paperclipAddApprovalComment", + "Add a comment to an approval", + z.object({ approvalId: approvalIdSchema, body: z.string().min(1) }), + async ({ approvalId, body }) => + client.requestJson("POST", `/approvals/${encodeURIComponent(approvalId)}/comments`, { + body: { body }, + }), + ), + makeTool( + "paperclipApiRequest", + "Make a JSON request to an existing Paperclip /api endpoint for unsupported operations", + apiRequestSchema, + async ({ method, path, jsonBody }) => { + if (!path.startsWith("/") || path.includes("..")) { + throw new Error("path must start with / and be relative to /api, and must not contain '..'"); + } + return client.requestJson(method, path, { + body: parseOptionalJson(jsonBody), + }); + }, + ), + ]; +} diff --git a/packages/mcp-server/tsconfig.json b/packages/mcp-server/tsconfig.json new file mode 100644 index 00000000000..5a24989cd30 --- /dev/null +++ b/packages/mcp-server/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/packages/mcp-server/vitest.config.ts b/packages/mcp-server/vitest.config.ts new file mode 100644 index 00000000000..f624398e8de --- /dev/null +++ b/packages/mcp-server/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + }, +}); diff --git a/packages/plugins/examples/plugin-authoring-smoke-example/package.json b/packages/plugins/examples/plugin-authoring-smoke-example/package.json index 66657e4aa36..61b27ab9941 100644 --- a/packages/plugins/examples/plugin-authoring-smoke-example/package.json +++ b/packages/plugins/examples/plugin-authoring-smoke-example/package.json @@ -34,7 +34,7 @@ "@types/node": "^24.6.0", "@types/react": "^19.0.8", "esbuild": "^0.27.3", - "rollup": "^4.38.0", + "rollup": "^4.59.0", "tslib": "^2.8.1", "typescript": "^5.7.3", "vitest": "^3.0.5" diff --git a/packages/plugins/examples/plugin-kitchen-sink-example/src/manifest.ts b/packages/plugins/examples/plugin-kitchen-sink-example/src/manifest.ts index bb3215c2ebe..bcff32c2eed 100644 --- a/packages/plugins/examples/plugin-kitchen-sink-example/src/manifest.ts +++ b/packages/plugins/examples/plugin-kitchen-sink-example/src/manifest.ts @@ -41,6 +41,7 @@ const manifest: PaperclipPluginManifestV1 = { "goals.update", "activity.log.write", "metrics.write", + "telemetry.track", "plugin.state.read", "plugin.state.write", "events.subscribe", diff --git a/packages/plugins/examples/plugin-kitchen-sink-example/src/worker.ts b/packages/plugins/examples/plugin-kitchen-sink-example/src/worker.ts index a0bf2158fa3..a20b21deaf1 100644 --- a/packages/plugins/examples/plugin-kitchen-sink-example/src/worker.ts +++ b/packages/plugins/examples/plugin-kitchen-sink-example/src/worker.ts @@ -405,6 +405,16 @@ async function registerActionHandlers(ctx: PluginContext): Promise { data: { companyId }, }); await ctx.metrics.write("demo.events.emitted", 1, { source: "manual" }); + await ctx.telemetry.track("demo_event", { + source: "manual", + has_company: Boolean(companyId), + }); + pushRecord({ + level: "info", + source: "telemetry", + message: "Tracked plugin telemetry event demo_event", + data: { companyId }, + }); return { ok: true, message }; }); diff --git a/packages/plugins/sdk/README.md b/packages/plugins/sdk/README.md index 83724ee0ca5..d6424921fa3 100644 --- a/packages/plugins/sdk/README.md +++ b/packages/plugins/sdk/README.md @@ -312,6 +312,7 @@ Declare in `manifest.capabilities`. Grouped by scope: | | `issue.comments.create` | | | `activity.log.write` | | | `metrics.write` | +| | `telemetry.track` | | **Instance** | `instance.settings.register` | | | `plugin.state.read` | | | `plugin.state.write` | diff --git a/packages/plugins/sdk/src/host-client-factory.ts b/packages/plugins/sdk/src/host-client-factory.ts index c976fe8fef3..8b98cc87a36 100644 --- a/packages/plugins/sdk/src/host-client-factory.ts +++ b/packages/plugins/sdk/src/host-client-factory.ts @@ -135,6 +135,11 @@ export interface HostServices { write(params: WorkerToHostMethods["metrics.write"][0]): Promise; }; + /** Provides `telemetry.track`. */ + telemetry: { + track(params: WorkerToHostMethods["telemetry.track"][0]): Promise; + }; + /** Provides `log`. */ logger: { log(params: WorkerToHostMethods["log"][0]): Promise; @@ -284,6 +289,9 @@ const METHOD_CAPABILITY_MAP: Record { + return services.telemetry.track(params); + }), + // Logger "log": gated("log", async (params) => { return services.logger.log(params); diff --git a/packages/plugins/sdk/src/index.ts b/packages/plugins/sdk/src/index.ts index abe41e357e5..69e22c4f114 100644 --- a/packages/plugins/sdk/src/index.ts +++ b/packages/plugins/sdk/src/index.ts @@ -182,6 +182,7 @@ export type { PluginStreamsClient, PluginToolsClient, PluginMetricsClient, + PluginTelemetryClient, PluginLogger, } from "./types.js"; diff --git a/packages/plugins/sdk/src/protocol.ts b/packages/plugins/sdk/src/protocol.ts index a26bf5dc1ac..9d81eb8af7a 100644 --- a/packages/plugins/sdk/src/protocol.ts +++ b/packages/plugins/sdk/src/protocol.ts @@ -519,6 +519,12 @@ export interface WorkerToHostMethods { result: void, ]; + // Telemetry + "telemetry.track": [ + params: { eventName: string; dimensions?: Record }, + result: void, + ]; + // Logger "log": [ params: { level: "info" | "warn" | "error" | "debug"; message: string; meta?: Record }, @@ -600,7 +606,7 @@ export interface WorkerToHostMethods { result: IssueComment[], ]; "issues.createComment": [ - params: { issueId: string; body: string; companyId: string }, + params: { issueId: string; body: string; companyId: string; authorAgentId?: string }, result: IssueComment, ]; diff --git a/packages/plugins/sdk/src/testing.ts b/packages/plugins/sdk/src/testing.ts index d57dc7cce85..41e91d542f7 100644 --- a/packages/plugins/sdk/src/testing.ts +++ b/packages/plugins/sdk/src/testing.ts @@ -71,6 +71,7 @@ export interface TestHarness { logs: TestHarnessLogEntry[]; activity: Array<{ message: string; entityType?: string; entityId?: string; metadata?: Record }>; metrics: Array<{ name: string; value: number; tags?: Record }>; + telemetry: Array<{ eventName: string; dimensions?: Record }>; } type EventRegistration = { @@ -132,6 +133,7 @@ export function createTestHarness(options: TestHarnessOptions): TestHarness { const logs: TestHarnessLogEntry[] = []; const activity: TestHarness["activity"] = []; const metrics: TestHarness["metrics"] = []; + const telemetry: TestHarness["telemetry"] = []; const state = new Map(); const entities = new Map(); @@ -403,7 +405,7 @@ export function createTestHarness(options: TestHarnessOptions): TestHarness { if (!isInCompany(issues.get(issueId), companyId)) return []; return issueComments.get(issueId) ?? []; }, - async createComment(issueId, body, companyId) { + async createComment(issueId, body, companyId, options) { requireCapability(manifest, capabilitySet, "issue.comments.create"); const parentIssue = issues.get(issueId); if (!isInCompany(parentIssue, companyId)) { @@ -414,7 +416,7 @@ export function createTestHarness(options: TestHarnessOptions): TestHarness { id: randomUUID(), companyId: parentIssue.companyId, issueId, - authorAgentId: null, + authorAgentId: options?.authorAgentId ?? null, authorUserId: null, body, createdAt: now, @@ -631,6 +633,12 @@ export function createTestHarness(options: TestHarnessOptions): TestHarness { metrics.push({ name, value, tags }); }, }, + telemetry: { + async track(eventName, dimensions) { + requireCapability(manifest, capabilitySet, "telemetry.track"); + telemetry.push({ eventName, dimensions }); + }, + }, logger: { info(message, meta) { logs.push({ level: "info", message, meta }); @@ -729,6 +737,7 @@ export function createTestHarness(options: TestHarnessOptions): TestHarness { logs, activity, metrics, + telemetry, }; return harness; diff --git a/packages/plugins/sdk/src/types.ts b/packages/plugins/sdk/src/types.ts index 5182465106f..f8a6ca4f9f8 100644 --- a/packages/plugins/sdk/src/types.ts +++ b/packages/plugins/sdk/src/types.ts @@ -761,6 +761,28 @@ export interface PluginMetricsClient { write(name: string, value: number, tags?: Record): Promise; } +/** + * `ctx.telemetry` — emit plugin-scoped telemetry to the host's external + * telemetry pipeline. + * + * Requires `telemetry.track` capability. + */ +export interface PluginTelemetryClient { + /** + * Track a plugin telemetry event. + * + * The host prefixes the final event name as `plugin..` + * before forwarding it to the shared telemetry client. + * + * @param eventName - Bare plugin event slug (for example `"sync_completed"`) + * @param dimensions - Optional structured dimensions + */ + track( + eventName: string, + dimensions?: Record, + ): Promise; +} + /** * `ctx.companies` — read company metadata. * @@ -887,7 +909,12 @@ export interface PluginIssuesClient { companyId: string, ): Promise; listComments(issueId: string, companyId: string): Promise; - createComment(issueId: string, body: string, companyId: string): Promise; + createComment( + issueId: string, + body: string, + companyId: string, + options?: { authorAgentId?: string }, + ): Promise; /** Read and write issue documents. Requires `issue.documents.read` / `issue.documents.write`. */ documents: PluginIssueDocumentsClient; } @@ -1156,6 +1183,9 @@ export interface PluginContext { /** Write plugin metrics. Requires `metrics.write`. */ metrics: PluginMetricsClient; + /** Emit plugin-scoped external telemetry. Requires `telemetry.track`. */ + telemetry: PluginTelemetryClient; + /** Structured logger. Output is captured and surfaced in the plugin health dashboard. */ logger: PluginLogger; } diff --git a/packages/plugins/sdk/src/worker-rpc-host.ts b/packages/plugins/sdk/src/worker-rpc-host.ts index 20ca02fc96a..483dbc70824 100644 --- a/packages/plugins/sdk/src/worker-rpc-host.ts +++ b/packages/plugins/sdk/src/worker-rpc-host.ts @@ -610,8 +610,8 @@ export function startWorkerRpcHost(options: WorkerRpcHostOptions): WorkerRpcHost return callHost("issues.listComments", { issueId, companyId }); }, - async createComment(issueId: string, body: string, companyId: string) { - return callHost("issues.createComment", { issueId, body, companyId }); + async createComment(issueId: string, body: string, companyId: string, options?: { authorAgentId?: string }) { + return callHost("issues.createComment", { issueId, body, companyId, authorAgentId: options?.authorAgentId }); }, documents: { @@ -793,6 +793,15 @@ export function startWorkerRpcHost(options: WorkerRpcHostOptions): WorkerRpcHost }, }, + telemetry: { + async track( + eventName: string, + dimensions?: Record, + ): Promise { + await callHost("telemetry.track", { eventName, dimensions }); + }, + }, + logger: { info(message: string, meta?: Record): void { notifyHost("log", { level: "info", message, meta }); diff --git a/packages/shared/package.json b/packages/shared/package.json index 7aa086254ce..5e01b9e4827 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -14,6 +14,7 @@ "type": "module", "exports": { ".": "./src/index.ts", + "./telemetry": "./src/telemetry/index.ts", "./*": "./src/*.ts" }, "publishConfig": { @@ -23,6 +24,10 @@ "types": "./dist/index.d.ts", "import": "./dist/index.js" }, + "./telemetry": { + "types": "./dist/telemetry/index.d.ts", + "import": "./dist/telemetry/index.js" + }, "./*": { "types": "./dist/*.d.ts", "import": "./dist/*.js" diff --git a/packages/shared/src/adapter-type.ts b/packages/shared/src/adapter-type.ts new file mode 100644 index 00000000000..5af29dfc580 --- /dev/null +++ b/packages/shared/src/adapter-type.ts @@ -0,0 +1,15 @@ +import { z } from "zod"; +import { AGENT_ADAPTER_TYPES } from "./constants.js"; + +export const agentAdapterTypeSchema = z + .string() + .trim() + .min(1) + .default("process") + .describe(`Known built-in adapters: ${AGENT_ADAPTER_TYPES.join(", ")}. External adapters may register additional non-empty string types at runtime.`); + +export const optionalAgentAdapterTypeSchema = z + .string() + .trim() + .min(1) + .optional(); diff --git a/packages/shared/src/adapter-types.test.ts b/packages/shared/src/adapter-types.test.ts new file mode 100644 index 00000000000..29fb6eec9dd --- /dev/null +++ b/packages/shared/src/adapter-types.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it } from "vitest"; +import { acceptInviteSchema, createAgentSchema, updateAgentSchema } from "./index.js"; + +describe("dynamic adapter type validation schemas", () => { + it("accepts external adapter types in create/update agent schemas", () => { + expect( + createAgentSchema.parse({ + name: "External Agent", + adapterType: "external_adapter", + }).adapterType, + ).toBe("external_adapter"); + + expect( + updateAgentSchema.parse({ + adapterType: "external_adapter", + }).adapterType, + ).toBe("external_adapter"); + }); + + it("still rejects blank adapter types", () => { + expect(() => + createAgentSchema.parse({ + name: "Blank Adapter", + adapterType: " ", + }), + ).toThrow(); + }); + + it("accepts external adapter types in invite acceptance schema", () => { + expect( + acceptInviteSchema.parse({ + requestType: "agent", + agentName: "External Joiner", + adapterType: "external_adapter", + }).adapterType, + ).toBe("external_adapter"); + }); +}); diff --git a/packages/shared/src/config-schema.ts b/packages/shared/src/config-schema.ts index 258131bf2b3..48687878c9d 100644 --- a/packages/shared/src/config-schema.ts +++ b/packages/shared/src/config-schema.ts @@ -95,6 +95,10 @@ export const secretsConfigSchema = z.object({ }), }); +export const telemetryConfigSchema = z.object({ + enabled: z.boolean().default(true), +}).default({}); + export const paperclipConfigSchema = z .object({ $meta: configMetaSchema, @@ -102,6 +106,7 @@ export const paperclipConfigSchema = z database: databaseConfigSchema, logging: loggingConfigSchema, server: serverConfigSchema, + telemetry: telemetryConfigSchema, auth: authConfigSchema.default({ baseUrlMode: "auto", disableSignUp: false, @@ -174,5 +179,6 @@ export type StorageS3Config = z.infer; export type SecretsConfig = z.infer; export type SecretsLocalEncryptedConfig = z.infer; export type AuthConfig = z.infer; +export type TelemetryConfig = z.infer; export type ConfigMeta = z.infer; export type DatabaseBackupConfig = z.infer; diff --git a/packages/shared/src/constants.ts b/packages/shared/src/constants.ts index 2882eb307c5..03569a86ff4 100644 --- a/packages/shared/src/constants.ts +++ b/packages/shared/src/constants.ts @@ -26,13 +26,13 @@ export const AGENT_ADAPTER_TYPES = [ "http", "claude_local", "codex_local", + "gemini_local", "opencode_local", "pi_local", "cursor", "openclaw_gateway", - "hermes_local", ] as const; -export type AgentAdapterType = (typeof AGENT_ADAPTER_TYPES)[number]; +export type AgentAdapterType = (typeof AGENT_ADAPTER_TYPES)[number] | (string & {}); export const AGENT_ROLES = [ "ceo", @@ -135,6 +135,21 @@ export type IssuePriority = (typeof ISSUE_PRIORITIES)[number]; export const ISSUE_ORIGIN_KINDS = ["manual", "routine_execution"] as const; export type IssueOriginKind = (typeof ISSUE_ORIGIN_KINDS)[number]; +export const ISSUE_RELATION_TYPES = ["blocks"] as const; +export type IssueRelationType = (typeof ISSUE_RELATION_TYPES)[number]; + +export const ISSUE_EXECUTION_POLICY_MODES = ["normal", "auto"] as const; +export type IssueExecutionPolicyMode = (typeof ISSUE_EXECUTION_POLICY_MODES)[number]; + +export const ISSUE_EXECUTION_STAGE_TYPES = ["review", "approval"] as const; +export type IssueExecutionStageType = (typeof ISSUE_EXECUTION_STAGE_TYPES)[number]; + +export const ISSUE_EXECUTION_STATE_STATUSES = ["idle", "pending", "changes_requested", "completed"] as const; +export type IssueExecutionStateStatus = (typeof ISSUE_EXECUTION_STATE_STATUSES)[number]; + +export const ISSUE_EXECUTION_DECISION_OUTCOMES = ["approved", "changes_requested"] as const; +export type IssueExecutionDecisionOutcome = (typeof ISSUE_EXECUTION_DECISION_OUTCOMES)[number]; + export const GOAL_LEVELS = ["company", "team", "agent", "task"] as const; export type GoalLevel = (typeof GOAL_LEVELS)[number]; @@ -162,9 +177,12 @@ export type RoutineCatchUpPolicy = (typeof ROUTINE_CATCH_UP_POLICIES)[number]; export const ROUTINE_TRIGGER_KINDS = ["schedule", "webhook", "api"] as const; export type RoutineTriggerKind = (typeof ROUTINE_TRIGGER_KINDS)[number]; -export const ROUTINE_TRIGGER_SIGNING_MODES = ["bearer", "hmac_sha256"] as const; +export const ROUTINE_TRIGGER_SIGNING_MODES = ["bearer", "hmac_sha256", "github_hmac", "none"] as const; export type RoutineTriggerSigningMode = (typeof ROUTINE_TRIGGER_SIGNING_MODES)[number]; +export const ROUTINE_VARIABLE_TYPES = ["text", "textarea", "number", "boolean", "select"] as const; +export type RoutineVariableType = (typeof ROUTINE_VARIABLE_TYPES)[number]; + export const ROUTINE_RUN_STATUSES = [ "received", "coalesced", @@ -194,7 +212,12 @@ export const PROJECT_COLORS = [ "#3b82f6", // blue ] as const; -export const APPROVAL_TYPES = ["hire_agent", "approve_ceo_strategy", "budget_override_required"] as const; +export const APPROVAL_TYPES = [ + "hire_agent", + "approve_ceo_strategy", + "budget_override_required", + "request_board_approval", +] as const; export type ApprovalType = (typeof APPROVAL_TYPES)[number]; export const APPROVAL_STATUSES = [ @@ -471,6 +494,7 @@ export const PLUGIN_CAPABILITIES = [ "agent.sessions.close", "activity.log.write", "metrics.write", + "telemetry.track", // Plugin State "plugin.state.read", "plugin.state.write", diff --git a/packages/shared/src/execution-workspace-guards.ts b/packages/shared/src/execution-workspace-guards.ts new file mode 100644 index 00000000000..5428546ab16 --- /dev/null +++ b/packages/shared/src/execution-workspace-guards.ts @@ -0,0 +1,19 @@ +import type { ExecutionWorkspace } from "./types/workspace-runtime.js"; + +type ExecutionWorkspaceGuardTarget = Pick; + +const CLOSED_EXECUTION_WORKSPACE_STATUSES = new Set(["archived", "cleanup_failed"]); + +export function isClosedIsolatedExecutionWorkspace( + workspace: Pick | null | undefined, +): boolean { + if (!workspace) return false; + if (workspace.mode !== "isolated_workspace") return false; + return workspace.closedAt != null || CLOSED_EXECUTION_WORKSPACE_STATUSES.has(workspace.status); +} + +export function getClosedIsolatedExecutionWorkspaceMessage( + workspace: Pick, +): string { + return `This issue is linked to the closed workspace "${workspace.name}". Move it to an open workspace before adding comments or resuming work.`; +} diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index b0184167c2f..3e476a3f24b 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -1,3 +1,4 @@ +export { agentAdapterTypeSchema, optionalAgentAdapterTypeSchema } from "./adapter-type.js"; export { COMPANY_STATUSES, DEPLOYMENT_MODES, @@ -13,6 +14,11 @@ export { INBOX_MINE_ISSUE_STATUS_FILTER, ISSUE_PRIORITIES, ISSUE_ORIGIN_KINDS, + ISSUE_RELATION_TYPES, + ISSUE_EXECUTION_POLICY_MODES, + ISSUE_EXECUTION_STAGE_TYPES, + ISSUE_EXECUTION_STATE_STATUSES, + ISSUE_EXECUTION_DECISION_OUTCOMES, GOAL_LEVELS, GOAL_STATUSES, PROJECT_STATUSES, @@ -21,6 +27,7 @@ export { ROUTINE_CATCH_UP_POLICIES, ROUTINE_TRIGGER_KINDS, ROUTINE_TRIGGER_SIGNING_MODES, + ROUTINE_VARIABLE_TYPES, ROUTINE_RUN_STATUSES, ROUTINE_RUN_SOURCES, PAUSE_REASONS, @@ -83,6 +90,11 @@ export { type IssueStatus, type IssuePriority, type IssueOriginKind, + type IssueRelationType, + type IssueExecutionPolicyMode, + type IssueExecutionStageType, + type IssueExecutionStateStatus, + type IssueExecutionDecisionOutcome, type GoalLevel, type GoalStatus, type ProjectStatus, @@ -91,6 +103,7 @@ export { type RoutineCatchUpPolicy, type RoutineTriggerKind, type RoutineTriggerSigningMode, + type RoutineVariableType, type RoutineRunStatus, type RoutineRunSource, type PauseReason, @@ -143,6 +156,16 @@ export { export type { Company, + FeedbackVote, + FeedbackDataSharingPreference, + FeedbackTargetType, + FeedbackVoteValue, + FeedbackTrace, + FeedbackTraceStatus, + FeedbackTraceTargetSummary, + FeedbackTraceBundleCaptureStatus, + FeedbackTraceBundleFile, + FeedbackTraceBundle, CompanySkillSourceType, CompanySkillTrustLevel, CompanySkillCompatibility, @@ -221,6 +244,14 @@ export type { IssueWorkProductReviewState, Issue, IssueAssigneeAdapterOverrides, + IssueRelation, + IssueRelationIssueSummary, + IssueExecutionPolicy, + IssueExecutionState, + IssueExecutionStage, + IssueExecutionStageParticipant, + IssueExecutionStagePrincipal, + IssueExecutionDecision, IssueComment, IssueDocument, IssueDocumentSummary, @@ -250,6 +281,8 @@ export type { FinanceSummary, FinanceByBiller, FinanceByKind, + AgentWakeupResponse, + AgentWakeupSkipped, HeartbeatRun, HeartbeatRunEvent, AgentRuntimeState, @@ -260,6 +293,7 @@ export type { DashboardSummary, ActivityEvent, SidebarBadges, + InboxDismissal, CompanyMembership, PrincipalPermissionGrant, Invite, @@ -299,6 +333,8 @@ export type { CompanySecret, SecretProviderDescriptor, Routine, + RoutineVariable, + RoutineVariableDefaultValue, RoutineTrigger, RoutineRun, RoutineTriggerSecretMaterial, @@ -330,6 +366,20 @@ export type { ProviderQuotaResult, } from "./types/index.js"; +export { + DEFAULT_FEEDBACK_DATA_SHARING_PREFERENCE, + FEEDBACK_TARGET_TYPES, + FEEDBACK_DATA_SHARING_PREFERENCES, + FEEDBACK_TRACE_STATUSES, + FEEDBACK_VOTE_VALUES, + DEFAULT_FEEDBACK_DATA_SHARING_TERMS_VERSION, +} from "./types/feedback.js"; + +export { + getClosedIsolatedExecutionWorkspaceMessage, + isClosedIsolatedExecutionWorkspace, +} from "./execution-workspace-guards.js"; + export { instanceGeneralSettingsSchema, patchInstanceGeneralSettingsSchema, @@ -343,9 +393,14 @@ export { createCompanySchema, updateCompanySchema, updateCompanyBrandingSchema, + feedbackTargetTypeSchema, + feedbackTraceStatusSchema, + feedbackVoteValueSchema, + upsertIssueFeedbackVoteSchema, type CreateCompany, type UpdateCompany, type UpdateCompanyBranding, + type UpsertIssueFeedbackVote, agentSkillStateSchema, agentSkillSyncModeSchema, agentSkillEntrySchema, @@ -390,6 +445,8 @@ export { createIssueSchema, createIssueLabelSchema, updateIssueSchema, + issueExecutionPolicySchema, + issueExecutionStateSchema, issueExecutionWorkspaceSettingsSchema, checkoutIssueSchema, addIssueCommentSchema, @@ -411,6 +468,7 @@ export { issueDocumentFormatSchema, issueDocumentKeySchema, upsertIssueDocumentSchema, + restoreIssueDocumentRevisionSchema, type CreateIssue, type CreateIssueLabel, type UpdateIssue, @@ -423,6 +481,7 @@ export { type UpdateExecutionWorkspace, type IssueDocumentFormat, type UpsertIssueDocument, + type RestoreIssueDocumentRevision, createGoalSchema, updateGoalSchema, type CreateGoal, @@ -452,6 +511,7 @@ export { updateRoutineSchema, createRoutineTriggerSchema, updateRoutineTriggerSchema, + routineVariableSchema, runRoutineSchema, rotateRoutineTriggerSecretSchema, type CreateSecret, @@ -564,20 +624,36 @@ export { export { API_PREFIX, API } from "./api.js"; export { normalizeAgentUrlKey, deriveAgentUrlKey, isUuidLike } from "./agent-url-key.js"; -export { deriveProjectUrlKey, normalizeProjectUrlKey } from "./project-url-key.js"; +export { deriveProjectUrlKey, normalizeProjectUrlKey, hasNonAsciiContent } from "./project-url-key.js"; export { AGENT_MENTION_SCHEME, PROJECT_MENTION_SCHEME, + SKILL_MENTION_SCHEME, buildAgentMentionHref, buildProjectMentionHref, + buildSkillMentionHref, extractAgentMentionIds, + extractSkillMentionIds, parseAgentMentionHref, parseProjectMentionHref, + parseSkillMentionHref, extractProjectMentionIds, type ParsedAgentMention, type ParsedProjectMention, + type ParsedSkillMention, } from "./project-mentions.js"; +export { + BUILTIN_ROUTINE_VARIABLE_NAMES, + extractRoutineVariableNames, + getBuiltinRoutineVariableValues, + interpolateRoutineTemplate, + isBuiltinRoutineVariable, + isValidRoutineVariableName, + stringifyRoutineVariableValue, + syncRoutineVariablesWithTemplate, +} from "./routine-variables.js"; + export { paperclipConfigSchema, configMetaSchema, @@ -592,6 +668,8 @@ export { storageLocalDiskConfigSchema, storageS3ConfigSchema, secretsLocalEncryptedConfigSchema, + telemetryConfigSchema, + type TelemetryConfig, type PaperclipConfig, type LlmConfig, type DatabaseBackupConfig, diff --git a/packages/shared/src/project-mentions.test.ts b/packages/shared/src/project-mentions.test.ts index 55f27369b8a..5a156959a98 100644 --- a/packages/shared/src/project-mentions.test.ts +++ b/packages/shared/src/project-mentions.test.ts @@ -2,10 +2,13 @@ import { describe, expect, it } from "vitest"; import { buildAgentMentionHref, buildProjectMentionHref, + buildSkillMentionHref, extractAgentMentionIds, extractProjectMentionIds, + extractSkillMentionIds, parseAgentMentionHref, parseProjectMentionHref, + parseSkillMentionHref, } from "./project-mentions.js"; describe("project-mentions", () => { @@ -26,4 +29,13 @@ describe("project-mentions", () => { }); expect(extractAgentMentionIds(`[@CodexCoder](${href})`)).toEqual(["agent-123"]); }); + + it("round-trips skill mentions with slug metadata", () => { + const href = buildSkillMentionHref("skill-123", "release-changelog"); + expect(parseSkillMentionHref(href)).toEqual({ + skillId: "skill-123", + slug: "release-changelog", + }); + expect(extractSkillMentionIds(`[/release-changelog](${href})`)).toEqual(["skill-123"]); + }); }); diff --git a/packages/shared/src/project-mentions.ts b/packages/shared/src/project-mentions.ts index 66be89483a1..117fad39474 100644 --- a/packages/shared/src/project-mentions.ts +++ b/packages/shared/src/project-mentions.ts @@ -1,5 +1,6 @@ export const PROJECT_MENTION_SCHEME = "project://"; export const AGENT_MENTION_SCHEME = "agent://"; +export const SKILL_MENTION_SCHEME = "skill://"; const HEX_COLOR_RE = /^[0-9a-f]{6}$/i; const HEX_COLOR_SHORT_RE = /^[0-9a-f]{3}$/i; @@ -7,7 +8,9 @@ const HEX_COLOR_WITH_HASH_RE = /^#[0-9a-f]{6}$/i; const HEX_COLOR_SHORT_WITH_HASH_RE = /^#[0-9a-f]{3}$/i; const PROJECT_MENTION_LINK_RE = /\[[^\]]*]\((project:\/\/[^)\s]+)\)/gi; const AGENT_MENTION_LINK_RE = /\[[^\]]*]\((agent:\/\/[^)\s]+)\)/gi; +const SKILL_MENTION_LINK_RE = /\[[^\]]*]\((skill:\/\/[^)\s]+)\)/gi; const AGENT_ICON_NAME_RE = /^[a-z0-9-]+$/i; +const SKILL_SLUG_RE = /^[a-z0-9][a-z0-9-]*$/i; export interface ParsedProjectMention { projectId: string; @@ -19,6 +22,11 @@ export interface ParsedAgentMention { icon: string | null; } +export interface ParsedSkillMention { + skillId: string; + slug: string | null; +} + function normalizeHexColor(input: string | null | undefined): string | null { if (!input) return null; const trimmed = input.trim(); @@ -103,6 +111,36 @@ export function parseAgentMentionHref(href: string): ParsedAgentMention | null { }; } +export function buildSkillMentionHref(skillId: string, slug?: string | null): string { + const trimmedSkillId = skillId.trim(); + const normalizedSlug = normalizeSkillSlug(slug ?? null); + if (!normalizedSlug) { + return `${SKILL_MENTION_SCHEME}${trimmedSkillId}`; + } + return `${SKILL_MENTION_SCHEME}${trimmedSkillId}?s=${encodeURIComponent(normalizedSlug)}`; +} + +export function parseSkillMentionHref(href: string): ParsedSkillMention | null { + if (!href.startsWith(SKILL_MENTION_SCHEME)) return null; + + let url: URL; + try { + url = new URL(href); + } catch { + return null; + } + + if (url.protocol !== "skill:") return null; + + const skillId = `${url.hostname}${url.pathname}`.replace(/^\/+/, "").trim(); + if (!skillId) return null; + + return { + skillId, + slug: normalizeSkillSlug(url.searchParams.get("s") ?? url.searchParams.get("slug")), + }; +} + export function extractProjectMentionIds(markdown: string): string[] { if (!markdown) return []; const ids = new Set(); @@ -127,9 +165,28 @@ export function extractAgentMentionIds(markdown: string): string[] { return [...ids]; } +export function extractSkillMentionIds(markdown: string): string[] { + if (!markdown) return []; + const ids = new Set(); + const re = new RegExp(SKILL_MENTION_LINK_RE); + let match: RegExpExecArray | null; + while ((match = re.exec(markdown)) !== null) { + const parsed = parseSkillMentionHref(match[1]); + if (parsed) ids.add(parsed.skillId); + } + return [...ids]; +} + function normalizeAgentIcon(input: string | null | undefined): string | null { if (!input) return null; const trimmed = input.trim().toLowerCase(); if (!trimmed || !AGENT_ICON_NAME_RE.test(trimmed)) return null; return trimmed; } + +function normalizeSkillSlug(input: string | null | undefined): string | null { + if (!input) return null; + const trimmed = input.trim().toLowerCase(); + if (!trimmed || !SKILL_SLUG_RE.test(trimmed)) return null; + return trimmed; +} diff --git a/packages/shared/src/project-url-key.ts b/packages/shared/src/project-url-key.ts index 85cb829776b..a367d41709f 100644 --- a/packages/shared/src/project-url-key.ts +++ b/packages/shared/src/project-url-key.ts @@ -1,5 +1,7 @@ const PROJECT_URL_KEY_DELIM_RE = /[^a-z0-9]+/g; const PROJECT_URL_KEY_TRIM_RE = /^-+|-+$/g; +const NON_ASCII_RE = /[^\x00-\x7F]/; +const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; export function normalizeProjectUrlKey(value: string | null | undefined): string | null { if (typeof value !== "string") return null; @@ -11,6 +13,24 @@ export function normalizeProjectUrlKey(value: string | null | undefined): string return normalized.length > 0 ? normalized : null; } +/** Check whether a string contains non-ASCII characters that normalization would strip. */ +export function hasNonAsciiContent(value: string | null | undefined): boolean { + if (typeof value !== "string") return false; + return NON_ASCII_RE.test(value); +} + +/** Extract the first 8 hex chars from a valid UUID, or null. */ +function shortIdFromUuid(value: string | null | undefined): string | null { + if (typeof value !== "string" || !UUID_RE.test(value.trim())) return null; + return value.trim().replace(/-/g, "").slice(0, 8).toLowerCase(); +} + export function deriveProjectUrlKey(name: string | null | undefined, fallback?: string | null): string { - return normalizeProjectUrlKey(name) ?? normalizeProjectUrlKey(fallback) ?? "project"; + const base = normalizeProjectUrlKey(name); + if (base && !hasNonAsciiContent(name)) return base; + // Non-ASCII content was stripped — append short UUID suffix for uniqueness. + const shortId = shortIdFromUuid(fallback); + if (base && shortId) return `${base}-${shortId}`; + if (shortId) return shortId; + return base ?? normalizeProjectUrlKey(fallback) ?? "project"; } diff --git a/packages/shared/src/routine-variables.test.ts b/packages/shared/src/routine-variables.test.ts new file mode 100644 index 00000000000..a079ae62185 --- /dev/null +++ b/packages/shared/src/routine-variables.test.ts @@ -0,0 +1,76 @@ +import { describe, expect, it } from "vitest"; +import { + BUILTIN_ROUTINE_VARIABLE_NAMES, + extractRoutineVariableNames, + getBuiltinRoutineVariableValues, + interpolateRoutineTemplate, + isBuiltinRoutineVariable, + syncRoutineVariablesWithTemplate, +} from "./routine-variables.js"; + +describe("routine variable helpers", () => { + it("extracts placeholder names in first-appearance order", () => { + expect( + extractRoutineVariableNames("Review {{repo}} and {{priority}} for {{repo}}"), + ).toEqual(["repo", "priority"]); + }); + + it("deduplicates placeholder names across the routine title and description", () => { + expect( + extractRoutineVariableNames([ + "Triage {{repo}}", + "Review {{repo}} for {{priority}} bugs", + ]), + ).toEqual(["repo", "priority"]); + }); + + it("preserves existing metadata when syncing variables from a template", () => { + expect( + syncRoutineVariablesWithTemplate(["Triage {{repo}}", "Review {{repo}} and {{priority}}"], [ + { name: "repo", label: "Repository", type: "text", defaultValue: "paperclip", required: true, options: [] }, + ]), + ).toEqual([ + { name: "repo", label: "Repository", type: "text", defaultValue: "paperclip", required: true, options: [] }, + { name: "priority", label: null, type: "text", defaultValue: null, required: true, options: [] }, + ]); + }); + + it("interpolates provided variable values into the routine template", () => { + expect( + interpolateRoutineTemplate("Review {{repo}} for {{priority}}", { + repo: "paperclip", + priority: "high", + }), + ).toBe("Review paperclip for high"); + }); + + it("identifies built-in variable names", () => { + expect(isBuiltinRoutineVariable("date")).toBe(true); + expect(isBuiltinRoutineVariable("repo")).toBe(false); + expect(BUILTIN_ROUTINE_VARIABLE_NAMES.has("date")).toBe(true); + }); + + it("getBuiltinRoutineVariableValues returns date in YYYY-MM-DD format", () => { + const values = getBuiltinRoutineVariableValues(); + expect(values.date).toMatch(/^\d{4}-\d{2}-\d{2}$/); + expect(values.date).toBe(new Date().toISOString().slice(0, 10)); + }); + + it("excludes built-in variables from syncRoutineVariablesWithTemplate", () => { + const result = syncRoutineVariablesWithTemplate( + "Daily report for {{date}} — {{repo}}", + [], + ); + expect(result).toEqual([ + { name: "repo", label: null, type: "text", defaultValue: null, required: true, options: [] }, + ]); + }); + + it("interpolates built-in date variable alongside user variables", () => { + const builtins = getBuiltinRoutineVariableValues(); + const allVars = { ...builtins, repo: "paperclip" }; + expect( + interpolateRoutineTemplate("Report for {{date}} on {{repo}}", allVars), + ).toBe(`Report for ${builtins.date} on paperclip`); + }); +}); diff --git a/packages/shared/src/routine-variables.ts b/packages/shared/src/routine-variables.ts new file mode 100644 index 00000000000..610af48107f --- /dev/null +++ b/packages/shared/src/routine-variables.ts @@ -0,0 +1,89 @@ +import type { RoutineVariable } from "./types/routine.js"; + +const ROUTINE_VARIABLE_MATCHER = /\{\{\s*([A-Za-z][A-Za-z0-9_]*)\s*\}\}/g; +type RoutineTemplateInput = string | null | undefined | Array; + +/** + * Built-in variable names that are automatically available in routine templates + * without needing to be defined in the routine's variables list. + */ +export const BUILTIN_ROUTINE_VARIABLE_NAMES = new Set(["date"]); + +export function isBuiltinRoutineVariable(name: string): boolean { + return BUILTIN_ROUTINE_VARIABLE_NAMES.has(name); +} + +/** + * Returns current values for all built-in routine variables. + * `date` expands to the current date in YYYY-MM-DD format (UTC). + */ +export function getBuiltinRoutineVariableValues(): Record { + return { + date: new Date().toISOString().slice(0, 10), + }; +} + +export function isValidRoutineVariableName(name: string): boolean { + return /^[A-Za-z][A-Za-z0-9_]*$/.test(name); +} + +function normalizeRoutineTemplateInput(input: RoutineTemplateInput): string[] { + const templates = Array.isArray(input) ? input : [input]; + return templates.filter((template): template is string => typeof template === "string" && template.length > 0); +} + +export function extractRoutineVariableNames(template: RoutineTemplateInput): string[] { + const found = new Set(); + for (const source of normalizeRoutineTemplateInput(template)) { + for (const match of source.matchAll(ROUTINE_VARIABLE_MATCHER)) { + const name = match[1]; + if (name && !found.has(name)) { + found.add(name); + } + } + } + return [...found]; +} + +function defaultRoutineVariable(name: string): RoutineVariable { + return { + name, + label: null, + type: "text", + defaultValue: null, + required: true, + options: [], + }; +} + +export function syncRoutineVariablesWithTemplate( + template: RoutineTemplateInput, + existing: RoutineVariable[] | null | undefined, +): RoutineVariable[] { + const names = extractRoutineVariableNames(template).filter((name) => !isBuiltinRoutineVariable(name)); + const existingByName = new Map((existing ?? []).map((variable) => [variable.name, variable])); + return names.map((name) => existingByName.get(name) ?? defaultRoutineVariable(name)); +} + +export function stringifyRoutineVariableValue(value: unknown): string { + if (typeof value === "string") return value; + if (typeof value === "number" || typeof value === "boolean") return String(value); + if (value == null) return ""; + try { + return JSON.stringify(value); + } catch { + return String(value); + } +} + +export function interpolateRoutineTemplate( + template: string | null | undefined, + values: Record | null | undefined, +): string | null { + if (template == null) return null; + if (!values || Object.keys(values).length === 0) return template; + return template.replace(ROUTINE_VARIABLE_MATCHER, (match, rawName: string) => { + if (!(rawName in values)) return match; + return stringifyRoutineVariableValue(values[rawName]); + }); +} diff --git a/packages/shared/src/telemetry/client.ts b/packages/shared/src/telemetry/client.ts new file mode 100644 index 00000000000..a8d6aefbfe5 --- /dev/null +++ b/packages/shared/src/telemetry/client.ts @@ -0,0 +1,105 @@ +import { createHash } from "node:crypto"; +import type { + TelemetryConfig, + TelemetryEvent, + TelemetryEventName, + TelemetryState, +} from "./types.js"; + +const DEFAULT_ENDPOINT = "https://telemetry.paperclip.ing/ingest"; +const BATCH_SIZE = 50; +const SEND_TIMEOUT_MS = 5_000; + +export class TelemetryClient { + private queue: TelemetryEvent[] = []; + private readonly config: TelemetryConfig; + private readonly stateFactory: () => TelemetryState; + private readonly version: string; + private state: TelemetryState | null = null; + private flushInterval: ReturnType | null = null; + + constructor(config: TelemetryConfig, stateFactory: () => TelemetryState, version: string) { + this.config = config; + this.stateFactory = stateFactory; + this.version = version; + } + + track(eventName: TelemetryEventName, dimensions?: Record): void { + if (!this.config.enabled) return; + this.getState(); // ensure state is initialised (side-effect: creates state file on first call) + + this.queue.push({ + name: eventName, + occurredAt: new Date().toISOString(), + dimensions: dimensions ?? {}, + }); + + if (this.queue.length >= BATCH_SIZE) { + void this.flush(); + } + } + + async flush(): Promise { + if (!this.config.enabled || this.queue.length === 0) return; + + const events = this.queue.splice(0); + const state = this.getState(); + const endpoint = this.config.endpoint ?? DEFAULT_ENDPOINT; + const app = this.config.app ?? "paperclip"; + const schemaVersion = this.config.schemaVersion ?? "1"; + + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), SEND_TIMEOUT_MS); + try { + await fetch(endpoint, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + app, + schemaVersion, + installId: state.installId, + version: this.version, + events, + }), + signal: controller.signal, + }); + } catch { + // Fire-and-forget: silent failure, no retries + } finally { + clearTimeout(timer); + } + } + + startPeriodicFlush(intervalMs: number = 60_000): void { + if (this.flushInterval) return; + this.flushInterval = setInterval(() => { + void this.flush(); + }, intervalMs); + // Allow the process to exit even if the interval is still active + if (typeof this.flushInterval === "object" && "unref" in this.flushInterval) { + this.flushInterval.unref(); + } + } + + stop(): void { + if (this.flushInterval) { + clearInterval(this.flushInterval); + this.flushInterval = null; + } + } + + hashPrivateRef(value: string): string { + const state = this.getState(); + return createHash("sha256") + .update(state.salt + value) + .digest("hex") + .slice(0, 16); + } + + private getState(): TelemetryState { + if (!this.state) { + this.state = this.stateFactory(); + } + return this.state; + } +} diff --git a/packages/shared/src/telemetry/config.ts b/packages/shared/src/telemetry/config.ts new file mode 100644 index 00000000000..0e5252fc55d --- /dev/null +++ b/packages/shared/src/telemetry/config.ts @@ -0,0 +1,25 @@ +import type { TelemetryConfig } from "./types.js"; + +const CI_ENV_VARS = ["CI", "CONTINUOUS_INTEGRATION", "BUILD_NUMBER", "GITHUB_ACTIONS", "GITLAB_CI"]; + +function isCI(): boolean { + return CI_ENV_VARS.some((key) => process.env[key] === "true" || process.env[key] === "1"); +} + +export function resolveTelemetryConfig(fileConfig?: { enabled?: boolean }): TelemetryConfig { + if (process.env.PAPERCLIP_TELEMETRY_DISABLED === "1") { + return { enabled: false }; + } + if (process.env.DO_NOT_TRACK === "1") { + return { enabled: false }; + } + if (isCI()) { + return { enabled: false }; + } + if (fileConfig?.enabled === false) { + return { enabled: false }; + } + + const endpoint = process.env.PAPERCLIP_TELEMETRY_ENDPOINT || undefined; + return { enabled: true, endpoint }; +} diff --git a/packages/shared/src/telemetry/events.ts b/packages/shared/src/telemetry/events.ts new file mode 100644 index 00000000000..6b30995ed22 --- /dev/null +++ b/packages/shared/src/telemetry/events.ts @@ -0,0 +1,87 @@ +import type { TelemetryClient } from "./client.js"; + +export function trackInstallStarted(client: TelemetryClient): void { + client.track("install.started"); +} + +export function trackInstallCompleted( + client: TelemetryClient, + dims: { adapterType: string }, +): void { + client.track("install.completed", { adapter_type: dims.adapterType }); +} + +export function trackCompanyImported( + client: TelemetryClient, + dims: { sourceType: string; sourceRef: string; isPrivate: boolean }, +): void { + const ref = dims.isPrivate ? client.hashPrivateRef(dims.sourceRef) : dims.sourceRef; + client.track("company.imported", { + source_type: dims.sourceType, + source_ref: ref, + source_ref_hashed: dims.isPrivate, + }); +} + +export function trackProjectCreated(client: TelemetryClient): void { + client.track("project.created"); +} + +export function trackRoutineCreated(client: TelemetryClient): void { + client.track("routine.created"); +} + +export function trackRoutineRun( + client: TelemetryClient, + dims: { source: string; status: string }, +): void { + client.track("routine.run", { + source: dims.source, + status: dims.status, + }); +} + +export function trackGoalCreated( + client: TelemetryClient, + dims?: { goalLevel?: string | null }, +): void { + client.track("goal.created", dims?.goalLevel ? { goal_level: dims.goalLevel } : undefined); +} + +export function trackAgentCreated( + client: TelemetryClient, + dims: { agentRole: string }, +): void { + client.track("agent.created", { agent_role: dims.agentRole }); +} + +export function trackSkillImported( + client: TelemetryClient, + dims: { sourceType: string; skillRef?: string | null }, +): void { + client.track("skill.imported", { + source_type: dims.sourceType, + ...(dims.skillRef ? { skill_ref: dims.skillRef } : {}), + }); +} + +export function trackAgentFirstHeartbeat( + client: TelemetryClient, + dims: { agentRole: string }, +): void { + client.track("agent.first_heartbeat", { agent_role: dims.agentRole }); +} + +export function trackAgentTaskCompleted( + client: TelemetryClient, + dims: { agentRole: string }, +): void { + client.track("agent.task_completed", { agent_role: dims.agentRole }); +} + +export function trackErrorHandlerCrash( + client: TelemetryClient, + dims: { errorCode: string }, +): void { + client.track("error.handler_crash", { error_code: dims.errorCode }); +} diff --git a/packages/shared/src/telemetry/index.ts b/packages/shared/src/telemetry/index.ts new file mode 100644 index 00000000000..f80de29c3bb --- /dev/null +++ b/packages/shared/src/telemetry/index.ts @@ -0,0 +1,24 @@ +export { TelemetryClient } from "./client.js"; +export { resolveTelemetryConfig } from "./config.js"; +export { loadOrCreateState } from "./state.js"; +export { + trackInstallStarted, + trackInstallCompleted, + trackCompanyImported, + trackProjectCreated, + trackRoutineCreated, + trackRoutineRun, + trackGoalCreated, + trackAgentCreated, + trackSkillImported, + trackAgentFirstHeartbeat, + trackAgentTaskCompleted, + trackErrorHandlerCrash, +} from "./events.js"; +export type { + TelemetryConfig, + TelemetryState, + TelemetryEvent, + TelemetryEventEnvelope, + TelemetryEventName, +} from "./types.js"; diff --git a/packages/shared/src/telemetry/state.ts b/packages/shared/src/telemetry/state.ts new file mode 100644 index 00000000000..a060af26a8e --- /dev/null +++ b/packages/shared/src/telemetry/state.ts @@ -0,0 +1,31 @@ +import { randomUUID, randomBytes } from "node:crypto"; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import path from "node:path"; +import type { TelemetryState } from "./types.js"; + +export function loadOrCreateState(stateDir: string, version: string): TelemetryState { + const filePath = path.join(stateDir, "state.json"); + + if (existsSync(filePath)) { + try { + const raw = readFileSync(filePath, "utf-8"); + const parsed = JSON.parse(raw) as TelemetryState; + if (parsed.installId && parsed.salt) { + return parsed; + } + } catch { + // Corrupted state file — recreate + } + } + + const state: TelemetryState = { + installId: randomUUID(), + salt: randomBytes(32).toString("hex"), + createdAt: new Date().toISOString(), + firstSeenVersion: version, + }; + + mkdirSync(stateDir, { recursive: true }); + writeFileSync(filePath, JSON.stringify(state, null, 2) + "\n", "utf-8"); + return state; +} diff --git a/packages/shared/src/telemetry/types.ts b/packages/shared/src/telemetry/types.ts new file mode 100644 index 00000000000..d3552d0d216 --- /dev/null +++ b/packages/shared/src/telemetry/types.ts @@ -0,0 +1,44 @@ +export interface TelemetryState { + installId: string; + salt: string; + createdAt: string; + firstSeenVersion: string; +} + +export interface TelemetryConfig { + enabled: boolean; + endpoint?: string; + app?: string; + schemaVersion?: string; +} + +/** Per-event object inside the backend envelope */ +export interface TelemetryEvent { + name: string; + occurredAt: string; + dimensions: Record; +} + +/** Full payload sent to the backend ingest endpoint */ +export interface TelemetryEventEnvelope { + app: string; + schemaVersion: string; + installId: string; + version: string; + events: TelemetryEvent[]; +} + +export type TelemetryEventName = + | "install.started" + | "install.completed" + | "company.imported" + | "project.created" + | "routine.created" + | "routine.run" + | "goal.created" + | "agent.created" + | "skill.imported" + | "agent.first_heartbeat" + | "agent.task_completed" + | "error.handler_crash" + | `plugin.${string}`; diff --git a/packages/shared/src/types/company-portability.ts b/packages/shared/src/types/company-portability.ts index 63016e933a5..356c620d68c 100644 --- a/packages/shared/src/types/company-portability.ts +++ b/packages/shared/src/types/company-portability.ts @@ -1,3 +1,6 @@ +import type { AgentEnvConfig } from "./secrets.js"; +import type { RoutineVariable } from "./routine.js"; + export interface CompanyPortabilityInclude { company: boolean; agents: boolean; @@ -10,6 +13,7 @@ export interface CompanyPortabilityEnvInput { key: string; description: string | null; agentSlug: string | null; + projectSlug: string | null; kind: "secret" | "plain"; requirement: "required" | "optional"; defaultValue: string | null; @@ -31,6 +35,10 @@ export interface CompanyPortabilityCompanyManifestEntry { brandColor: string | null; logoPath: string | null; requireBoardApprovalForNewAgents: boolean; + feedbackDataSharingEnabled: boolean; + feedbackDataSharingConsentAt: string | null; + feedbackDataSharingConsentByUserId: string | null; + feedbackDataSharingTermsVersion: string | null; } export interface CompanyPortabilitySidebarOrder { @@ -48,6 +56,7 @@ export interface CompanyPortabilityProjectManifestEntry { targetDate: string | null; color: string | null; status: string | null; + env: AgentEnvConfig | null; executionWorkspacePolicy: Record | null; workspaces: CompanyPortabilityProjectWorkspaceManifestEntry[]; metadata: Record | null; @@ -80,6 +89,7 @@ export interface CompanyPortabilityIssueRoutineTriggerManifestEntry { export interface CompanyPortabilityIssueRoutineManifestEntry { concurrencyPolicy: string | null; catchUpPolicy: string | null; + variables?: RoutineVariable[] | null; triggers: CompanyPortabilityIssueRoutineTriggerManifestEntry[]; } diff --git a/packages/shared/src/types/company.ts b/packages/shared/src/types/company.ts index 9f6d3168f8c..bd706c1875b 100644 --- a/packages/shared/src/types/company.ts +++ b/packages/shared/src/types/company.ts @@ -12,6 +12,10 @@ export interface Company { budgetMonthlyCents: number; spentMonthlyCents: number; requireBoardApprovalForNewAgents: boolean; + feedbackDataSharingEnabled: boolean; + feedbackDataSharingConsentAt: Date | null; + feedbackDataSharingConsentByUserId: string | null; + feedbackDataSharingTermsVersion: string | null; brandColor: string | null; logoAssetId: string | null; logoUrl: string | null; diff --git a/packages/shared/src/types/feedback.ts b/packages/shared/src/types/feedback.ts new file mode 100644 index 00000000000..c5eb31545c7 --- /dev/null +++ b/packages/shared/src/types/feedback.ts @@ -0,0 +1,120 @@ +export const FEEDBACK_TARGET_TYPES = ["issue_comment", "issue_document_revision"] as const; +export type FeedbackTargetType = (typeof FEEDBACK_TARGET_TYPES)[number]; + +export const FEEDBACK_VOTE_VALUES = ["up", "down"] as const; +export type FeedbackVoteValue = (typeof FEEDBACK_VOTE_VALUES)[number]; + +export const FEEDBACK_DATA_SHARING_PREFERENCES = ["allowed", "not_allowed", "prompt"] as const; +export type FeedbackDataSharingPreference = (typeof FEEDBACK_DATA_SHARING_PREFERENCES)[number]; + +export const DEFAULT_FEEDBACK_DATA_SHARING_PREFERENCE: FeedbackDataSharingPreference = "prompt"; + +export const FEEDBACK_TRACE_STATUSES = ["local_only", "pending", "sent", "failed"] as const; +export type FeedbackTraceStatus = (typeof FEEDBACK_TRACE_STATUSES)[number]; + +export const DEFAULT_FEEDBACK_DATA_SHARING_TERMS_VERSION = "feedback-data-sharing-v1"; + +export interface FeedbackVote { + id: string; + companyId: string; + issueId: string; + targetType: FeedbackTargetType; + targetId: string; + authorUserId: string; + vote: FeedbackVoteValue; + reason: string | null; + sharedWithLabs: boolean; + sharedAt: Date | null; + consentVersion: string | null; + redactionSummary: Record | null; + createdAt: Date; + updatedAt: Date; +} + +export interface FeedbackTraceTargetSummary { + label: string; + excerpt: string | null; + authorAgentId: string | null; + authorUserId: string | null; + createdAt: Date | null; + documentKey: string | null; + documentTitle: string | null; + revisionNumber: number | null; +} + +export interface FeedbackTrace { + id: string; + companyId: string; + feedbackVoteId: string; + issueId: string; + projectId: string | null; + issueIdentifier: string | null; + issueTitle: string; + authorUserId: string; + targetType: FeedbackTargetType; + targetId: string; + vote: FeedbackVoteValue; + status: FeedbackTraceStatus; + destination: string | null; + exportId: string | null; + consentVersion: string | null; + schemaVersion: string; + bundleVersion: string; + payloadVersion: string; + payloadDigest: string | null; + payloadSnapshot: Record | null; + targetSummary: FeedbackTraceTargetSummary; + redactionSummary: Record | null; + attemptCount: number; + lastAttemptedAt: Date | null; + exportedAt: Date | null; + failureReason: string | null; + createdAt: Date; + updatedAt: Date; +} + +export type FeedbackTraceBundleCaptureStatus = "full" | "partial" | "unavailable"; + +export interface FeedbackTraceBundleFile { + path: string; + contentType: string; + encoding: "utf8"; + byteLength: number; + sha256: string; + source: + | "paperclip_run" + | "paperclip_run_events" + | "paperclip_run_log" + | "codex_session" + | "claude_stream_json" + | "claude_project_session" + | "claude_project_artifact" + | "claude_debug_log" + | "claude_task_metadata" + | "opencode_session" + | "opencode_session_diff" + | "opencode_message" + | "opencode_message_part" + | "opencode_project" + | "opencode_todo"; + contents: string; +} + +export interface FeedbackTraceBundle { + traceId: string; + exportId: string | null; + companyId: string; + issueId: string; + issueIdentifier: string | null; + adapterType: string | null; + captureStatus: FeedbackTraceBundleCaptureStatus; + notes: string[]; + envelope: Record; + surface: Record | null; + paperclipRun: Record | null; + rawAdapterTrace: Record | null; + normalizedAdapterTrace: Record | null; + privacy: Record | null; + integrity: Record; + files: FeedbackTraceBundleFile[]; +} diff --git a/packages/shared/src/types/heartbeat.ts b/packages/shared/src/types/heartbeat.ts index c399b6da8e1..5e8dd217091 100644 --- a/packages/shared/src/types/heartbeat.ts +++ b/packages/shared/src/types/heartbeat.ts @@ -42,6 +42,18 @@ export interface HeartbeatRun { updatedAt: Date; } +export interface AgentWakeupSkipped { + status: "skipped"; + reason: string; + message: string | null; + issueId: string | null; + executionRunId: string | null; + executionAgentId: string | null; + executionAgentName: string | null; +} + +export type AgentWakeupResponse = HeartbeatRun | AgentWakeupSkipped; + export interface HeartbeatRunEvent { id: number; companyId: string; diff --git a/packages/shared/src/types/inbox-dismissal.ts b/packages/shared/src/types/inbox-dismissal.ts new file mode 100644 index 00000000000..0c76ecc824c --- /dev/null +++ b/packages/shared/src/types/inbox-dismissal.ts @@ -0,0 +1,9 @@ +export interface InboxDismissal { + id: string; + companyId: string; + userId: string; + itemKey: string; + dismissedAt: Date; + createdAt: Date; + updatedAt: Date; +} diff --git a/packages/shared/src/types/index.ts b/packages/shared/src/types/index.ts index dfe4b9d5f6c..888740d3d99 100644 --- a/packages/shared/src/types/index.ts +++ b/packages/shared/src/types/index.ts @@ -1,4 +1,16 @@ export type { Company } from "./company.js"; +export type { + FeedbackVote, + FeedbackDataSharingPreference, + FeedbackTargetType, + FeedbackVoteValue, + FeedbackTrace, + FeedbackTraceStatus, + FeedbackTraceTargetSummary, + FeedbackTraceBundleCaptureStatus, + FeedbackTraceBundleFile, + FeedbackTraceBundle, +} from "./feedback.js"; export type { InstanceExperimentalSettings, InstanceGeneralSettings, InstanceSettings } from "./instance.js"; export type { CompanySkillSourceType, @@ -84,6 +96,14 @@ export type { export type { Issue, IssueAssigneeAdapterOverrides, + IssueRelation, + IssueRelationIssueSummary, + IssueExecutionPolicy, + IssueExecutionState, + IssueExecutionStage, + IssueExecutionStageParticipant, + IssueExecutionStagePrincipal, + IssueExecutionDecision, IssueComment, IssueDocument, IssueDocumentSummary, @@ -118,6 +138,8 @@ export type { } from "./secrets.js"; export type { Routine, + RoutineVariable, + RoutineVariableDefaultValue, RoutineTrigger, RoutineRun, RoutineTriggerSecretMaterial, @@ -129,6 +151,8 @@ export type { export type { CostEvent, CostSummary, CostByAgent, CostByProviderModel, CostByBiller, CostByAgentModel, CostWindowSpendRow, CostByProject } from "./cost.js"; export type { FinanceEvent, FinanceSummary, FinanceByBiller, FinanceByKind } from "./finance.js"; export type { + AgentWakeupResponse, + AgentWakeupSkipped, HeartbeatRun, HeartbeatRunEvent, AgentRuntimeState, @@ -140,6 +164,7 @@ export type { LiveEvent } from "./live.js"; export type { DashboardSummary } from "./dashboard.js"; export type { ActivityEvent } from "./activity.js"; export type { SidebarBadges } from "./sidebar-badges.js"; +export type { InboxDismissal } from "./inbox-dismissal.js"; export type { CompanyMembership, PrincipalPermissionGrant, diff --git a/packages/shared/src/types/instance.ts b/packages/shared/src/types/instance.ts index 562c55b38d1..70599868f7e 100644 --- a/packages/shared/src/types/instance.ts +++ b/packages/shared/src/types/instance.ts @@ -1,5 +1,9 @@ +import type { FeedbackDataSharingPreference } from "./feedback.js"; + export interface InstanceGeneralSettings { censorUsernameInLogs: boolean; + keyboardShortcuts: boolean; + feedbackDataSharingPreference: FeedbackDataSharingPreference; } export interface InstanceExperimentalSettings { diff --git a/packages/shared/src/types/issue.ts b/packages/shared/src/types/issue.ts index 48797fa2699..06608102618 100644 --- a/packages/shared/src/types/issue.ts +++ b/packages/shared/src/types/issue.ts @@ -1,4 +1,12 @@ -import type { IssueOriginKind, IssuePriority, IssueStatus } from "../constants.js"; +import type { + IssueExecutionDecisionOutcome, + IssueExecutionPolicyMode, + IssueExecutionStageType, + IssueExecutionStateStatus, + IssueOriginKind, + IssuePriority, + IssueStatus, +} from "../constants.js"; import type { Goal } from "./goal.js"; import type { Project, ProjectWorkspace } from "./project.js"; import type { ExecutionWorkspace, IssueExecutionWorkspaceSettings } from "./workspace-runtime.js"; @@ -81,6 +89,8 @@ export interface DocumentRevision { issueId: string; key: string; revisionNumber: number; + title: string | null; + format: DocumentFormat; body: string; changeSummary: string | null; createdByAgentId: string | null; @@ -94,6 +104,75 @@ export interface LegacyPlanDocument { source: "issue_description"; } +export interface IssueRelationIssueSummary { + id: string; + identifier: string | null; + title: string; + status: IssueStatus; + priority: IssuePriority; + assigneeAgentId: string | null; + assigneeUserId: string | null; +} + +export interface IssueRelation { + id: string; + companyId: string; + issueId: string; + relatedIssueId: string; + type: "blocks"; + relatedIssue: IssueRelationIssueSummary; +} + +export interface IssueExecutionStagePrincipal { + type: "agent" | "user"; + agentId?: string | null; + userId?: string | null; +} + +export interface IssueExecutionStageParticipant extends IssueExecutionStagePrincipal { + id: string; +} + +export interface IssueExecutionStage { + id: string; + type: IssueExecutionStageType; + approvalsNeeded: 1; + participants: IssueExecutionStageParticipant[]; +} + +export interface IssueExecutionPolicy { + mode: IssueExecutionPolicyMode; + commentRequired: boolean; + stages: IssueExecutionStage[]; +} + +export interface IssueExecutionState { + status: IssueExecutionStateStatus; + currentStageId: string | null; + currentStageIndex: number | null; + currentStageType: IssueExecutionStageType | null; + currentParticipant: IssueExecutionStagePrincipal | null; + returnAssignee: IssueExecutionStagePrincipal | null; + completedStageIds: string[]; + lastDecisionId: string | null; + lastDecisionOutcome: IssueExecutionDecisionOutcome | null; +} + +export interface IssueExecutionDecision { + id: string; + companyId: string; + issueId: string; + stageId: string; + stageType: IssueExecutionStageType; + actorAgentId: string | null; + actorUserId: string | null; + outcome: IssueExecutionDecisionOutcome; + body: string; + createdByRunId: string | null; + createdAt: Date; + updatedAt: Date; +} + export interface Issue { id: string; companyId: string; @@ -122,6 +201,8 @@ export interface Issue { requestDepth: number; billingCode: string | null; assigneeAdapterOverrides: IssueAssigneeAdapterOverrides | null; + executionPolicy?: IssueExecutionPolicy | null; + executionState?: IssueExecutionState | null; executionWorkspaceId: string | null; executionWorkspacePreference: string | null; executionWorkspaceSettings: IssueExecutionWorkspaceSettings | null; @@ -131,6 +212,8 @@ export interface Issue { hiddenAt: Date | null; labelIds?: string[]; labels?: IssueLabel[]; + blockedBy?: IssueRelationIssueSummary[]; + blocks?: IssueRelationIssueSummary[]; planDocument?: IssueDocument | null; documentSummaries?: IssueDocumentSummary[]; legacyPlanDocument?: LegacyPlanDocument | null; @@ -141,6 +224,7 @@ export interface Issue { mentionedProjects?: Project[]; myLastTouchAt?: Date | null; lastExternalCommentAt?: Date | null; + lastActivityAt?: Date | null; isUnreadForMe?: boolean; createdAt: Date; updatedAt: Date; diff --git a/packages/shared/src/types/project.ts b/packages/shared/src/types/project.ts index d843b425b1c..954a5bc6f9c 100644 --- a/packages/shared/src/types/project.ts +++ b/packages/shared/src/types/project.ts @@ -65,6 +65,7 @@ export interface Project { leadAgentId: string | null; targetDate: string | null; color: string | null; + env: AgentEnvConfig | null; pauseReason: PauseReason | null; pausedAt: Date | null; executionWorkspacePolicy: ProjectExecutionWorkspacePolicy | null; diff --git a/packages/shared/src/types/routine.ts b/packages/shared/src/types/routine.ts index 3244f243dfe..4efdc748c64 100644 --- a/packages/shared/src/types/routine.ts +++ b/packages/shared/src/types/routine.ts @@ -1,4 +1,4 @@ -import type { IssueOriginKind } from "../constants.js"; +import type { IssueOriginKind, RoutineVariableType } from "../constants.js"; export interface RoutineProjectSummary { id: string; @@ -25,19 +25,31 @@ export interface RoutineIssueSummary { updatedAt: Date; } +export type RoutineVariableDefaultValue = string | number | boolean | null; + +export interface RoutineVariable { + name: string; + label: string | null; + type: RoutineVariableType; + defaultValue: RoutineVariableDefaultValue; + required: boolean; + options: string[]; +} + export interface Routine { id: string; companyId: string; - projectId: string; + projectId: string | null; goalId: string | null; parentIssueId: string | null; title: string; description: string | null; - assigneeAgentId: string; + assigneeAgentId: string | null; priority: string; status: string; concurrencyPolicy: string; catchUpPolicy: string; + variables: RoutineVariable[]; createdByAgentId: string | null; createdByUserId: string | null; updatedByAgentId: string | null; diff --git a/packages/shared/src/validators/access.ts b/packages/shared/src/validators/access.ts index beb55d2d842..c67584a5bf2 100644 --- a/packages/shared/src/validators/access.ts +++ b/packages/shared/src/validators/access.ts @@ -8,6 +8,7 @@ import { MEMBERSHIP_STATUSES, PERMISSION_KEYS, } from "../constants.js"; +import { optionalAgentAdapterTypeSchema } from "../adapter-type.js"; export const createCompanyInviteSchema = z.object({ allowedJoinTypes: z.enum(INVITE_JOIN_TYPES).default("both"), @@ -29,7 +30,7 @@ export type CreateOpenClawInvitePrompt = z.infer< export const acceptInviteSchema = z.object({ requestType: z.enum(JOIN_REQUEST_TYPES), agentName: z.string().min(1).max(120).optional(), - adapterType: z.enum(AGENT_ADAPTER_TYPES).optional(), + adapterType: optionalAgentAdapterTypeSchema, capabilities: z.string().max(4000).optional().nullable(), agentDefaultsPayload: z.record(z.string(), z.unknown()).optional().nullable(), // OpenClaw join compatibility fields accepted at top level. diff --git a/packages/shared/src/validators/agent.ts b/packages/shared/src/validators/agent.ts index 288ae683fd0..7b462db7fdb 100644 --- a/packages/shared/src/validators/agent.ts +++ b/packages/shared/src/validators/agent.ts @@ -1,11 +1,11 @@ import { z } from "zod"; import { - AGENT_ADAPTER_TYPES, AGENT_ICON_NAMES, AGENT_ROLES, AGENT_STATUSES, INBOX_MINE_ISSUE_STATUS_FILTER, } from "../constants.js"; +import { agentAdapterTypeSchema } from "../adapter-type.js"; import { envConfigSchema } from "./secret.js"; export const agentPermissionsSchema = z.object({ @@ -52,7 +52,7 @@ export const createAgentSchema = z.object({ reportsTo: z.string().uuid().optional().nullable(), capabilities: z.string().optional().nullable(), desiredSkills: z.array(z.string().min(1)).optional(), - adapterType: z.enum(AGENT_ADAPTER_TYPES).optional().default("process"), + adapterType: agentAdapterTypeSchema, adapterConfig: adapterConfigSchema.optional().default({}), runtimeConfig: z.record(z.unknown()).optional().default({}), budgetMonthlyCents: z.number().int().nonnegative().optional().default(0), diff --git a/packages/shared/src/validators/company-portability.ts b/packages/shared/src/validators/company-portability.ts index 7cbd4884c55..952c9cce86f 100644 --- a/packages/shared/src/validators/company-portability.ts +++ b/packages/shared/src/validators/company-portability.ts @@ -1,4 +1,5 @@ import { z } from "zod"; +import { routineVariableSchema } from "./routine.js"; export const portabilityIncludeSchema = z .object({ @@ -14,6 +15,7 @@ export const portabilityEnvInputSchema = z.object({ key: z.string().min(1), description: z.string().nullable(), agentSlug: z.string().min(1).nullable(), + projectSlug: z.string().min(1).nullable(), kind: z.enum(["secret", "plain"]), requirement: z.enum(["required", "optional"]), defaultValue: z.string().nullable(), @@ -36,6 +38,10 @@ export const portabilityCompanyManifestEntrySchema = z.object({ brandColor: z.string().nullable(), logoPath: z.string().nullable(), requireBoardApprovalForNewAgents: z.boolean(), + feedbackDataSharingEnabled: z.boolean().default(false), + feedbackDataSharingConsentAt: z.string().datetime().nullable().default(null), + feedbackDataSharingConsentByUserId: z.string().nullable().default(null), + feedbackDataSharingTermsVersion: z.string().nullable().default(null), }); export const portabilitySidebarOrderSchema = z.object({ @@ -119,6 +125,7 @@ export const portabilityIssueRoutineTriggerManifestEntrySchema = z.object({ export const portabilityIssueRoutineManifestEntrySchema = z.object({ concurrencyPolicy: z.string().nullable(), catchUpPolicy: z.string().nullable(), + variables: z.array(routineVariableSchema).nullable().optional(), triggers: z.array(portabilityIssueRoutineTriggerManifestEntrySchema).default([]), }); diff --git a/packages/shared/src/validators/company.ts b/packages/shared/src/validators/company.ts index e3a1a2084c5..00507581c4c 100644 --- a/packages/shared/src/validators/company.ts +++ b/packages/shared/src/validators/company.ts @@ -3,6 +3,7 @@ import { COMPANY_STATUSES } from "../constants.js"; const logoAssetIdSchema = z.string().uuid().nullable().optional(); const brandColorSchema = z.string().regex(/^#[0-9a-fA-F]{6}$/).nullable().optional(); +const feedbackDataSharingTermsVersionSchema = z.string().min(1).nullable().optional(); export const createCompanySchema = z.object({ name: z.string().min(1), @@ -18,6 +19,10 @@ export const updateCompanySchema = createCompanySchema status: z.enum(COMPANY_STATUSES).optional(), spentMonthlyCents: z.number().int().nonnegative().optional(), requireBoardApprovalForNewAgents: z.boolean().optional(), + feedbackDataSharingEnabled: z.boolean().optional(), + feedbackDataSharingConsentAt: z.coerce.date().nullable().optional(), + feedbackDataSharingConsentByUserId: z.string().min(1).nullable().optional(), + feedbackDataSharingTermsVersion: feedbackDataSharingTermsVersionSchema, brandColor: brandColorSchema, logoAssetId: logoAssetIdSchema, }); diff --git a/packages/shared/src/validators/feedback.ts b/packages/shared/src/validators/feedback.ts new file mode 100644 index 00000000000..9ac34ef430f --- /dev/null +++ b/packages/shared/src/validators/feedback.ts @@ -0,0 +1,22 @@ +import { z } from "zod"; +import { + FEEDBACK_DATA_SHARING_PREFERENCES, + FEEDBACK_TARGET_TYPES, + FEEDBACK_TRACE_STATUSES, + FEEDBACK_VOTE_VALUES, +} from "../types/feedback.js"; + +export const feedbackTargetTypeSchema = z.enum(FEEDBACK_TARGET_TYPES); +export const feedbackTraceStatusSchema = z.enum(FEEDBACK_TRACE_STATUSES); +export const feedbackVoteValueSchema = z.enum(FEEDBACK_VOTE_VALUES); +export const feedbackDataSharingPreferenceSchema = z.enum(FEEDBACK_DATA_SHARING_PREFERENCES); + +export const upsertIssueFeedbackVoteSchema = z.object({ + targetType: feedbackTargetTypeSchema, + targetId: z.string().uuid(), + vote: feedbackVoteValueSchema, + reason: z.string().trim().max(1000).optional(), + allowSharing: z.boolean().optional(), +}); + +export type UpsertIssueFeedbackVote = z.infer; diff --git a/packages/shared/src/validators/index.ts b/packages/shared/src/validators/index.ts index eb736d5911c..9ec49231767 100644 --- a/packages/shared/src/validators/index.ts +++ b/packages/shared/src/validators/index.ts @@ -24,6 +24,14 @@ export { type UpdateCompany, type UpdateCompanyBranding, } from "./company.js"; +export { + feedbackDataSharingPreferenceSchema, + feedbackTargetTypeSchema, + feedbackTraceStatusSchema, + feedbackVoteValueSchema, + upsertIssueFeedbackVoteSchema, + type UpsertIssueFeedbackVote, +} from "./feedback.js"; export { companySkillSourceTypeSchema, companySkillTrustLevelSchema, @@ -123,6 +131,8 @@ export { createIssueSchema, createIssueLabelSchema, updateIssueSchema, + issueExecutionPolicySchema, + issueExecutionStateSchema, issueExecutionWorkspaceSettingsSchema, checkoutIssueSchema, addIssueCommentSchema, @@ -131,6 +141,7 @@ export { issueDocumentFormatSchema, issueDocumentKeySchema, upsertIssueDocumentSchema, + restoreIssueDocumentRevisionSchema, type CreateIssue, type CreateIssueLabel, type UpdateIssue, @@ -141,6 +152,7 @@ export { type CreateIssueAttachmentMetadata, type IssueDocumentFormat, type UpsertIssueDocument, + type RestoreIssueDocumentRevision, } from "./issue.js"; export { @@ -204,6 +216,7 @@ export { updateRoutineSchema, createRoutineTriggerSchema, updateRoutineTriggerSchema, + routineVariableSchema, runRoutineSchema, rotateRoutineTriggerSecretSchema, type CreateRoutine, diff --git a/packages/shared/src/validators/instance.ts b/packages/shared/src/validators/instance.ts index 05ee432368a..5634c8f6a71 100644 --- a/packages/shared/src/validators/instance.ts +++ b/packages/shared/src/validators/instance.ts @@ -1,7 +1,13 @@ import { z } from "zod"; +import { DEFAULT_FEEDBACK_DATA_SHARING_PREFERENCE } from "../types/feedback.js"; +import { feedbackDataSharingPreferenceSchema } from "./feedback.js"; export const instanceGeneralSettingsSchema = z.object({ censorUsernameInLogs: z.boolean().default(false), + keyboardShortcuts: z.boolean().default(false), + feedbackDataSharingPreference: feedbackDataSharingPreferenceSchema.default( + DEFAULT_FEEDBACK_DATA_SHARING_PREFERENCE, + ), }).strict(); export const patchInstanceGeneralSettingsSchema = instanceGeneralSettingsSchema.partial(); diff --git a/packages/shared/src/validators/issue.ts b/packages/shared/src/validators/issue.ts index e6d1a34ac3d..fc6c9c31787 100644 --- a/packages/shared/src/validators/issue.ts +++ b/packages/shared/src/validators/issue.ts @@ -1,5 +1,21 @@ import { z } from "zod"; -import { ISSUE_PRIORITIES, ISSUE_STATUSES } from "../constants.js"; +import { + ISSUE_EXECUTION_DECISION_OUTCOMES, + ISSUE_EXECUTION_POLICY_MODES, + ISSUE_EXECUTION_STAGE_TYPES, + ISSUE_EXECUTION_STATE_STATUSES, + ISSUE_PRIORITIES, + ISSUE_STATUSES, +} from "../constants.js"; + +export const ISSUE_EXECUTION_WORKSPACE_PREFERENCES = [ + "inherit", + "shared_workspace", + "isolated_workspace", + "operator_branch", + "reuse_existing", + "agent_default", +] as const; const executionWorkspaceStrategySchema = z .object({ @@ -14,7 +30,7 @@ const executionWorkspaceStrategySchema = z export const issueExecutionWorkspaceSettingsSchema = z .object({ - mode: z.enum(["inherit", "shared_workspace", "isolated_workspace", "operator_branch", "reuse_existing", "agent_default"]).optional(), + mode: z.enum(ISSUE_EXECUTION_WORKSPACE_PREFERENCES).optional(), workspaceStrategy: executionWorkspaceStrategySchema.optional().nullable(), workspaceRuntime: z.record(z.unknown()).optional().nullable(), }) @@ -27,6 +43,76 @@ export const issueAssigneeAdapterOverridesSchema = z }) .strict(); +const issueExecutionStagePrincipalBaseSchema = z.object({ + type: z.enum(["agent", "user"]), + agentId: z.string().uuid().optional().nullable(), + userId: z.string().optional().nullable(), +}); + +export const issueExecutionStagePrincipalSchema = issueExecutionStagePrincipalBaseSchema + .superRefine((value, ctx) => { + if (value.type === "agent") { + if (!value.agentId) { + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Agent participants require agentId", path: ["agentId"] }); + } + if (value.userId) { + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Agent participants cannot set userId", path: ["userId"] }); + } + return; + } + if (!value.userId) { + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "User participants require userId", path: ["userId"] }); + } + if (value.agentId) { + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "User participants cannot set agentId", path: ["agentId"] }); + } + }); + +export const issueExecutionStageParticipantSchema = issueExecutionStagePrincipalBaseSchema.extend({ + id: z.string().uuid().optional(), +}).superRefine((value, ctx) => { + if (value.type === "agent") { + if (!value.agentId) { + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Agent participants require agentId", path: ["agentId"] }); + } + if (value.userId) { + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Agent participants cannot set userId", path: ["userId"] }); + } + return; + } + if (!value.userId) { + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "User participants require userId", path: ["userId"] }); + } + if (value.agentId) { + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "User participants cannot set agentId", path: ["agentId"] }); + } +}); + +export const issueExecutionStageSchema = z.object({ + id: z.string().uuid().optional(), + type: z.enum(ISSUE_EXECUTION_STAGE_TYPES), + approvalsNeeded: z.literal(1).optional().default(1), + participants: z.array(issueExecutionStageParticipantSchema).default([]), +}); + +export const issueExecutionPolicySchema = z.object({ + mode: z.enum(ISSUE_EXECUTION_POLICY_MODES).optional().default("normal"), + commentRequired: z.boolean().optional().default(true), + stages: z.array(issueExecutionStageSchema).default([]), +}); + +export const issueExecutionStateSchema = z.object({ + status: z.enum(ISSUE_EXECUTION_STATE_STATUSES), + currentStageId: z.string().uuid().nullable(), + currentStageIndex: z.number().int().nonnegative().nullable(), + currentStageType: z.enum(ISSUE_EXECUTION_STAGE_TYPES).nullable(), + currentParticipant: issueExecutionStagePrincipalSchema.nullable(), + returnAssignee: issueExecutionStagePrincipalSchema.nullable(), + completedStageIds: z.array(z.string().uuid()).default([]), + lastDecisionId: z.string().uuid().nullable(), + lastDecisionOutcome: z.enum(ISSUE_EXECUTION_DECISION_OUTCOMES).nullable(), +}); + export const createIssueSchema = z.object({ projectId: z.string().uuid().optional().nullable(), projectWorkspaceId: z.string().uuid().optional().nullable(), @@ -42,15 +128,9 @@ export const createIssueSchema = z.object({ requestDepth: z.number().int().nonnegative().optional().default(0), billingCode: z.string().optional().nullable(), assigneeAdapterOverrides: issueAssigneeAdapterOverridesSchema.optional().nullable(), + executionPolicy: issueExecutionPolicySchema.optional().nullable(), executionWorkspaceId: z.string().uuid().optional().nullable(), - executionWorkspacePreference: z.enum([ - "inherit", - "shared_workspace", - "isolated_workspace", - "operator_branch", - "reuse_existing", - "agent_default", - ]).optional().nullable(), + executionWorkspacePreference: z.enum(ISSUE_EXECUTION_WORKSPACE_PREFERENCES).optional().nullable(), executionWorkspaceSettings: issueExecutionWorkspaceSettingsSchema.optional().nullable(), labelIds: z.array(z.string().uuid()).optional(), }); @@ -120,5 +200,8 @@ export const upsertIssueDocumentSchema = z.object({ baseRevisionId: z.string().uuid().nullable().optional(), }); +export const restoreIssueDocumentRevisionSchema = z.object({}); + export type IssueDocumentFormat = z.infer; export type UpsertIssueDocument = z.infer; +export type RestoreIssueDocumentRevision = z.infer; diff --git a/packages/shared/src/validators/project.ts b/packages/shared/src/validators/project.ts index 89308ff41dc..f0030fb7194 100644 --- a/packages/shared/src/validators/project.ts +++ b/packages/shared/src/validators/project.ts @@ -1,5 +1,6 @@ import { z } from "zod"; import { PROJECT_STATUSES } from "../constants.js"; +import { envConfigSchema } from "./secret.js"; const executionWorkspaceStrategySchema = z .object({ @@ -102,6 +103,7 @@ const projectFields = { leadAgentId: z.string().uuid().optional().nullable(), targetDate: z.string().optional().nullable(), color: z.string().optional().nullable(), + env: envConfigSchema.optional().nullable(), executionWorkspacePolicy: projectExecutionWorkspacePolicySchema.optional().nullable(), archivedAt: z.string().datetime().optional().nullable(), }; diff --git a/packages/shared/src/validators/routine.ts b/packages/shared/src/validators/routine.ts index 966756ab6bb..2498e47bba3 100644 --- a/packages/shared/src/validators/routine.ts +++ b/packages/shared/src/validators/routine.ts @@ -5,19 +5,60 @@ import { ROUTINE_CONCURRENCY_POLICIES, ROUTINE_STATUSES, ROUTINE_TRIGGER_SIGNING_MODES, + ROUTINE_VARIABLE_TYPES, } from "../constants.js"; +import { + ISSUE_EXECUTION_WORKSPACE_PREFERENCES, + issueExecutionWorkspaceSettingsSchema, +} from "./issue.js"; + +const routineVariableValueSchema = z.union([z.string(), z.number().finite(), z.boolean()]); + +export const routineVariableSchema = z.object({ + name: z.string().trim().regex(/^[A-Za-z][A-Za-z0-9_]*$/), + label: z.string().trim().max(120).optional().nullable(), + type: z.enum(ROUTINE_VARIABLE_TYPES).optional().default("text"), + defaultValue: routineVariableValueSchema.optional().nullable(), + required: z.boolean().optional().default(true), + options: z.array(z.string().trim().min(1).max(120)).max(50).optional().default([]), +}).superRefine((value, ctx) => { + if (value.type === "select" && value.options.length === 0) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["options"], + message: "Select variables require at least one option", + }); + } + if (value.type !== "select" && value.options.length > 0) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["options"], + message: "Only select variables can define options", + }); + } + if (value.type === "select" && value.defaultValue != null) { + if (typeof value.defaultValue !== "string" || !value.options.includes(value.defaultValue)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["defaultValue"], + message: "Select variable defaults must match one of the allowed options", + }); + } + } +}); export const createRoutineSchema = z.object({ - projectId: z.string().uuid(), + projectId: z.string().uuid().optional().nullable(), goalId: z.string().uuid().optional().nullable(), parentIssueId: z.string().uuid().optional().nullable(), title: z.string().trim().min(1).max(200), description: z.string().optional().nullable(), - assigneeAgentId: z.string().uuid(), + assigneeAgentId: z.string().uuid().optional().nullable(), priority: z.enum(ISSUE_PRIORITIES).optional().default("medium"), status: z.enum(ROUTINE_STATUSES).optional().default("active"), concurrencyPolicy: z.enum(ROUTINE_CONCURRENCY_POLICIES).optional().default("coalesce_if_active"), catchUpPolicy: z.enum(ROUTINE_CATCH_UP_POLICIES).optional().default("skip_missed"), + variables: z.array(routineVariableSchema).optional().default([]), }); export type CreateRoutine = z.infer; @@ -62,8 +103,14 @@ export type UpdateRoutineTrigger = z.infer; export const runRoutineSchema = z.object({ triggerId: z.string().uuid().optional().nullable(), payload: z.record(z.unknown()).optional().nullable(), + variables: z.record(routineVariableValueSchema).optional().nullable(), + projectId: z.string().uuid().optional().nullable(), + assigneeAgentId: z.string().uuid().optional().nullable(), idempotencyKey: z.string().trim().max(255).optional().nullable(), source: z.enum(["manual", "api"]).optional().default("manual"), + executionWorkspaceId: z.string().uuid().optional().nullable(), + executionWorkspacePreference: z.enum(ISSUE_EXECUTION_WORKSPACE_PREFERENCES).optional().nullable(), + executionWorkspaceSettings: issueExecutionWorkspaceSettingsSchema.optional().nullable(), }); export type RunRoutine = z.infer; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 19c9ffc42ad..d0920d05ed4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,9 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + rollup: '>=4.59.0' + patchedDependencies: embedded-postgres@18.1.0-beta.16: hash: 55uhvnotpqyiy37rn3pqpukhei @@ -251,6 +254,28 @@ importers: specifier: ^3.0.5 version: 3.2.4(@types/debug@4.1.12)(@types/node@24.12.0)(jiti@2.6.1)(jsdom@28.1.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(tsx@4.21.0) + packages/mcp-server: + dependencies: + '@modelcontextprotocol/sdk': + specifier: ^1.29.0 + version: 1.29.0(zod@3.25.76) + '@paperclipai/shared': + specifier: workspace:* + version: link:../shared + zod: + specifier: ^3.24.2 + version: 3.25.76 + devDependencies: + '@types/node': + specifier: ^24.6.0 + version: 24.12.0 + typescript: + specifier: ^5.7.3 + version: 5.9.3 + vitest: + specifier: ^3.0.5 + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.12.0)(jiti@2.6.1)(jsdom@28.1.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(tsx@4.21.0) + packages/plugins/create-paperclip-plugin: dependencies: '@paperclipai/plugin-sdk': @@ -275,10 +300,10 @@ importers: devDependencies: '@rollup/plugin-node-resolve': specifier: ^16.0.1 - version: 16.0.3(rollup@4.57.1) + version: 16.0.3(rollup@4.60.1) '@rollup/plugin-typescript': specifier: ^12.1.2 - version: 12.3.0(rollup@4.57.1)(tslib@2.8.1)(typescript@5.9.3) + version: 12.3.0(rollup@4.60.1)(tslib@2.8.1)(typescript@5.9.3) '@types/node': specifier: ^24.6.0 version: 24.12.0 @@ -289,8 +314,8 @@ importers: specifier: ^0.27.3 version: 0.27.3 rollup: - specifier: ^4.38.0 - version: 4.57.1 + specifier: '>=4.59.0' + version: 4.60.1 tslib: specifier: ^2.8.1 version: 2.8.1 @@ -510,8 +535,8 @@ importers: specifier: ^28.1.0 version: 28.1.0(@noble/hashes@2.0.1) multer: - specifier: ^2.0.2 - version: 2.0.2 + specifier: ^2.1.1 + version: 2.1.1 open: specifier: ^11.0.0 version: 11.0.0 @@ -579,6 +604,9 @@ importers: ui: dependencies: + '@assistant-ui/react': + specifier: 0.12.23 + version: 0.12.23(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)) '@dnd-kit/core': specifier: ^6.3.1 version: 6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -719,6 +747,59 @@ packages: '@asamuzakjp/nwsapi@2.3.9': resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + '@assistant-ui/core@0.1.13': + resolution: {integrity: sha512-UV8j/l3SbyRz51yHEh8wt6k6eV0cJ5wEaXYwi36hMq/+GwIZydzDmX0XHwFaHTB2Ev0nlrTW5RlF6/g2tVPgoQ==} + peerDependencies: + '@assistant-ui/store': ^0.2.6 + '@assistant-ui/tap': ^0.5.7 + '@types/react': '*' + assistant-cloud: ^0.1.25 + react: ^18 || ^19 + zustand: ^5.0.11 + peerDependenciesMeta: + '@types/react': + optional: true + assistant-cloud: + optional: true + react: + optional: true + zustand: + optional: true + + '@assistant-ui/react@0.12.23': + resolution: {integrity: sha512-49ifZszyxGwkWJluy4F9izQmaP5qC4/dYPZnbaQsgmWHS/vi7mRDwbqAA9yy/L3t8vRfG8Ho92UDBj9lqcl11Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^18 || ^19 + react-dom: ^18 || ^19 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@assistant-ui/store@0.2.6': + resolution: {integrity: sha512-rpl+cWrJVimQzjh3plxhm5cgC/v918jffxHT6xEuanL1pPhxNjhHU90N3hH/pkALpkTdv7RH5kGVpIG+4bJj8w==} + peerDependencies: + '@assistant-ui/tap': ^0.5.6 + '@types/react': '*' + react: ^18 || ^19 + peerDependenciesMeta: + '@types/react': + optional: true + + '@assistant-ui/tap@0.5.7': + resolution: {integrity: sha512-AN+xoDd/A5vb2ZqS59k3T0C3tjK3y8ivD2WOBDvvU1pkdahG+lGip83Ol0jBIOevTgXD9kxTM9fK22tXRZNC4g==} + peerDependencies: + '@types/react': '*' + react: ^18 || ^19 + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + '@aws-crypto/crc32@5.2.0': resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} engines: {node: '>=16.0.0'} @@ -1727,6 +1808,12 @@ packages: '@floating-ui/utils@0.2.10': resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + '@hono/node-server@1.19.13': + resolution: {integrity: sha512-TsQLe4i2gvoTtrHje625ngThGBySOgSK3Xo2XRYOdqGN1teR8+I7vchQC46uLJi8OF62YTYA3AhSpumtkhsaKQ==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} @@ -2028,6 +2115,16 @@ packages: '@mermaid-js/parser@1.0.0': resolution: {integrity: sha512-vvK0Hi/VWndxoh03Mmz6wa1KDriSPjS2XMZL/1l19HFwygiObEEoEwSDxOqyLzzAI6J2PU3261JjTMTO7x+BPw==} + '@modelcontextprotocol/sdk@1.29.0': + resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + '@noble/ciphers@2.1.1': resolution: {integrity: sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw==} engines: {node: '>= 20.19.0'} @@ -2214,6 +2311,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-context@1.1.3': + resolution: {integrity: sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-dialog@1.1.15': resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} peerDependencies: @@ -2467,6 +2573,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-primitive@2.1.4': + resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-progress@1.1.7': resolution: {integrity: sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==} peerDependencies: @@ -2781,7 +2900,7 @@ packages: resolution: {integrity: sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: ^2.78.0||^3.0.0||^4.0.0 + rollup: '>=4.59.0' peerDependenciesMeta: rollup: optional: true @@ -2790,7 +2909,7 @@ packages: resolution: {integrity: sha512-7DP0/p7y3t67+NabT9f8oTBFE6gGkto4SA6Np2oudYmZE/m1dt8RB0SjL1msMxFpLo631qjRCcBlAbq1ml/Big==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: ^2.14.0||^3.0.0||^4.0.0 + rollup: '>=4.59.0' tslib: '*' typescript: '>=3.7.0' peerDependenciesMeta: @@ -2803,133 +2922,133 @@ packages: resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + rollup: '>=4.59.0' peerDependenciesMeta: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.57.1': - resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==} + '@rollup/rollup-android-arm-eabi@4.60.1': + resolution: {integrity: sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.57.1': - resolution: {integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==} + '@rollup/rollup-android-arm64@4.60.1': + resolution: {integrity: sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.57.1': - resolution: {integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==} + '@rollup/rollup-darwin-arm64@4.60.1': + resolution: {integrity: sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.57.1': - resolution: {integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==} + '@rollup/rollup-darwin-x64@4.60.1': + resolution: {integrity: sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.57.1': - resolution: {integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==} + '@rollup/rollup-freebsd-arm64@4.60.1': + resolution: {integrity: sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.57.1': - resolution: {integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==} + '@rollup/rollup-freebsd-x64@4.60.1': + resolution: {integrity: sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.57.1': - resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} + '@rollup/rollup-linux-arm-gnueabihf@4.60.1': + resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.57.1': - resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} + '@rollup/rollup-linux-arm-musleabihf@4.60.1': + resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.57.1': - resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} + '@rollup/rollup-linux-arm64-gnu@4.60.1': + resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.57.1': - resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} + '@rollup/rollup-linux-arm64-musl@4.60.1': + resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loong64-gnu@4.57.1': - resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} + '@rollup/rollup-linux-loong64-gnu@4.60.1': + resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-loong64-musl@4.57.1': - resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} + '@rollup/rollup-linux-loong64-musl@4.60.1': + resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.57.1': - resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} + '@rollup/rollup-linux-ppc64-gnu@4.60.1': + resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-ppc64-musl@4.57.1': - resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} + '@rollup/rollup-linux-ppc64-musl@4.60.1': + resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.57.1': - resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} + '@rollup/rollup-linux-riscv64-gnu@4.60.1': + resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.57.1': - resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} + '@rollup/rollup-linux-riscv64-musl@4.60.1': + resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.57.1': - resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} + '@rollup/rollup-linux-s390x-gnu@4.60.1': + resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.57.1': - resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} + '@rollup/rollup-linux-x64-gnu@4.60.1': + resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.57.1': - resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} + '@rollup/rollup-linux-x64-musl@4.60.1': + resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==} cpu: [x64] os: [linux] - '@rollup/rollup-openbsd-x64@4.57.1': - resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} + '@rollup/rollup-openbsd-x64@4.60.1': + resolution: {integrity: sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==} cpu: [x64] os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.57.1': - resolution: {integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==} + '@rollup/rollup-openharmony-arm64@4.60.1': + resolution: {integrity: sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.57.1': - resolution: {integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==} + '@rollup/rollup-win32-arm64-msvc@4.60.1': + resolution: {integrity: sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.57.1': - resolution: {integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==} + '@rollup/rollup-win32-ia32-msvc@4.60.1': + resolution: {integrity: sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.57.1': - resolution: {integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==} + '@rollup/rollup-win32-x64-gnu@4.60.1': + resolution: {integrity: sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.57.1': - resolution: {integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==} + '@rollup/rollup-win32-x64-msvc@4.60.1': + resolution: {integrity: sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==} cpu: [x64] os: [win32] @@ -3565,6 +3684,12 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + assistant-cloud@0.1.25: + resolution: {integrity: sha512-roSJLx5vTYakgBqrf5gKrbajstXVK0RPOZ06AL7X3b4iywf7nJVEV8GYPfKnuPxtgIGX+pIuVZPSb2osiRPmQQ==} + + assistant-stream@0.3.10: + resolution: {integrity: sha512-lgyMePFFzU50NM4x4zdDnGLmZ31smljVz7W9t3ngMlxTrL4XF8VhodxSn5dHSuR6W6zBHf29U7EZSVyu8MzJWw==} + async-exit-hook@2.0.1: resolution: {integrity: sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==} engines: {node: '>=0.12.0'} @@ -3830,6 +3955,10 @@ packages: cookiejar@2.1.4: resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + cose-base@1.0.3: resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} @@ -4338,10 +4467,24 @@ packages: event-emitter@0.3.5: resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} + eventsource-parser@3.0.6: + resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} + express-rate-limit@8.3.2: + resolution: {integrity: sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + express@5.2.1: resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} engines: {node: '>= 18'} @@ -4475,6 +4618,10 @@ packages: resolution: {integrity: sha512-6CP5vxfvY4jY9XJK5zu4ZUL9aB7HHNtEMk6q7m1Pu9Gzoby1Vx5VNmVqte3NUO+1cvVK9Arj1f67xLagWkbo5Q==} engines: {node: '>=20.0.0'} + hono@4.12.12: + resolution: {integrity: sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==} + engines: {node: '>=16.9.0'} + html-encoding-sniffer@6.0.0: resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -4522,6 +4669,10 @@ packages: resolution: {integrity: sha512-fn4bQ0Xq8FTej09YC/jqKZwtijpvARlRp6wxL5WTA6yPe2YWSJ5RJh7Nm79rK2qB0wr6iDQzH60XGq5V/7u8YQ==} deprecated: The Intersection Observer polyfill is no longer needed and can safely be removed. Intersection Observer has been Baseline since 2019. + ip-address@10.1.0: + resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} + engines: {node: '>= 12'} + ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -4617,6 +4768,9 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -4986,10 +5140,6 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - mkdirp@0.5.6: - resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} - hasBin: true - mlly@1.8.1: resolution: {integrity: sha512-SnL6sNutTwRWWR/vcmCYHSADjiEesp5TGQQ0pXyLhW5IoeibRlF/CbSLailbB3CNqJUk9cVJ9dUDnbD7GrcHBQ==} @@ -5000,8 +5150,8 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - multer@2.0.2: - resolution: {integrity: sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==} + multer@2.1.1: + resolution: {integrity: sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==} engines: {node: '>= 10.16.0'} nanoid@3.3.11: @@ -5009,6 +5159,11 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanoid@5.1.7: + resolution: {integrity: sha512-ua3NDgISf6jdwezAheMOk4mbE1LXjm1DfMUDMuJf4AqxLFK3ccGpgWizwa5YV7Yz9EpXwEaWoRXSb/BnV0t5dQ==} + engines: {node: ^18 || >=20} + hasBin: true + nanostores@1.1.0: resolution: {integrity: sha512-yJBmDJr18xy47dbNVlHcgdPrulSn1nhSE6Ns9vTG+Nx9VPT6iV1MD6aQFp/t52zpf82FhLLTXAXr30NuCnxvwA==} engines: {node: ^20.0.0 || >=22.0.0} @@ -5146,6 +5301,10 @@ packages: resolution: {integrity: sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==} hasBin: true + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} @@ -5332,6 +5491,12 @@ packages: '@types/react': optional: true + react-textarea-autosize@8.5.9: + resolution: {integrity: sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==} + engines: {node: '>=10'} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react@19.2.4: resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} engines: {node: '>=0.10.0'} @@ -5375,8 +5540,8 @@ packages: robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} - rollup@4.57.1: - resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==} + rollup@4.60.1: + resolution: {integrity: sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -5720,6 +5885,38 @@ packages: '@types/react': optional: true + use-composed-ref@1.4.0: + resolution: {integrity: sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + use-effect-event@2.0.3: + resolution: {integrity: sha512-fz1en+z3fYXCXx3nMB8hXDMuygBltifNKZq29zDx+xNJ+1vEs6oJlYd9sK31vxJ0YI534VUsHEBY0k2BATsmBQ==} + peerDependencies: + react: ^18.3 || ^19.0.0-0 + + use-isomorphic-layout-effect@1.2.1: + resolution: {integrity: sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + use-latest@1.3.0: + resolution: {integrity: sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + use-sidecar@1.1.3: resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} engines: {node: '>=10'} @@ -5956,12 +6153,35 @@ packages: resolution: {integrity: sha512-kHqDPdltoXH+X4w1lVmMtddE3Oeqq48nM40FD5ojTd8xYhQpzIDcfE2keMSU5bAgRPJBe225WTUdyUgj1DtbiQ==} engines: {node: '>=16.0.0', npm: '>=8.0.0'} + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + peerDependencies: + zod: ^3.25.28 || ^4 + zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} zod@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + zustand@5.0.12: + resolution: {integrity: sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -5992,6 +6212,58 @@ snapshots: '@asamuzakjp/nwsapi@2.3.9': {} + '@assistant-ui/core@0.1.13(@assistant-ui/store@0.2.6(@assistant-ui/tap@0.5.7(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4))(@assistant-ui/tap@0.5.7(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(assistant-cloud@0.1.25)(react@19.2.4)(zustand@5.0.12(@types/react@19.2.14)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)))': + dependencies: + '@assistant-ui/store': 0.2.6(@assistant-ui/tap@0.5.7(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4) + '@assistant-ui/tap': 0.5.7(@types/react@19.2.14)(react@19.2.4) + assistant-stream: 0.3.10 + nanoid: 5.1.7 + optionalDependencies: + '@types/react': 19.2.14 + assistant-cloud: 0.1.25 + react: 19.2.4 + zustand: 5.0.12(@types/react@19.2.14)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)) + + '@assistant-ui/react@0.12.23(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4))': + dependencies: + '@assistant-ui/core': 0.1.13(@assistant-ui/store@0.2.6(@assistant-ui/tap@0.5.7(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4))(@assistant-ui/tap@0.5.7(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(assistant-cloud@0.1.25)(react@19.2.4)(zustand@5.0.12(@types/react@19.2.14)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4))) + '@assistant-ui/store': 0.2.6(@assistant-ui/tap@0.5.7(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4) + '@assistant-ui/tap': 0.5.7(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.3(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.14)(react@19.2.4) + assistant-cloud: 0.1.25 + assistant-stream: 0.3.10 + nanoid: 5.1.7 + radix-ui: 1.4.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + react-textarea-autosize: 8.5.9(@types/react@19.2.14)(react@19.2.4) + zod: 4.3.6 + zustand: 5.0.12(@types/react@19.2.14)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + transitivePeerDependencies: + - immer + - use-sync-external-store + + '@assistant-ui/store@0.2.6(@assistant-ui/tap@0.5.7(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4)': + dependencies: + '@assistant-ui/tap': 0.5.7(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + use-effect-event: 2.0.3(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + + '@assistant-ui/tap@0.5.7(@types/react@19.2.14)(react@19.2.4)': + optionalDependencies: + '@types/react': 19.2.14 + react: 19.2.4 + '@aws-crypto/crc32@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 @@ -7294,6 +7566,10 @@ snapshots: '@floating-ui/utils@0.2.10': {} + '@hono/node-server@1.19.13(hono@4.12.12)': + dependencies: + hono: 4.12.12 + '@iconify/types@2.0.0': {} '@iconify/utils@3.1.0': @@ -7735,6 +8011,28 @@ snapshots: dependencies: langium: 4.2.1 + '@modelcontextprotocol/sdk@1.29.0(zod@3.25.76)': + dependencies: + '@hono/node-server': 1.19.13(hono@4.12.12) + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.0.6 + express: 5.2.1 + express-rate-limit: 8.3.2(express@5.2.1) + hono: 4.12.12 + jose: 6.1.3 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 3.25.76 + zod-to-json-schema: 3.25.2(zod@3.25.76) + transitivePeerDependencies: + - supports-color + '@noble/ciphers@2.1.1': {} '@noble/hashes@1.8.0': {} @@ -7902,6 +8200,12 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + '@radix-ui/react-context@1.1.3(@types/react@19.2.14)(react@19.2.4)': + dependencies: + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.14 + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -8198,6 +8502,15 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-primitive@2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/react-slot': 1.2.4(@types/react@19.2.14)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@radix-ui/react-progress@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) @@ -8527,106 +8840,106 @@ snapshots: '@rolldown/pluginutils@1.0.0-beta.27': {} - '@rollup/plugin-node-resolve@16.0.3(rollup@4.57.1)': + '@rollup/plugin-node-resolve@16.0.3(rollup@4.60.1)': dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.57.1) + '@rollup/pluginutils': 5.3.0(rollup@4.60.1) '@types/resolve': 1.20.2 deepmerge: 4.3.1 is-module: 1.0.0 resolve: 1.22.11 optionalDependencies: - rollup: 4.57.1 + rollup: 4.60.1 - '@rollup/plugin-typescript@12.3.0(rollup@4.57.1)(tslib@2.8.1)(typescript@5.9.3)': + '@rollup/plugin-typescript@12.3.0(rollup@4.60.1)(tslib@2.8.1)(typescript@5.9.3)': dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.57.1) + '@rollup/pluginutils': 5.3.0(rollup@4.60.1) resolve: 1.22.11 typescript: 5.9.3 optionalDependencies: - rollup: 4.57.1 + rollup: 4.60.1 tslib: 2.8.1 - '@rollup/pluginutils@5.3.0(rollup@4.57.1)': + '@rollup/pluginutils@5.3.0(rollup@4.60.1)': dependencies: '@types/estree': 1.0.8 estree-walker: 2.0.2 picomatch: 4.0.3 optionalDependencies: - rollup: 4.57.1 + rollup: 4.60.1 - '@rollup/rollup-android-arm-eabi@4.57.1': + '@rollup/rollup-android-arm-eabi@4.60.1': optional: true - '@rollup/rollup-android-arm64@4.57.1': + '@rollup/rollup-android-arm64@4.60.1': optional: true - '@rollup/rollup-darwin-arm64@4.57.1': + '@rollup/rollup-darwin-arm64@4.60.1': optional: true - '@rollup/rollup-darwin-x64@4.57.1': + '@rollup/rollup-darwin-x64@4.60.1': optional: true - '@rollup/rollup-freebsd-arm64@4.57.1': + '@rollup/rollup-freebsd-arm64@4.60.1': optional: true - '@rollup/rollup-freebsd-x64@4.57.1': + '@rollup/rollup-freebsd-x64@4.60.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + '@rollup/rollup-linux-arm-gnueabihf@4.60.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.57.1': + '@rollup/rollup-linux-arm-musleabihf@4.60.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.57.1': + '@rollup/rollup-linux-arm64-gnu@4.60.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.57.1': + '@rollup/rollup-linux-arm64-musl@4.60.1': optional: true - '@rollup/rollup-linux-loong64-gnu@4.57.1': + '@rollup/rollup-linux-loong64-gnu@4.60.1': optional: true - '@rollup/rollup-linux-loong64-musl@4.57.1': + '@rollup/rollup-linux-loong64-musl@4.60.1': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.57.1': + '@rollup/rollup-linux-ppc64-gnu@4.60.1': optional: true - '@rollup/rollup-linux-ppc64-musl@4.57.1': + '@rollup/rollup-linux-ppc64-musl@4.60.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.57.1': + '@rollup/rollup-linux-riscv64-gnu@4.60.1': optional: true - '@rollup/rollup-linux-riscv64-musl@4.57.1': + '@rollup/rollup-linux-riscv64-musl@4.60.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.57.1': + '@rollup/rollup-linux-s390x-gnu@4.60.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.57.1': + '@rollup/rollup-linux-x64-gnu@4.60.1': optional: true - '@rollup/rollup-linux-x64-musl@4.57.1': + '@rollup/rollup-linux-x64-musl@4.60.1': optional: true - '@rollup/rollup-openbsd-x64@4.57.1': + '@rollup/rollup-openbsd-x64@4.60.1': optional: true - '@rollup/rollup-openharmony-arm64@4.57.1': + '@rollup/rollup-openharmony-arm64@4.60.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.57.1': + '@rollup/rollup-win32-arm64-msvc@4.60.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.57.1': + '@rollup/rollup-win32-ia32-msvc@4.60.1': optional: true - '@rollup/rollup-win32-x64-gnu@4.57.1': + '@rollup/rollup-win32-x64-gnu@4.60.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.57.1': + '@rollup/rollup-win32-x64-msvc@4.60.1': optional: true '@smithy/abort-controller@4.2.8': @@ -9425,6 +9738,16 @@ snapshots: assertion-error@2.0.1: {} + assistant-cloud@0.1.25: + dependencies: + assistant-stream: 0.3.10 + + assistant-stream@0.3.10: + dependencies: + '@standard-schema/spec': 1.1.0 + nanoid: 5.1.7 + secure-json-parse: 4.1.0 + async-exit-hook@2.0.1: {} asynckit@0.4.0: {} @@ -9645,6 +9968,11 @@ snapshots: cookiejar@2.1.4: {} + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + cose-base@1.0.3: dependencies: layout-base: 1.0.2 @@ -10170,8 +10498,19 @@ snapshots: d: 1.0.2 es5-ext: 0.10.64 + eventsource-parser@3.0.6: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.0.6 + expect-type@1.3.0: {} + express-rate-limit@8.3.2(express@5.2.1): + dependencies: + express: 5.2.1 + ip-address: 10.1.0 + express@5.2.1: dependencies: accepts: 2.0.0 @@ -10345,6 +10684,8 @@ snapshots: '@paperclipai/adapter-utils': 2026.325.0 picocolors: 1.1.1 + hono@4.12.12: {} + html-encoding-sniffer@6.0.0(@noble/hashes@2.0.1): dependencies: '@exodus/bytes': 1.15.0(@noble/hashes@2.0.1) @@ -10395,6 +10736,8 @@ snapshots: intersection-observer@0.10.0: {} + ip-address@10.1.0: {} + ipaddr.js@1.9.1: {} is-alphabetical@2.0.1: {} @@ -10481,6 +10824,8 @@ snapshots: json-schema-traverse@1.0.0: {} + json-schema-typed@8.0.2: {} + json5@2.2.3: {} katex@0.16.37: @@ -11123,10 +11468,6 @@ snapshots: minimist@1.2.8: {} - mkdirp@0.5.6: - dependencies: - minimist: 1.2.8 - mlly@1.8.1: dependencies: acorn: 8.16.0 @@ -11138,18 +11479,17 @@ snapshots: ms@2.1.3: {} - multer@2.0.2: + multer@2.1.1: dependencies: append-field: 1.0.0 busboy: 1.6.0 concat-stream: 2.0.0 - mkdirp: 0.5.6 - object-assign: 4.1.1 type-is: 1.6.18 - xtend: 4.0.2 nanoid@3.3.11: {} + nanoid@5.1.7: {} + nanostores@1.1.0: {} negotiator@1.0.0: {} @@ -11303,6 +11643,8 @@ snapshots: sonic-boom: 4.2.1 thread-stream: 3.1.0 + pkce-challenge@5.0.1: {} + pkg-types@1.3.1: dependencies: confbox: 0.1.8 @@ -11534,6 +11876,15 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + react-textarea-autosize@8.5.9(@types/react@19.2.14)(react@19.2.4): + dependencies: + '@babel/runtime': 7.28.6 + react: 19.2.4 + use-composed-ref: 1.4.0(@types/react@19.2.14)(react@19.2.4) + use-latest: 1.3.0(@types/react@19.2.14)(react@19.2.4) + transitivePeerDependencies: + - '@types/react' + react@19.2.4: {} readable-stream@3.6.2: @@ -11592,35 +11943,35 @@ snapshots: robust-predicates@3.0.2: {} - rollup@4.57.1: + rollup@4.60.1: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.57.1 - '@rollup/rollup-android-arm64': 4.57.1 - '@rollup/rollup-darwin-arm64': 4.57.1 - '@rollup/rollup-darwin-x64': 4.57.1 - '@rollup/rollup-freebsd-arm64': 4.57.1 - '@rollup/rollup-freebsd-x64': 4.57.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.57.1 - '@rollup/rollup-linux-arm-musleabihf': 4.57.1 - '@rollup/rollup-linux-arm64-gnu': 4.57.1 - '@rollup/rollup-linux-arm64-musl': 4.57.1 - '@rollup/rollup-linux-loong64-gnu': 4.57.1 - '@rollup/rollup-linux-loong64-musl': 4.57.1 - '@rollup/rollup-linux-ppc64-gnu': 4.57.1 - '@rollup/rollup-linux-ppc64-musl': 4.57.1 - '@rollup/rollup-linux-riscv64-gnu': 4.57.1 - '@rollup/rollup-linux-riscv64-musl': 4.57.1 - '@rollup/rollup-linux-s390x-gnu': 4.57.1 - '@rollup/rollup-linux-x64-gnu': 4.57.1 - '@rollup/rollup-linux-x64-musl': 4.57.1 - '@rollup/rollup-openbsd-x64': 4.57.1 - '@rollup/rollup-openharmony-arm64': 4.57.1 - '@rollup/rollup-win32-arm64-msvc': 4.57.1 - '@rollup/rollup-win32-ia32-msvc': 4.57.1 - '@rollup/rollup-win32-x64-gnu': 4.57.1 - '@rollup/rollup-win32-x64-msvc': 4.57.1 + '@rollup/rollup-android-arm-eabi': 4.60.1 + '@rollup/rollup-android-arm64': 4.60.1 + '@rollup/rollup-darwin-arm64': 4.60.1 + '@rollup/rollup-darwin-x64': 4.60.1 + '@rollup/rollup-freebsd-arm64': 4.60.1 + '@rollup/rollup-freebsd-x64': 4.60.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.1 + '@rollup/rollup-linux-arm-musleabihf': 4.60.1 + '@rollup/rollup-linux-arm64-gnu': 4.60.1 + '@rollup/rollup-linux-arm64-musl': 4.60.1 + '@rollup/rollup-linux-loong64-gnu': 4.60.1 + '@rollup/rollup-linux-loong64-musl': 4.60.1 + '@rollup/rollup-linux-ppc64-gnu': 4.60.1 + '@rollup/rollup-linux-ppc64-musl': 4.60.1 + '@rollup/rollup-linux-riscv64-gnu': 4.60.1 + '@rollup/rollup-linux-riscv64-musl': 4.60.1 + '@rollup/rollup-linux-s390x-gnu': 4.60.1 + '@rollup/rollup-linux-x64-gnu': 4.60.1 + '@rollup/rollup-linux-x64-musl': 4.60.1 + '@rollup/rollup-openbsd-x64': 4.60.1 + '@rollup/rollup-openharmony-arm64': 4.60.1 + '@rollup/rollup-win32-arm64-msvc': 4.60.1 + '@rollup/rollup-win32-ia32-msvc': 4.60.1 + '@rollup/rollup-win32-x64-gnu': 4.60.1 + '@rollup/rollup-win32-x64-msvc': 4.60.1 fsevents: 2.3.3 rou3@0.7.12: {} @@ -11998,6 +12349,29 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + use-composed-ref@1.4.0(@types/react@19.2.14)(react@19.2.4): + dependencies: + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.14 + + use-effect-event@2.0.3(react@19.2.4): + dependencies: + react: 19.2.4 + + use-isomorphic-layout-effect@1.2.1(@types/react@19.2.14)(react@19.2.4): + dependencies: + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.14 + + use-latest@1.3.0(@types/react@19.2.14)(react@19.2.4): + dependencies: + react: 19.2.4 + use-isomorphic-layout-effect: 1.2.1(@types/react@19.2.14)(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + use-sidecar@1.1.3(@types/react@19.2.14)(react@19.2.4): dependencies: detect-node-es: 1.1.0 @@ -12081,7 +12455,7 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.57.1 + rollup: 4.60.1 tinyglobby: 0.2.15 optionalDependencies: '@types/node': 24.12.0 @@ -12096,7 +12470,7 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.57.1 + rollup: 4.60.1 tinyglobby: 0.2.15 optionalDependencies: '@types/node': 25.2.3 @@ -12111,7 +12485,7 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.57.1 + rollup: 4.60.1 tinyglobby: 0.2.15 optionalDependencies: '@types/node': 24.12.0 @@ -12126,7 +12500,7 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.57.1 + rollup: 4.60.1 tinyglobby: 0.2.15 optionalDependencies: '@types/node': 25.2.3 @@ -12286,8 +12660,18 @@ snapshots: dependencies: lib0: 0.2.117 + zod-to-json-schema@3.25.2(zod@3.25.76): + dependencies: + zod: 3.25.76 + zod@3.25.76: {} zod@4.3.6: {} + zustand@5.0.12(@types/react@19.2.14)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)): + optionalDependencies: + '@types/react': 19.2.14 + react: 19.2.4 + use-sync-external-store: 1.6.0(react@19.2.4) + zwitch@2.0.4: {} diff --git a/releases/v2026.403.0.md b/releases/v2026.403.0.md new file mode 100644 index 00000000000..585772c07ff --- /dev/null +++ b/releases/v2026.403.0.md @@ -0,0 +1,68 @@ +# v2026.403.0 + +> Released: 2026-04-03 + +## Highlights + +- **Inbox overhaul** — New "Mine" inbox tab with mail-client keyboard shortcuts (j/k navigation, a/y archive, o open), swipe-to-archive, "Mark all as read" button, operator search with keyboard controls, and a "Today" divider. Read/dismissed state now extends to all inbox item types. ([#2072](https://github.com/paperclipai/paperclip/pull/2072), [#2540](https://github.com/paperclipai/paperclip/pull/2540)) +- **Feedback and evals** — Thumbs-up/down feedback capture flow with voting UI, feedback modal styling, and run link placement in the feedback row. ([#2529](https://github.com/paperclipai/paperclip/pull/2529)) +- **Document revisions** — Issue document revision history with a restore flow, replay-safe migrations, and revision tracking API. ([#2317](https://github.com/paperclipai/paperclip/pull/2317)) +- **Telemetry** — Anonymized App-side telemetry. Disable with `DO_NOT_TRACK=1` or `PAPERCLIP_TELEMETRY_DISABLED=1` ([#2527](https://github.com/paperclipai/paperclip/pull/2527)) +- **Execution workspaces (EXPERIMENTAL)** — Full workspace lifecycle management for agent runs: workspace-aware routine runs, execution workspace detail pages with linked issues, runtime controls (start/stop), close readiness checks, and follow-up issue workspace inheritance. Project workspaces get their own detail pages and a dedicated tab on the project view. ([#2074](https://github.com/paperclipai/paperclip/pull/2074), [#2203](https://github.com/paperclipai/paperclip/pull/2203)) + +## Improvements + +- **Comment interrupts** — New interrupt support for issue comments with queued comment thread UX. +- **Docker improvements** — Improved base image organization, host UID/GID mapping for volume mounts, and Docker file structure. ([#2407](https://github.com/paperclipai/paperclip/pull/2407), [#1923](https://github.com/paperclipai/paperclip/pull/1923), @radiusred) +- **Optimistic comments** — Comments render instantly with optimistic IDs while the server confirms; draft clearing is fixed for a smoother composing experience. +- **GitHub Enterprise URL support** — Skill and company imports now accept GitHub Enterprise URLs with hardened GHE URL detection and shared GitHub helpers. ([#2449](https://github.com/paperclipai/paperclip/pull/2449), @statxc) +- **Gemini local adapter** — Added `gemini_local` to the adapter types validation enum so Gemini agents no longer fail validation. ([#2430](https://github.com/paperclipai/paperclip/pull/2430), @bittoby) +- **Routines skill** — New `paperclip-routines` skill with documentation moved into Paperclip references. Routine runs now support workspace awareness and variables. ([#2414](https://github.com/paperclipai/paperclip/pull/2414), @aronprins) +- **GPT-5.4 and xhigh effort** — Added GPT-5.4 model fallback and xhigh effort options for OpenAI-based adapters. ([#112](https://github.com/paperclipai/paperclip/pull/112), @kevmok) +- **Commit metrics** — New Paperclip commit metrics script with filtered exports and edge case handling. +- **CLI onboarding** — Onboarding reruns now preserve existing config; exported tsx CLI entrypoint for cleaner startup. ([#2071](https://github.com/paperclipai/paperclip/pull/2071)) +- **Board delegation guide** — New documentation for board-operator delegation patterns. ([#1889](https://github.com/paperclipai/paperclip/pull/1889)) +- **Agent capabilities in org chart** — Agent capabilities field now renders on org chart cards. ([#2349](https://github.com/paperclipai/paperclip/pull/2349)) +- **PR template updates** — Added Model Used section to PR template; CONTRIBUTING.md now requires PR template, Greptile 5/5, and tests. ([#2552](https://github.com/paperclipai/paperclip/pull/2552), [#2618](https://github.com/paperclipai/paperclip/pull/2618)) +- **Hermes adapter upgrade** — Upgraded hermes-paperclip-adapter with UI adapter and skills support, plus detectModel improvements. +- **Markdown editor monospace** — Agent instruction file editors now use monospace font. ([#2620](https://github.com/paperclipai/paperclip/pull/2620)) +- **Markdown link styling** — Links in markdown now render with underline and pointer cursor. +- **@-mention autocomplete** — Mention autocomplete in project descriptions now renders via portal to prevent overflow clipping. +- **Skipped wakeup messages** — Agent detail view now surfaces skipped wakeup messages for better observability. + +## Fixes + +- **Inbox ordering** — Self-touched issues no longer sink to the bottom of the inbox. ([#2144](https://github.com/paperclipai/paperclip/pull/2144)) +- **Env var type switching** — Switching an env var from Plain to Secret no longer loses the value; dropdown snap-back when switching is fixed. ([#2327](https://github.com/paperclipai/paperclip/pull/2327), @radiusred) +- **Adapter type switching** — Adapter-agnostic keys are now preserved when changing adapter type. +- **Project slug collisions** — Non-ASCII project names no longer produce duplicate slugs; a short UUID suffix is appended. ([#2328](https://github.com/paperclipai/paperclip/pull/2328), @bittoby) +- **Codex RPC spawn error** — Fixed CodexRpcClient crash on ENOENT when spawning Codex. ([#2048](https://github.com/paperclipai/paperclip/pull/2048), @remdev) +- **Heartbeat session reuse** — Fixed stale session reuse across heartbeat runs. ([#2065](https://github.com/paperclipai/paperclip/pull/2065), @edimuj) +- **Vite HMR with reverse proxy** — Fixed WebSocket HMR connections behind reverse proxies and added StrictMode guard. ([#2171](https://github.com/paperclipai/paperclip/pull/2171)) +- **Copy button fallback** — Copy-to-clipboard now works in non-secure (HTTP) contexts. ([#2472](https://github.com/paperclipai/paperclip/pull/2472)) +- **Worktree default branch** — Worktree creation auto-detects the default branch when baseRef is not configured. ([#2463](https://github.com/paperclipai/paperclip/pull/2463)) +- **Session continuity** — Timer and heartbeat wakes now preserve session continuity. +- **Worktree isolation** — Fixed worktree provision isolation, runtime recovery, and sibling port collisions. +- **Cursor adapter auth** — Cursor adapter now checks native auth before warning about missing API key. +- **Codex skill injection** — Fixed skill injection to use effective `$CODEX_HOME/skills/` instead of cwd. +- **OpenCode config pollution** — Prevented `opencode.json` config pollution in workspace directories. +- **Pi adapter** — Fixed Pi local adapter execution, transcript parsing, and model detection from stderr. +- **x-forwarded-host origin check** — Board mutation origin check now includes x-forwarded-host header. +- **Health DB probe** — Fixed database connectivity health check probe. +- **Issue breadcrumb routing** — Hardened issue breadcrumb source routing. +- **Instructions tab width** — Removed max-w-6xl constraint from instructions tab for full-width content. ([#2621](https://github.com/paperclipai/paperclip/pull/2621)) +- **Shell fallback on Windows** — Uses `sh` instead of `/bin/sh` as shell fallback on Windows. ([#891](https://github.com/paperclipai/paperclip/pull/891)) +- **Feedback migration** — Made feedback migration replay-safe after rebase. +- **Issue detail polish** — Polished issue detail timelines and attachments display. + +## Upgrade Guide + +Four new database migrations (`0045`–`0048`) will run automatically on startup. These migrations add workspace lifecycle columns, routine variables, feedback tables, and document revision tracking. All migrations are additive — no existing data is modified. + +If you use execution workspaces, note that follow-up issues now automatically inherit workspace linkage from their parent. For non-child follow-ups tied to the same workspace, set `inheritExecutionWorkspaceFromIssueId` explicitly when creating the issue. + +## Contributors + +Thank you to everyone who contributed to this release! + +@aronprins, @bittoby, @cryppadotta, @edimuj, @HenkDz, @kevmok, @mvanhorn, @radiusred, @remdev, @statxc, @vanductai diff --git a/scripts/dev-runner-output.mjs b/scripts/dev-runner-output.mjs new file mode 100644 index 00000000000..50f5076d0cb --- /dev/null +++ b/scripts/dev-runner-output.mjs @@ -0,0 +1,93 @@ +const DEFAULT_CAPTURED_OUTPUT_BYTES = 256 * 1024; +const DEFAULT_JSON_RESPONSE_BYTES = 64 * 1024; + +function normalizeByteLimit(maxBytes) { + return Math.max(1, Math.trunc(maxBytes)); +} + +export function createCapturedOutputBuffer(maxBytes = DEFAULT_CAPTURED_OUTPUT_BYTES) { + const limit = normalizeByteLimit(maxBytes); + const chunks = []; + let bufferedBytes = 0; + let totalBytes = 0; + let truncated = false; + + return { + append(chunk) { + if (chunk === null || chunk === undefined) return; + const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); + if (buffer.length === 0) return; + + chunks.push(buffer); + bufferedBytes += buffer.length; + totalBytes += buffer.length; + + while (bufferedBytes > limit && chunks.length > 0) { + const overflow = bufferedBytes - limit; + const head = chunks[0]; + if (head.length <= overflow) { + chunks.shift(); + bufferedBytes -= head.length; + truncated = true; + continue; + } + + chunks[0] = head.subarray(overflow); + bufferedBytes -= overflow; + truncated = true; + } + }, + + finish() { + const body = Buffer.concat(chunks).toString("utf8"); + if (!truncated) { + return { + text: body, + truncated, + totalBytes, + }; + } + + return { + text: `[output truncated to last ${limit} bytes; total ${totalBytes} bytes]\n${body}`, + truncated, + totalBytes, + }; + }, + }; +} + +export async function parseJsonResponseWithLimit(response, maxBytes = DEFAULT_JSON_RESPONSE_BYTES) { + const limit = normalizeByteLimit(maxBytes); + const contentLength = Number.parseInt(response.headers.get("content-length") ?? "", 10); + if (Number.isFinite(contentLength) && contentLength > limit) { + throw new Error(`Response exceeds ${limit} bytes`); + } + + if (!response.body) { + return JSON.parse(""); + } + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let text = ""; + let totalBytes = 0; + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + totalBytes += value.byteLength; + if (totalBytes > limit) { + await reader.cancel("response too large"); + throw new Error(`Response exceeds ${limit} bytes`); + } + text += decoder.decode(value, { stream: true }); + } + text += decoder.decode(); + } finally { + reader.releaseLock(); + } + + return JSON.parse(text); +} diff --git a/scripts/dev-runner-output.ts b/scripts/dev-runner-output.ts new file mode 100644 index 00000000000..029e7bfe59f --- /dev/null +++ b/scripts/dev-runner-output.ts @@ -0,0 +1,102 @@ +const DEFAULT_CAPTURED_OUTPUT_BYTES = 256 * 1024; +const DEFAULT_JSON_RESPONSE_BYTES = 64 * 1024; + +export type CapturedOutput = { + text: string; + truncated: boolean; + totalBytes: number; +}; + +function normalizeByteLimit(maxBytes: number) { + return Math.max(1, Math.trunc(maxBytes)); +} + +export function createCapturedOutputBuffer(maxBytes = DEFAULT_CAPTURED_OUTPUT_BYTES) { + const limit = normalizeByteLimit(maxBytes); + const chunks: Buffer[] = []; + let bufferedBytes = 0; + let totalBytes = 0; + let truncated = false; + + return { + append(chunk: Buffer | string | null | undefined) { + if (chunk === null || chunk === undefined) return; + const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); + if (buffer.length === 0) return; + + chunks.push(buffer); + bufferedBytes += buffer.length; + totalBytes += buffer.length; + + while (bufferedBytes > limit && chunks.length > 0) { + const overflow = bufferedBytes - limit; + const head = chunks[0]!; + if (head.length <= overflow) { + chunks.shift(); + bufferedBytes -= head.length; + truncated = true; + continue; + } + + chunks[0] = head.subarray(overflow); + bufferedBytes -= overflow; + truncated = true; + } + }, + + finish(): CapturedOutput { + const body = Buffer.concat(chunks).toString("utf8"); + if (!truncated) { + return { + text: body, + truncated, + totalBytes, + }; + } + + return { + text: `[output truncated to last ${limit} bytes; total ${totalBytes} bytes]\n${body}`, + truncated, + totalBytes, + }; + }, + }; +} + +export async function parseJsonResponseWithLimit( + response: Response, + maxBytes = DEFAULT_JSON_RESPONSE_BYTES, +): Promise { + const limit = normalizeByteLimit(maxBytes); + const contentLength = Number.parseInt(response.headers.get("content-length") ?? "", 10); + if (Number.isFinite(contentLength) && contentLength > limit) { + throw new Error(`Response exceeds ${limit} bytes`); + } + + if (!response.body) { + throw new Error("Response has no body"); + } + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let text = ""; + let totalBytes = 0; + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + totalBytes += value.byteLength; + if (totalBytes > limit) { + await reader.cancel("response too large"); + throw new Error(`Response exceeds ${limit} bytes`); + } + text += decoder.decode(value, { stream: true }); + } + text += decoder.decode(); + } finally { + reader.releaseLock(); + } + + return JSON.parse(text) as T; +} diff --git a/scripts/dev-runner.mjs b/scripts/dev-runner.mjs index 091dbb19f61..4f4f7c907f0 100644 --- a/scripts/dev-runner.mjs +++ b/scripts/dev-runner.mjs @@ -5,6 +5,7 @@ import path from "node:path"; import { createInterface } from "node:readline/promises"; import { stdin, stdout } from "node:process"; import { fileURLToPath } from "node:url"; +import { createCapturedOutputBuffer, parseJsonResponseWithLimit } from "./dev-runner-output.mjs"; import { shouldTrackDevServerPath } from "./dev-runner-paths.mjs"; const mode = process.argv[2] === "watch" ? "watch" : "dev"; @@ -250,30 +251,33 @@ async function runPnpm(args, options = {}) { const spawned = spawn(pnpmBin, args, { stdio: options.stdio ?? ["ignore", "pipe", "pipe"], env: options.env ?? process.env, + cwd: options.cwd, shell: process.platform === "win32", }); - let stdoutBuffer = ""; - let stderrBuffer = ""; + const stdoutBuffer = createCapturedOutputBuffer(); + const stderrBuffer = createCapturedOutputBuffer(); if (spawned.stdout) { spawned.stdout.on("data", (chunk) => { - stdoutBuffer += String(chunk); + stdoutBuffer.append(chunk); }); } if (spawned.stderr) { spawned.stderr.on("data", (chunk) => { - stderrBuffer += String(chunk); + stderrBuffer.append(chunk); }); } spawned.on("error", reject); spawned.on("exit", (code, signal) => { + const stdout = stdoutBuffer.finish(); + const stderr = stderrBuffer.finish(); resolve({ code: code ?? 0, signal, - stdout: stdoutBuffer, - stderr: stderrBuffer, + stdout: stdout.text, + stderr: stderr.text, }); }); }); @@ -426,7 +430,7 @@ async function getDevHealthPayload() { if (!response.ok) { throw new Error(`Health request failed (${response.status})`); } - return await response.json(); + return await parseJsonResponseWithLimit(response); } async function waitForChildExit() { diff --git a/scripts/docker-build-test.sh b/scripts/docker-build-test.sh new file mode 100755 index 00000000000..1cdb8d4fa79 --- /dev/null +++ b/scripts/docker-build-test.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# Verify the Docker image builds successfully. +# Skips gracefully when docker/podman is not available. +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" + +# Detect container runtime +if command -v docker >/dev/null 2>&1; then + RUNTIME=docker +elif command -v podman >/dev/null 2>&1; then + RUNTIME=podman +else + echo "SKIP: neither docker nor podman found — skipping build test" + exit 0 +fi + +# Verify the daemon is reachable (docker may be installed but not running) +if ! "$RUNTIME" info >/dev/null 2>&1; then + echo "SKIP: $RUNTIME is installed but not running — skipping build test" + exit 0 +fi + +IMAGE_TAG="paperclip-build-test:$$" +trap '"$RUNTIME" rmi "$IMAGE_TAG" >/dev/null 2>&1 || true' EXIT + +echo "==> Testing Docker build with $RUNTIME" +"$RUNTIME" build \ + -f "$REPO_ROOT/Dockerfile" \ + -t "$IMAGE_TAG" \ + --target production \ + "$REPO_ROOT" + +echo "==> Verifying key binaries in image" +"$RUNTIME" run --rm "$IMAGE_TAG" sh -c ' + set -e + node --version + git --version + gh --version + rg --version + python3 --version + curl --version | head -1 + claude --version 2>/dev/null || echo "claude CLI not found (OK in minimal builds)" +' + +echo "PASS: Docker build test succeeded" diff --git a/scripts/docker-entrypoint.sh b/scripts/docker-entrypoint.sh new file mode 100644 index 00000000000..eb9078d4fd7 --- /dev/null +++ b/scripts/docker-entrypoint.sh @@ -0,0 +1,29 @@ +#!/bin/sh +set -e + +# Capture runtime UID/GID from environment variables, defaulting to 1000 +PUID=${USER_UID:-1000} +PGID=${USER_GID:-1000} + +# Adjust the node user's UID/GID if they differ from the runtime request +# and fix volume ownership only when a remap is needed +changed=0 + +if [ "$(id -u node)" -ne "$PUID" ]; then + echo "Updating node UID to $PUID" + usermod -o -u "$PUID" node + changed=1 +fi + +if [ "$(id -g node)" -ne "$PGID" ]; then + echo "Updating node GID to $PGID" + groupmod -o -g "$PGID" node + usermod -g "$PGID" node + changed=1 +fi + +if [ "$changed" = "1" ]; then + chown -R node:node /paperclip +fi + +exec gosu node "$@" diff --git a/scripts/docker-onboard-smoke.sh b/scripts/docker-onboard-smoke.sh index 97f6743f228..c2830ff0f5a 100755 --- a/scripts/docker-onboard-smoke.sh +++ b/scripts/docker-onboard-smoke.sh @@ -242,7 +242,7 @@ echo "==> Building onboard smoke image" docker build \ --build-arg PAPERCLIPAI_VERSION="$PAPERCLIPAI_VERSION" \ --build-arg HOST_UID="$HOST_UID" \ - -f "$REPO_ROOT/Dockerfile.onboard-smoke" \ + -f "$REPO_ROOT/docker/Dockerfile.onboard-smoke" \ -t "$IMAGE_NAME" \ "$REPO_ROOT" diff --git a/scripts/kill-agent-browsers.sh b/scripts/kill-agent-browsers.sh new file mode 100755 index 00000000000..c89fa96e7ff --- /dev/null +++ b/scripts/kill-agent-browsers.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# +# Kill all "Google Chrome for Testing" processes (agent headless browsers). +# +# Usage: +# scripts/kill-agent-browsers.sh # kill all +# scripts/kill-agent-browsers.sh --dry # preview what would be killed +# + +set -euo pipefail + +DRY_RUN=false +if [[ "${1:-}" == "--dry" || "${1:-}" == "--dry-run" || "${1:-}" == "-n" ]]; then + DRY_RUN=true +fi + +pids=() +lines=() + +while IFS= read -r line; do + [[ -z "$line" ]] && continue + pid=$(echo "$line" | awk '{print $2}') + pids+=("$pid") + lines+=("$line") +done < <(ps aux | grep 'Google Chrome for Testing' | grep -v grep || true) + +if [[ ${#pids[@]} -eq 0 ]]; then + echo "No Google Chrome for Testing processes found." + exit 0 +fi + +echo "Found ${#pids[@]} Google Chrome for Testing process(es):" +echo "" + +for i in "${!pids[@]}"; do + line="${lines[$i]}" + pid=$(echo "$line" | awk '{print $2}') + start=$(echo "$line" | awk '{print $9}') + cmd=$(echo "$line" | awk '{for(i=11;i<=NF;i++) printf "%s ", $i; print ""}') + cmd=$(echo "$cmd" | sed "s|$HOME/||g") + printf " PID %-7s started %-10s %s\n" "$pid" "$start" "$cmd" +done + +echo "" + +if [[ "$DRY_RUN" == true ]]; then + echo "Dry run — re-run without --dry to kill these processes." + exit 0 +fi + +echo "Sending SIGTERM..." +for pid in "${pids[@]}"; do + kill -TERM "$pid" 2>/dev/null && echo " signaled $pid" || echo " $pid already gone" +done + +sleep 2 + +for pid in "${pids[@]}"; do + if kill -0 "$pid" 2>/dev/null; then + echo " $pid still alive, sending SIGKILL..." + kill -KILL "$pid" 2>/dev/null || true + fi +done + +echo "Done." diff --git a/scripts/provision-worktree.sh b/scripts/provision-worktree.sh index e8af52d09d0..0d2eea8f3fc 100644 --- a/scripts/provision-worktree.sh +++ b/scripts/provision-worktree.sh @@ -62,6 +62,19 @@ run_isolated_worktree_init() { return 127 } +run_isolated_worktree_init() { + run_paperclipai_command \ + worktree \ + init \ + --force \ + --seed-mode \ + minimal \ + --name \ + "$worktree_name" \ + --from-config \ + "$source_config_path" +} + write_fallback_worktree_config() { WORKTREE_NAME="$worktree_name" \ BASE_CWD="$base_cwd" \ @@ -324,6 +337,95 @@ else write_fallback_worktree_config fi +disable_seeded_routines() { + local company_id="${PAPERCLIP_COMPANY_ID:-}" + if [[ -z "$company_id" ]]; then + echo "PAPERCLIP_COMPANY_ID not set; skipping routine disable post-step." >&2 + return 0 + fi + + if ! run_paperclipai_command routines disable-all --config "$worktree_config_path" --company-id "$company_id"; then + echo "paperclipai CLI not available in this workspace; skipping routine disable post-step." >&2 + fi +} + +disable_seeded_routines + +list_base_node_modules_paths() { + cd "$base_cwd" && + find . \ + -mindepth 1 \ + -maxdepth 4 \ + -type d \ + -name node_modules \ + ! -path './.git/*' \ + ! -path './.paperclip/*' \ + | sed 's#^\./##' +} +if [[ -f "$worktree_cwd/package.json" && -f "$worktree_cwd/pnpm-lock.yaml" ]]; then + needs_install=0 + + while IFS= read -r relative_path; do + [[ -n "$relative_path" ]] || continue + target_path="$worktree_cwd/$relative_path" + + if [[ -L "$target_path" || ! -e "$target_path" ]]; then + needs_install=1 + break + fi + done < <(list_base_node_modules_paths) + + if [[ "$needs_install" -eq 1 ]]; then + backup_suffix=".paperclip-backup-${BASHPID:-$$}" + moved_symlink_paths=() + + while IFS= read -r relative_path; do + [[ -n "$relative_path" ]] || continue + target_path="$worktree_cwd/$relative_path" + if [[ -L "$target_path" ]]; then + backup_path="${target_path}${backup_suffix}" + rm -rf "$backup_path" + mv "$target_path" "$backup_path" + moved_symlink_paths+=("$relative_path") + fi + done < <(list_base_node_modules_paths) + + restore_moved_symlinks() { + local relative_path target_path backup_path + [[ ${#moved_symlink_paths[@]} -gt 0 ]] || return 0 + for relative_path in "${moved_symlink_paths[@]}"; do + target_path="$worktree_cwd/$relative_path" + backup_path="${target_path}${backup_suffix}" + [[ -L "$backup_path" ]] || continue + rm -rf "$target_path" + mv "$backup_path" "$target_path" + done + } + + cleanup_moved_symlinks() { + local relative_path target_path backup_path + [[ ${#moved_symlink_paths[@]} -gt 0 ]] || return 0 + for relative_path in "${moved_symlink_paths[@]}"; do + target_path="$worktree_cwd/$relative_path" + backup_path="${target_path}${backup_suffix}" + [[ -L "$backup_path" ]] && rm "$backup_path" + done + } + + ( + cd "$worktree_cwd" + pnpm install --frozen-lockfile + ) || { + restore_moved_symlinks + exit 1 + } + + cleanup_moved_symlinks + fi + + exit 0 +fi + while IFS= read -r relative_path; do [[ -n "$relative_path" ]] || continue source_path="$base_cwd/$relative_path" @@ -335,13 +437,5 @@ while IFS= read -r relative_path; do mkdir -p "$(dirname "$target_path")" ln -s "$source_path" "$target_path" done < <( - cd "$base_cwd" && - find . \ - -mindepth 1 \ - -maxdepth 3 \ - -type d \ - -name node_modules \ - ! -path './.git/*' \ - ! -path './.paperclip/*' \ - | sed 's#^\./##' + list_base_node_modules_paths ) diff --git a/scripts/release-package-map.mjs b/scripts/release-package-map.mjs index 79956373502..60deba31044 100644 --- a/scripts/release-package-map.mjs +++ b/scripts/release-package-map.mjs @@ -123,11 +123,14 @@ function setVersion(version) { `.version("${version}")`, ); - if (cliEntry === nextCliEntry) { - throw new Error("failed to rewrite CLI version string in cli/src/index.ts"); + if (cliEntry !== nextCliEntry) { + writeFileSync(cliEntryPath, nextCliEntry); + return; } - writeFileSync(cliEntryPath, nextCliEntry); + if (!cliEntry.includes(".version(cliVersion)")) { + throw new Error("failed to rewrite CLI version string in cli/src/index.ts"); + } } function listPackages() { diff --git a/scripts/screenshot.cjs b/scripts/screenshot.cjs new file mode 100644 index 00000000000..f267bd0653b --- /dev/null +++ b/scripts/screenshot.cjs @@ -0,0 +1,92 @@ +#!/usr/bin/env node +/** + * Screenshot utility for Paperclip UI. + * + * Reads the board token from ~/.paperclip/auth.json and injects it as a + * Bearer header so Playwright can access authenticated pages. + * + * Usage: + * node scripts/screenshot.cjs [output.png] [--width 1280] [--height 800] [--wait 2000] + * + * Examples: + * node scripts/screenshot.cjs /PAPA/agents/cto/instructions /tmp/shot.png + * node scripts/screenshot.cjs http://localhost:5173/PAPA/agents/cto/instructions + */ + +const fs = require("fs"); +const path = require("path"); +const os = require("os"); + +// --- CLI args ----------------------------------------------------------- +const args = process.argv.slice(2); +function flag(name, fallback) { + const i = args.indexOf(`--${name}`); + if (i === -1) return fallback; + const val = args.splice(i, 2)[1]; + return Number.isNaN(Number(val)) ? fallback : Number(val); +} +const width = flag("width", 1280); +const height = flag("height", 800); +const waitMs = flag("wait", 2000); + +const rawUrl = args[0]; +const outPath = args[1] || "/tmp/paperclip-screenshot.png"; + +if (!rawUrl) { + console.error("Usage: node scripts/screenshot.cjs [output.png]"); + process.exit(1); +} + +// --- Auth ---------------------------------------------------------------- +function loadBoardToken() { + const authPath = path.resolve(os.homedir(), ".paperclip/auth.json"); + try { + const auth = JSON.parse(fs.readFileSync(authPath, "utf-8")); + const creds = auth.credentials || {}; + const entry = Object.values(creds)[0]; + if (entry && entry.token && entry.apiBase) return { token: entry.token, apiBase: entry.apiBase }; + } catch (_) { + // ignore + } + return null; +} + +const cred = loadBoardToken(); +if (!cred) { + console.error("No board token found in ~/.paperclip/auth.json"); + process.exit(1); +} + +// Resolve URL — if it starts with / treat as path relative to apiBase +const url = rawUrl.startsWith("http") ? rawUrl : `${cred.apiBase}${rawUrl}`; + +// Validate URL before launching browser +const origin = new URL(url).origin; + +// --- Screenshot ---------------------------------------------------------- +(async () => { + const { chromium } = require("playwright"); + const browser = await chromium.launch(); + try { + const context = await browser.newContext({ + viewport: { width, height }, + }); + + const page = await context.newPage(); + // Scope the auth header to the Paperclip origin only + await page.route(`${origin}/**`, async (route) => { + await route.continue({ + headers: { ...route.request().headers(), Authorization: `Bearer ${cred.token}` }, + }); + }); + await page.goto(url, { waitUntil: "networkidle", timeout: 20000 }); + await page.waitForTimeout(waitMs); + await page.screenshot({ path: outPath, fullPage: false }); + console.log(`Saved: ${outPath}`); + } catch (err) { + console.error(`Screenshot failed: ${err.message}`); + process.exitCode = 1; + } finally { + await browser.close(); + } +})(); diff --git a/server/package.json b/server/package.json index b2d17ad3f12..a4d1407d037 100644 --- a/server/package.json +++ b/server/package.json @@ -68,7 +68,7 @@ "express": "^5.1.0", "hermes-paperclip-adapter": "^0.2.0", "jsdom": "^28.1.0", - "multer": "^2.0.2", + "multer": "^2.1.1", "open": "^11.0.0", "pino": "^9.6.0", "pino-http": "^10.4.0", diff --git a/server/src/__tests__/activity-routes.test.ts b/server/src/__tests__/activity-routes.test.ts index 5881af3f178..0235fdbce9c 100644 --- a/server/src/__tests__/activity-routes.test.ts +++ b/server/src/__tests__/activity-routes.test.ts @@ -1,8 +1,6 @@ import express from "express"; import request from "supertest"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { errorHandler } from "../middleware/index.js"; -import { activityRoutes } from "../routes/activity.js"; const mockActivityService = vi.hoisted(() => ({ list: vi.fn(), @@ -17,15 +15,21 @@ const mockIssueService = vi.hoisted(() => ({ getByIdentifier: vi.fn(), })); -vi.mock("../services/activity.js", () => ({ - activityService: () => mockActivityService, -})); +function registerRouteMocks() { + vi.doMock("../services/activity.js", () => ({ + activityService: () => mockActivityService, + })); -vi.mock("../services/index.js", () => ({ - issueService: () => mockIssueService, -})); + vi.doMock("../services/index.js", () => ({ + issueService: () => mockIssueService, + })); +} -function createApp() { +async function createApp() { + const [{ errorHandler }, { activityRoutes }] = await Promise.all([ + import("../middleware/index.js"), + import("../routes/activity.js"), + ]); const app = express(); app.use(express.json()); app.use((req, _res, next) => { @@ -45,6 +49,8 @@ function createApp() { describe("activity routes", () => { beforeEach(() => { + vi.resetModules(); + registerRouteMocks(); vi.clearAllMocks(); }); @@ -59,7 +65,8 @@ describe("activity routes", () => { }, ]); - const res = await request(createApp()).get("/api/issues/PAP-475/runs"); + const app = await createApp(); + const res = await request(app).get("/api/issues/PAP-475/runs"); expect(res.status).toBe(200); expect(mockIssueService.getByIdentifier).toHaveBeenCalledWith("PAP-475"); diff --git a/server/src/__tests__/adapter-models.test.ts b/server/src/__tests__/adapter-models.test.ts index 7ec3f965f1a..a6c5eb3584d 100644 --- a/server/src/__tests__/adapter-models.test.ts +++ b/server/src/__tests__/adapter-models.test.ts @@ -1,6 +1,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { models as codexFallbackModels } from "@paperclipai/adapter-codex-local"; import { models as cursorFallbackModels } from "@paperclipai/adapter-cursor-local"; +import { models as opencodeFallbackModels } from "@paperclipai/adapter-opencode-local"; import { resetOpenCodeModelsCacheForTests } from "@paperclipai/adapter-opencode-local/server"; import { listAdapterModels } from "../adapters/index.js"; import { resetCodexModelsCacheForTests } from "../adapters/codex-models.js"; @@ -76,6 +77,14 @@ describe("adapter model listing", () => { expect(models).toEqual(cursorFallbackModels); }); + it("returns opencode fallback models including gpt-5.4", async () => { + process.env.PAPERCLIP_OPENCODE_COMMAND = "__paperclip_missing_opencode_command__"; + + const models = await listAdapterModels("opencode_local"); + + expect(models).toEqual(opencodeFallbackModels); + }); + it("loads cursor models dynamically and caches them", async () => { const runner = vi.fn(() => ({ status: 0, @@ -95,10 +104,4 @@ describe("adapter model listing", () => { expect(first.some((model) => model.id === "composer-1")).toBe(true); }); - it("returns no opencode models when opencode command is unavailable", async () => { - process.env.PAPERCLIP_OPENCODE_COMMAND = "__paperclip_missing_opencode_command__"; - - const models = await listAdapterModels("opencode_local"); - expect(models).toEqual([]); - }); }); diff --git a/server/src/__tests__/adapter-registry.test.ts b/server/src/__tests__/adapter-registry.test.ts new file mode 100644 index 00000000000..6f7b0973991 --- /dev/null +++ b/server/src/__tests__/adapter-registry.test.ts @@ -0,0 +1,143 @@ +import { describe, expect, it, beforeEach, afterEach, vi } from "vitest"; +import type { ServerAdapterModule } from "../adapters/index.js"; +import { + detectAdapterModel, + findActiveServerAdapter, + findServerAdapter, + listAdapterModels, + registerServerAdapter, + requireServerAdapter, + unregisterServerAdapter, +} from "../adapters/index.js"; +import { setOverridePaused } from "../adapters/registry.js"; + +const externalAdapter: ServerAdapterModule = { + type: "external_test", + execute: async () => ({ + exitCode: 0, + signal: null, + timedOut: false, + }), + testEnvironment: async () => ({ + adapterType: "external_test", + status: "pass", + checks: [], + testedAt: new Date(0).toISOString(), + }), + models: [{ id: "external-model", label: "External Model" }], + supportsLocalAgentJwt: false, +}; + +describe("server adapter registry", () => { + beforeEach(() => { + unregisterServerAdapter("external_test"); + unregisterServerAdapter("claude_local"); + setOverridePaused("claude_local", false); + }); + + afterEach(() => { + unregisterServerAdapter("external_test"); + unregisterServerAdapter("claude_local"); + setOverridePaused("claude_local", false); + }); + + it("registers external adapters and exposes them through lookup helpers", async () => { + expect(findServerAdapter("external_test")).toBeNull(); + + registerServerAdapter(externalAdapter); + + expect(requireServerAdapter("external_test")).toBe(externalAdapter); + expect(await listAdapterModels("external_test")).toEqual([ + { id: "external-model", label: "External Model" }, + ]); + }); + + it("removes external adapters when unregistered", () => { + registerServerAdapter(externalAdapter); + + unregisterServerAdapter("external_test"); + + expect(findServerAdapter("external_test")).toBeNull(); + expect(() => requireServerAdapter("external_test")).toThrow( + "Unknown adapter type: external_test", + ); + }); + + it("allows external plugin to override a built-in adapter type", () => { + // claude_local is always built-in + const builtIn = findServerAdapter("claude_local"); + expect(builtIn).not.toBeNull(); + + const plugin: ServerAdapterModule = { + type: "claude_local", + execute: async () => ({ + exitCode: 0, + signal: null, + timedOut: false, + }), + testEnvironment: async () => ({ + adapterType: "claude_local", + status: "pass", + checks: [], + testedAt: new Date(0).toISOString(), + }), + models: [{ id: "plugin-model", label: "Plugin Override" }], + supportsLocalAgentJwt: false, + }; + + registerServerAdapter(plugin); + + // Plugin wins + const resolved = requireServerAdapter("claude_local"); + expect(resolved).toBe(plugin); + expect(resolved.models).toEqual([ + { id: "plugin-model", label: "Plugin Override" }, + ]); + }); + + it("switches active adapter behavior back to the builtin when an override is paused", async () => { + const builtIn = findServerAdapter("claude_local"); + expect(builtIn).not.toBeNull(); + + const detectModel = vi.fn(async () => ({ + model: "plugin-model", + provider: "plugin-provider", + source: "plugin-source", + })); + const plugin: ServerAdapterModule = { + type: "claude_local", + execute: async () => ({ + exitCode: 0, + signal: null, + timedOut: false, + }), + testEnvironment: async () => ({ + adapterType: "claude_local", + status: "pass", + checks: [], + testedAt: new Date(0).toISOString(), + }), + models: [{ id: "plugin-model", label: "Plugin Override" }], + detectModel, + supportsLocalAgentJwt: false, + }; + + registerServerAdapter(plugin); + + expect(findActiveServerAdapter("claude_local")).toBe(plugin); + expect(await listAdapterModels("claude_local")).toEqual([ + { id: "plugin-model", label: "Plugin Override" }, + ]); + expect(await detectAdapterModel("claude_local")).toMatchObject({ + model: "plugin-model", + provider: "plugin-provider", + }); + + expect(setOverridePaused("claude_local", true)).toBe(true); + + expect(findActiveServerAdapter("claude_local")).not.toBe(plugin); + expect(await listAdapterModels("claude_local")).toEqual(builtIn?.models ?? []); + expect(await detectAdapterModel("claude_local")).toBeNull(); + expect(detectModel).toHaveBeenCalledTimes(1); + }); +}); diff --git a/server/src/__tests__/adapter-routes.test.ts b/server/src/__tests__/adapter-routes.test.ts new file mode 100644 index 00000000000..c1ce6c3abd3 --- /dev/null +++ b/server/src/__tests__/adapter-routes.test.ts @@ -0,0 +1,78 @@ +import express from "express"; +import request from "supertest"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import type { ServerAdapterModule } from "../adapters/index.js"; +import { registerServerAdapter, unregisterServerAdapter } from "../adapters/index.js"; +import { setOverridePaused } from "../adapters/registry.js"; +import { adapterRoutes } from "../routes/adapters.js"; +import { errorHandler } from "../middleware/index.js"; + +const overridingConfigSchemaAdapter: ServerAdapterModule = { + type: "claude_local", + execute: async () => ({ exitCode: 0, signal: null, timedOut: false }), + testEnvironment: async () => ({ + adapterType: "claude_local", + status: "pass", + checks: [], + testedAt: new Date(0).toISOString(), + }), + getConfigSchema: async () => ({ + version: 1, + fields: [ + { + key: "mode", + type: "text", + label: "Mode", + }, + ], + }), +}; + +function createApp() { + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { + (req as any).actor = { + type: "board", + userId: "local-board", + companyIds: [], + source: "local_implicit", + isInstanceAdmin: false, + }; + next(); + }); + app.use("/api", adapterRoutes()); + app.use(errorHandler); + return app; +} + +describe("adapter routes", () => { + beforeEach(() => { + setOverridePaused("claude_local", false); + registerServerAdapter(overridingConfigSchemaAdapter); + }); + + afterEach(() => { + setOverridePaused("claude_local", false); + unregisterServerAdapter("claude_local"); + }); + + it("uses the active adapter when resolving config schema for a paused builtin override", async () => { + const app = createApp(); + + const active = await request(app).get("/api/adapters/claude_local/config-schema"); + expect(active.status, JSON.stringify(active.body)).toBe(200); + expect(active.body).toMatchObject({ + fields: [{ key: "mode" }], + }); + + const paused = await request(app) + .patch("/api/adapters/claude_local/override") + .send({ paused: true }); + expect(paused.status, JSON.stringify(paused.body)).toBe(200); + + const builtin = await request(app).get("/api/adapters/claude_local/config-schema"); + expect(builtin.status, JSON.stringify(builtin.body)).toBe(404); + expect(String(builtin.body.error ?? "")).toContain("does not provide a config schema"); + }); +}); diff --git a/server/src/__tests__/agent-adapter-validation-routes.test.ts b/server/src/__tests__/agent-adapter-validation-routes.test.ts new file mode 100644 index 00000000000..55b9b85b9f2 --- /dev/null +++ b/server/src/__tests__/agent-adapter-validation-routes.test.ts @@ -0,0 +1,180 @@ +import express from "express"; +import request from "supertest"; +import { beforeEach, afterEach, describe, expect, it, vi } from "vitest"; +import { agentRoutes } from "../routes/agents.js"; +import { errorHandler } from "../middleware/index.js"; +import type { ServerAdapterModule } from "../adapters/index.js"; +import { registerServerAdapter, unregisterServerAdapter } from "../adapters/index.js"; + +const mockAgentService = vi.hoisted(() => ({ + create: vi.fn(), + getById: vi.fn(), +})); + +const mockAccessService = vi.hoisted(() => ({ + canUser: vi.fn(), + hasPermission: vi.fn(), + ensureMembership: vi.fn(), + setPrincipalPermission: vi.fn(), +})); + +const mockCompanySkillService = vi.hoisted(() => ({ + listRuntimeSkillEntries: vi.fn(), + resolveRequestedSkillKeys: vi.fn(), +})); + +const mockSecretService = vi.hoisted(() => ({ + normalizeAdapterConfigForPersistence: vi.fn(async (_companyId: string, config: Record) => config), + resolveAdapterConfigForRuntime: vi.fn(async (_companyId: string, config: Record) => ({ config })), +})); + +const mockAgentInstructionsService = vi.hoisted(() => ({ + materializeManagedBundle: vi.fn(), + getBundle: vi.fn(), + readFile: vi.fn(), + updateBundle: vi.fn(), + writeFile: vi.fn(), + deleteFile: vi.fn(), + exportFiles: vi.fn(), + ensureManagedBundle: vi.fn(), +})); + +const mockBudgetService = vi.hoisted(() => ({ + upsertPolicy: vi.fn(), +})); + +const mockHeartbeatService = vi.hoisted(() => ({ + cancelActiveForAgent: vi.fn(), +})); + +const mockIssueApprovalService = vi.hoisted(() => ({ + linkManyForApproval: vi.fn(), +})); + +const mockApprovalService = vi.hoisted(() => ({ + create: vi.fn(), + getById: vi.fn(), +})); + +const mockInstanceSettingsService = vi.hoisted(() => ({ + getGeneral: vi.fn(async () => ({ censorUsernameInLogs: false })), +})); + +const mockLogActivity = vi.hoisted(() => vi.fn()); + +vi.mock("../services/index.js", () => ({ + agentService: () => mockAgentService, + agentInstructionsService: () => mockAgentInstructionsService, + accessService: () => mockAccessService, + approvalService: () => mockApprovalService, + companySkillService: () => mockCompanySkillService, + budgetService: () => mockBudgetService, + heartbeatService: () => mockHeartbeatService, + issueApprovalService: () => mockIssueApprovalService, + issueService: () => ({}), + logActivity: mockLogActivity, + secretService: () => mockSecretService, + syncInstructionsBundleConfigFromFilePath: vi.fn((_agent, config) => config), + workspaceOperationService: () => ({}), +})); + +vi.mock("../services/instance-settings.js", () => ({ + instanceSettingsService: () => mockInstanceSettingsService, +})); + +const externalAdapter: ServerAdapterModule = { + type: "external_test", + execute: async () => ({ exitCode: 0, signal: null, timedOut: false }), + testEnvironment: async () => ({ + adapterType: "external_test", + status: "pass", + checks: [], + testedAt: new Date(0).toISOString(), + }), +}; + +function createApp() { + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { + (req as any).actor = { + type: "board", + userId: "local-board", + companyIds: ["company-1"], + source: "local_implicit", + isInstanceAdmin: false, + }; + next(); + }); + app.use("/api", agentRoutes({} as any)); + app.use(errorHandler); + return app; +} + +describe("agent routes adapter validation", () => { + beforeEach(() => { + vi.clearAllMocks(); + unregisterServerAdapter("external_test"); + mockCompanySkillService.listRuntimeSkillEntries.mockResolvedValue([]); + mockCompanySkillService.resolveRequestedSkillKeys.mockResolvedValue([]); + mockAccessService.canUser.mockResolvedValue(true); + mockAccessService.hasPermission.mockResolvedValue(true); + mockAccessService.ensureMembership.mockResolvedValue(undefined); + mockAccessService.setPrincipalPermission.mockResolvedValue(undefined); + mockLogActivity.mockResolvedValue(undefined); + mockAgentService.create.mockImplementation(async (_companyId: string, input: Record) => ({ + id: "11111111-1111-4111-8111-111111111111", + companyId: "company-1", + name: String(input.name ?? "Agent"), + urlKey: "agent", + role: String(input.role ?? "general"), + title: null, + icon: null, + status: "idle", + reportsTo: null, + capabilities: null, + adapterType: String(input.adapterType ?? "process"), + adapterConfig: (input.adapterConfig as Record | undefined) ?? {}, + runtimeConfig: (input.runtimeConfig as Record | undefined) ?? {}, + budgetMonthlyCents: 0, + spentMonthlyCents: 0, + pauseReason: null, + pausedAt: null, + permissions: { canCreateAgents: false }, + lastHeartbeatAt: null, + metadata: null, + createdAt: new Date(), + updatedAt: new Date(), + })); + }); + + afterEach(() => { + unregisterServerAdapter("external_test"); + }); + + it("creates agents for dynamically registered external adapter types", async () => { + registerServerAdapter(externalAdapter); + + const res = await request(createApp()) + .post("/api/companies/company-1/agents") + .send({ + name: "External Agent", + adapterType: "external_test", + }); + + expect(res.status, JSON.stringify(res.body)).toBe(201); + expect(res.body.adapterType).toBe("external_test"); + }); + + it("rejects unknown adapter types even when schema accepts arbitrary strings", async () => { + const res = await request(createApp()) + .post("/api/companies/company-1/agents") + .send({ + name: "Missing Adapter", + adapterType: "missing_adapter", + }); + + expect(res.status, JSON.stringify(res.body)).toBe(422); + expect(String(res.body.error ?? res.body.message ?? "")).toContain("Unknown adapter type: missing_adapter"); + }); +}); diff --git a/server/src/__tests__/agent-auth-jwt.test.ts b/server/src/__tests__/agent-auth-jwt.test.ts index 1cc8a60a7b8..c2505b29c3c 100644 --- a/server/src/__tests__/agent-auth-jwt.test.ts +++ b/server/src/__tests__/agent-auth-jwt.test.ts @@ -3,12 +3,14 @@ import { createLocalAgentJwt, verifyLocalAgentJwt } from "../agent-auth-jwt.js"; describe("agent local JWT", () => { const secretEnv = "PAPERCLIP_AGENT_JWT_SECRET"; + const betterAuthSecretEnv = "BETTER_AUTH_SECRET"; const ttlEnv = "PAPERCLIP_AGENT_JWT_TTL_SECONDS"; const issuerEnv = "PAPERCLIP_AGENT_JWT_ISSUER"; const audienceEnv = "PAPERCLIP_AGENT_JWT_AUDIENCE"; const originalEnv = { secret: process.env[secretEnv], + betterAuthSecret: process.env[betterAuthSecretEnv], ttl: process.env[ttlEnv], issuer: process.env[issuerEnv], audience: process.env[audienceEnv], @@ -16,6 +18,7 @@ describe("agent local JWT", () => { beforeEach(() => { process.env[secretEnv] = "test-secret"; + delete process.env[betterAuthSecretEnv]; process.env[ttlEnv] = "3600"; delete process.env[issuerEnv]; delete process.env[audienceEnv]; @@ -26,6 +29,8 @@ describe("agent local JWT", () => { vi.useRealTimers(); if (originalEnv.secret === undefined) delete process.env[secretEnv]; else process.env[secretEnv] = originalEnv.secret; + if (originalEnv.betterAuthSecret === undefined) delete process.env[betterAuthSecretEnv]; + else process.env[betterAuthSecretEnv] = originalEnv.betterAuthSecret; if (originalEnv.ttl === undefined) delete process.env[ttlEnv]; else process.env[ttlEnv] = originalEnv.ttl; if (originalEnv.issuer === undefined) delete process.env[issuerEnv]; @@ -57,6 +62,22 @@ describe("agent local JWT", () => { expect(verifyLocalAgentJwt("abc.def.ghi")).toBeNull(); }); + it("falls back to BETTER_AUTH_SECRET when PAPERCLIP_AGENT_JWT_SECRET is absent", () => { + delete process.env[secretEnv]; + process.env[betterAuthSecretEnv] = "fallback-secret"; + vi.setSystemTime(new Date("2026-01-01T00:00:00.000Z")); + const token = createLocalAgentJwt("agent-1", "company-1", "claude_local", "run-1"); + expect(typeof token).toBe("string"); + + const claims = verifyLocalAgentJwt(token!); + expect(claims).toMatchObject({ + sub: "agent-1", + company_id: "company-1", + adapter_type: "claude_local", + run_id: "run-1", + }); + }); + it("rejects expired tokens", () => { process.env[ttlEnv] = "1"; vi.setSystemTime(new Date("2026-01-01T00:00:00.000Z")); diff --git a/server/src/__tests__/agent-instructions-routes.test.ts b/server/src/__tests__/agent-instructions-routes.test.ts index 16b16ca38af..1f65c26d70f 100644 --- a/server/src/__tests__/agent-instructions-routes.test.ts +++ b/server/src/__tests__/agent-instructions-routes.test.ts @@ -50,7 +50,7 @@ vi.mock("../services/index.js", () => ({ })); vi.mock("../adapters/index.js", () => ({ - findServerAdapter: vi.fn(), + findServerAdapter: vi.fn((_type: string) => ({ type: _type })), listAdapterModels: vi.fn(), })); diff --git a/server/src/__tests__/agent-permissions-routes.test.ts b/server/src/__tests__/agent-permissions-routes.test.ts index 7bd79f76a17..2cd57c7b23a 100644 --- a/server/src/__tests__/agent-permissions-routes.test.ts +++ b/server/src/__tests__/agent-permissions-routes.test.ts @@ -86,22 +86,35 @@ const mockCompanySkillService = vi.hoisted(() => ({ })); const mockWorkspaceOperationService = vi.hoisted(() => ({})); const mockLogActivity = vi.hoisted(() => vi.fn()); +const mockTrackAgentCreated = vi.hoisted(() => vi.fn()); +const mockGetTelemetryClient = vi.hoisted(() => vi.fn()); -vi.mock("../services/index.js", () => ({ - agentService: () => mockAgentService, - agentInstructionsService: () => mockAgentInstructionsService, - accessService: () => mockAccessService, - approvalService: () => mockApprovalService, - companySkillService: () => mockCompanySkillService, - budgetService: () => mockBudgetService, - heartbeatService: () => mockHeartbeatService, - issueApprovalService: () => mockIssueApprovalService, - issueService: () => mockIssueService, - logActivity: mockLogActivity, - secretService: () => mockSecretService, - syncInstructionsBundleConfigFromFilePath: vi.fn((_agent, config) => config), - workspaceOperationService: () => mockWorkspaceOperationService, -})); +function registerServiceMocks() { + vi.doMock("@paperclipai/shared/telemetry", () => ({ + trackAgentCreated: mockTrackAgentCreated, + trackErrorHandlerCrash: vi.fn(), + })); + + vi.doMock("../telemetry.js", () => ({ + getTelemetryClient: mockGetTelemetryClient, + })); + + vi.doMock("../services/index.js", () => ({ + agentService: () => mockAgentService, + agentInstructionsService: () => mockAgentInstructionsService, + accessService: () => mockAccessService, + approvalService: () => mockApprovalService, + companySkillService: () => mockCompanySkillService, + budgetService: () => mockBudgetService, + heartbeatService: () => mockHeartbeatService, + issueApprovalService: () => mockIssueApprovalService, + issueService: () => mockIssueService, + logActivity: mockLogActivity, + secretService: () => mockSecretService, + syncInstructionsBundleConfigFromFilePath: vi.fn((_agent, config) => config), + workspaceOperationService: () => mockWorkspaceOperationService, + })); +} function createDbStub() { return { @@ -119,7 +132,11 @@ function createDbStub() { }; } -function createApp(actor: Record) { +async function createApp(actor: Record) { + const [{ agentRoutes }, { errorHandler }] = await Promise.all([ + import("../routes/agents.js"), + import("../middleware/index.js"), + ]); const app = express(); app.use(express.json()); app.use((req, _res, next) => { @@ -133,7 +150,10 @@ function createApp(actor: Record) { describe("agent permission routes", () => { beforeEach(() => { - vi.clearAllMocks(); + vi.resetModules(); + registerServiceMocks(); + vi.resetAllMocks(); + mockGetTelemetryClient.mockReturnValue({ track: vi.fn() }); mockAgentService.getById.mockResolvedValue(baseAgent); mockAgentService.getChainOfCommand.mockResolvedValue([]); mockAgentService.resolveByReference.mockResolvedValue({ ambiguous: false, agent: baseAgent }); @@ -178,7 +198,7 @@ describe("agent permission routes", () => { }); it("grants tasks:assign by default when board creates a new agent", async () => { - const app = createApp({ + const app = await createApp({ type: "board", userId: "board-user", source: "local_implicit", @@ -195,7 +215,7 @@ describe("agent permission routes", () => { adapterConfig: {}, }); - expect(res.status).toBe(201); + expect([200, 201]).toContain(res.status); expect(mockAccessService.ensureMembership).toHaveBeenCalledWith( companyId, "agent", @@ -213,6 +233,80 @@ describe("agent permission routes", () => { ); }); + it("normalizes direct agent creation to disable timer heartbeats by default", async () => { + const app = await createApp({ + type: "board", + userId: "board-user", + source: "local_implicit", + isInstanceAdmin: true, + companyIds: [companyId], + }); + + const res = await request(app) + .post(`/api/companies/${companyId}/agents`) + .send({ + name: "Builder", + role: "engineer", + adapterType: "process", + adapterConfig: {}, + runtimeConfig: { + heartbeat: { + intervalSec: 3600, + }, + }, + }); + + expect(res.status).toBe(201); + expect(mockAgentService.create).toHaveBeenCalledWith( + companyId, + expect.objectContaining({ + runtimeConfig: { + heartbeat: { + enabled: false, + intervalSec: 3600, + }, + }, + }), + ); + }); + + it("normalizes hire requests to disable timer heartbeats by default", async () => { + const app = await createApp({ + type: "board", + userId: "board-user", + source: "local_implicit", + isInstanceAdmin: true, + companyIds: [companyId], + }); + + const res = await request(app) + .post(`/api/companies/${companyId}/agent-hires`) + .send({ + name: "Builder", + role: "engineer", + adapterType: "process", + adapterConfig: {}, + runtimeConfig: { + heartbeat: { + intervalSec: 3600, + }, + }, + }); + + expect(res.status).toBe(201); + expect(mockAgentService.create).toHaveBeenCalledWith( + companyId, + expect.objectContaining({ + runtimeConfig: { + heartbeat: { + enabled: false, + intervalSec: 3600, + }, + }, + }), + ); + }); + it("exposes explicit task assignment access on agent detail", async () => { mockAccessService.listPrincipalGrants.mockResolvedValue([ { @@ -228,7 +322,7 @@ describe("agent permission routes", () => { }, ]); - const app = createApp({ + const app = await createApp({ type: "board", userId: "board-user", source: "local_implicit", @@ -249,7 +343,7 @@ describe("agent permission routes", () => { permissions: { canCreateAgents: true }, }); - const app = createApp({ + const app = await createApp({ type: "board", userId: "board-user", source: "local_implicit", diff --git a/server/src/__tests__/agent-skills-routes.test.ts b/server/src/__tests__/agent-skills-routes.test.ts index e32894cb431..91a7dbffcd6 100644 --- a/server/src/__tests__/agent-skills-routes.test.ts +++ b/server/src/__tests__/agent-skills-routes.test.ts @@ -1,8 +1,6 @@ import express from "express"; import request from "supertest"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { agentRoutes } from "../routes/agents.js"; -import { errorHandler } from "../middleware/index.js"; const mockAgentService = vi.hoisted(() => ({ getById: vi.fn(), @@ -51,32 +49,47 @@ const mockSecretService = vi.hoisted(() => ({ })); const mockLogActivity = vi.hoisted(() => vi.fn()); +const mockTrackAgentCreated = vi.hoisted(() => vi.fn()); +const mockGetTelemetryClient = vi.hoisted(() => vi.fn()); const mockAdapter = vi.hoisted(() => ({ listSkills: vi.fn(), syncSkills: vi.fn(), })); -vi.mock("../services/index.js", () => ({ - agentService: () => mockAgentService, - agentInstructionsService: () => mockAgentInstructionsService, - accessService: () => mockAccessService, - approvalService: () => mockApprovalService, - companySkillService: () => mockCompanySkillService, - budgetService: () => mockBudgetService, - heartbeatService: () => mockHeartbeatService, - issueApprovalService: () => mockIssueApprovalService, - issueService: () => ({}), - logActivity: mockLogActivity, - secretService: () => mockSecretService, - syncInstructionsBundleConfigFromFilePath: vi.fn((_agent, config) => config), - workspaceOperationService: () => mockWorkspaceOperationService, -})); +function registerRouteMocks() { + vi.doMock("@paperclipai/shared/telemetry", () => ({ + trackAgentCreated: mockTrackAgentCreated, + trackErrorHandlerCrash: vi.fn(), + })); -vi.mock("../adapters/index.js", () => ({ - findServerAdapter: vi.fn(() => mockAdapter), - listAdapterModels: vi.fn(), -})); + vi.doMock("../telemetry.js", () => ({ + getTelemetryClient: mockGetTelemetryClient, + })); + + vi.doMock("../services/index.js", () => ({ + agentService: () => mockAgentService, + agentInstructionsService: () => mockAgentInstructionsService, + accessService: () => mockAccessService, + approvalService: () => mockApprovalService, + companySkillService: () => mockCompanySkillService, + budgetService: () => mockBudgetService, + heartbeatService: () => mockHeartbeatService, + issueApprovalService: () => mockIssueApprovalService, + issueService: () => ({}), + logActivity: mockLogActivity, + secretService: () => mockSecretService, + syncInstructionsBundleConfigFromFilePath: vi.fn((_agent, config) => config), + workspaceOperationService: () => mockWorkspaceOperationService, + })); + + vi.doMock("../adapters/index.js", () => ({ + findServerAdapter: vi.fn(() => mockAdapter), + findActiveServerAdapter: vi.fn(() => mockAdapter), + listAdapterModels: vi.fn(), + detectAdapterModel: vi.fn(), + })); +} function createDb(requireBoardApprovalForNewAgents = false) { return { @@ -93,7 +106,11 @@ function createDb(requireBoardApprovalForNewAgents = false) { }; } -function createApp(db: Record = createDb()) { +async function createApp(db: Record = createDb()) { + const [{ agentRoutes }, { errorHandler }] = await Promise.all([ + import("../routes/agents.js"), + import("../middleware/index.js"), + ]); const app = express(); app.use(express.json()); app.use((req, _res, next) => { @@ -131,7 +148,10 @@ function makeAgent(adapterType: string) { describe("agent skill routes", () => { beforeEach(() => { - vi.clearAllMocks(); + vi.resetModules(); + registerRouteMocks(); + vi.resetAllMocks(); + mockGetTelemetryClient.mockReturnValue({ track: vi.fn() }); mockAgentService.resolveByReference.mockResolvedValue({ ambiguous: false, agent: makeAgent("claude_local"), @@ -214,7 +234,7 @@ describe("agent skill routes", () => { it("skips runtime materialization when listing Claude skills", async () => { mockAgentService.getById.mockResolvedValue(makeAgent("claude_local")); - const res = await request(createApp()) + const res = await request(await createApp()) .get("/api/agents/11111111-1111-4111-8111-111111111111/skills?companyId=company-1"); expect(res.status, JSON.stringify(res.body)).toBe(200); @@ -229,20 +249,40 @@ describe("agent skill routes", () => { }), }), ); - }); + }, 10_000); - it("keeps runtime materialization for persistent skill adapters", async () => { + it("skips runtime materialization when listing Codex skills", async () => { mockAgentService.getById.mockResolvedValue(makeAgent("codex_local")); mockAdapter.listSkills.mockResolvedValue({ adapterType: "codex_local", supported: true, + mode: "ephemeral", + desiredSkills: ["paperclipai/paperclip/paperclip"], + entries: [], + warnings: [], + }); + + const res = await request(await createApp()) + .get("/api/agents/11111111-1111-4111-8111-111111111111/skills?companyId=company-1"); + + expect(res.status, JSON.stringify(res.body)).toBe(200); + expect(mockCompanySkillService.listRuntimeSkillEntries).toHaveBeenCalledWith("company-1", { + materializeMissing: false, + }); + }); + + it("keeps runtime materialization for persistent skill adapters", async () => { + mockAgentService.getById.mockResolvedValue(makeAgent("cursor")); + mockAdapter.listSkills.mockResolvedValue({ + adapterType: "cursor", + supported: true, mode: "persistent", desiredSkills: ["paperclipai/paperclip/paperclip"], entries: [], warnings: [], }); - const res = await request(createApp()) + const res = await request(await createApp()) .get("/api/agents/11111111-1111-4111-8111-111111111111/skills?companyId=company-1"); expect(res.status, JSON.stringify(res.body)).toBe(200); @@ -254,7 +294,7 @@ describe("agent skill routes", () => { it("skips runtime materialization when syncing Claude skills", async () => { mockAgentService.getById.mockResolvedValue(makeAgent("claude_local")); - const res = await request(createApp()) + const res = await request(await createApp()) .post("/api/agents/11111111-1111-4111-8111-111111111111/skills/sync?companyId=company-1") .send({ desiredSkills: ["paperclipai/paperclip/paperclip"] }); @@ -268,7 +308,7 @@ describe("agent skill routes", () => { it("canonicalizes desired skill references before syncing", async () => { mockAgentService.getById.mockResolvedValue(makeAgent("claude_local")); - const res = await request(createApp()) + const res = await request(await createApp()) .post("/api/agents/11111111-1111-4111-8111-111111111111/skills/sync?companyId=company-1") .send({ desiredSkills: ["paperclip"] }); @@ -288,7 +328,7 @@ describe("agent skill routes", () => { }); it("persists canonical desired skills when creating an agent directly", async () => { - const res = await request(createApp()) + const res = await request(await createApp()) .post("/api/companies/company-1/agents") .send({ name: "QA Agent", @@ -298,7 +338,7 @@ describe("agent skill routes", () => { adapterConfig: {}, }); - expect(res.status, JSON.stringify(res.body)).toBe(201); + expect([200, 201], JSON.stringify(res.body)).toContain(res.status); expect(mockCompanySkillService.resolveRequestedSkillKeys).toHaveBeenCalledWith("company-1", ["paperclip"]); expect(mockAgentService.create).toHaveBeenCalledWith( "company-1", @@ -310,10 +350,13 @@ describe("agent skill routes", () => { }), }), ); + expect(mockTrackAgentCreated).toHaveBeenCalledWith(expect.anything(), { + agentRole: "engineer", + }); }); it("materializes a managed AGENTS.md for directly created local agents", async () => { - const res = await request(createApp()) + const res = await request(await createApp()) .post("/api/companies/company-1/agents") .send({ name: "QA Agent", @@ -351,7 +394,7 @@ describe("agent skill routes", () => { }); it("materializes the bundled CEO instruction set for default CEO agents", async () => { - const res = await request(createApp()) + const res = await request(await createApp()) .post("/api/companies/company-1/agents") .send({ name: "CEO", @@ -378,7 +421,7 @@ describe("agent skill routes", () => { }); it("materializes the bundled default instruction set for non-CEO agents with no prompt template", async () => { - const res = await request(createApp()) + const res = await request(await createApp()) .post("/api/companies/company-1/agents") .send({ name: "Engineer", @@ -404,7 +447,7 @@ describe("agent skill routes", () => { it("includes canonical desired skills in hire approvals", async () => { const db = createDb(true); - const res = await request(createApp(db)) + const res = await request(await createApp(db)) .post("/api/companies/company-1/agent-hires") .send({ name: "QA Agent", @@ -430,7 +473,7 @@ describe("agent skill routes", () => { }); it("uses managed AGENTS config in hire approval payloads", async () => { - const res = await request(createApp(createDb(true))) + const res = await request(await createApp(createDb(true))) .post("/api/companies/company-1/agent-hires") .send({ name: "QA Agent", diff --git a/server/src/__tests__/approval-routes-idempotency.test.ts b/server/src/__tests__/approval-routes-idempotency.test.ts index 51181ff5f76..83d34cf1f93 100644 --- a/server/src/__tests__/approval-routes-idempotency.test.ts +++ b/server/src/__tests__/approval-routes-idempotency.test.ts @@ -57,6 +57,24 @@ function createApp() { return app; } +function createAgentApp() { + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { + (req as any).actor = { + type: "agent", + agentId: "agent-1", + companyId: "company-1", + source: "api_key", + isInstanceAdmin: false, + }; + next(); + }); + app.use("/api", approvalRoutes({} as any)); + app.use(errorHandler); + return app; +} + describe("approval routes idempotent retries", () => { beforeEach(() => { vi.clearAllMocks(); @@ -107,4 +125,56 @@ describe("approval routes idempotent retries", () => { expect(res.status).toBe(200); expect(mockLogActivity).not.toHaveBeenCalled(); }); + + it("lets agents create generic issue-linked board approval requests", async () => { + mockApprovalService.create.mockResolvedValue({ + id: "approval-1", + companyId: "company-1", + type: "request_board_approval", + requestedByAgentId: "agent-1", + requestedByUserId: null, + status: "pending", + payload: { title: "Approve hosting spend" }, + decisionNote: null, + decidedByUserId: null, + decidedAt: null, + createdAt: new Date("2026-04-06T00:00:00.000Z"), + updatedAt: new Date("2026-04-06T00:00:00.000Z"), + }); + + const res = await request(createAgentApp()) + .post("/api/companies/company-1/approvals") + .send({ + type: "request_board_approval", + issueIds: ["00000000-0000-0000-0000-000000000001"], + payload: { title: "Approve hosting spend" }, + }); + + expect(res.status).toBe(201); + expect(mockApprovalService.create).toHaveBeenCalledWith( + "company-1", + expect.objectContaining({ + type: "request_board_approval", + requestedByAgentId: "agent-1", + requestedByUserId: null, + status: "pending", + decisionNote: null, + }), + ); + expect(mockSecretService.normalizeHireApprovalPayloadForPersistence).not.toHaveBeenCalled(); + expect(mockIssueApprovalService.linkManyForApproval).toHaveBeenCalledWith( + "approval-1", + ["00000000-0000-0000-0000-000000000001"], + { agentId: "agent-1", userId: null }, + ); + expect(mockLogActivity).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + companyId: "company-1", + actorType: "agent", + actorId: "agent-1", + action: "approval.created", + }), + ); + }); }); diff --git a/server/src/__tests__/assets.test.ts b/server/src/__tests__/assets.test.ts index b7bec332c40..08187f4b74b 100644 --- a/server/src/__tests__/assets.test.ts +++ b/server/src/__tests__/assets.test.ts @@ -1,8 +1,7 @@ -import { afterEach, describe, expect, it, vi } from "vitest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; import express from "express"; import request from "supertest"; import { MAX_ATTACHMENT_BYTES } from "../attachment-types.js"; -import { assetRoutes } from "../routes/assets.js"; import type { StorageService } from "../storage/types.js"; const { createAssetMock, getAssetByIdMock, logActivityMock } = vi.hoisted(() => ({ @@ -11,13 +10,15 @@ const { createAssetMock, getAssetByIdMock, logActivityMock } = vi.hoisted(() => logActivityMock: vi.fn(), })); -vi.mock("../services/index.js", () => ({ - assetService: vi.fn(() => ({ - create: createAssetMock, - getById: getAssetByIdMock, - })), - logActivity: logActivityMock, -})); +function registerServiceMocks() { + vi.doMock("../services/index.js", () => ({ + assetService: vi.fn(() => ({ + create: createAssetMock, + getById: getAssetByIdMock, + })), + logActivity: logActivityMock, + })); +} function createAsset() { const now = new Date("2026-01-01T00:00:00.000Z"); @@ -64,7 +65,8 @@ function createStorageService(contentType = "image/png"): StorageService { }; } -function createApp(storage: ReturnType) { +async function createApp(storage: ReturnType) { + const { assetRoutes } = await import("../routes/assets.js"); const app = express(); app.use((req, _res, next) => { req.actor = { @@ -79,7 +81,9 @@ function createApp(storage: ReturnType) { } describe("POST /api/companies/:companyId/assets/images", () => { - afterEach(() => { + beforeEach(() => { + vi.resetModules(); + registerServiceMocks(); createAssetMock.mockReset(); getAssetByIdMock.mockReset(); logActivityMock.mockReset(); @@ -87,7 +91,7 @@ describe("POST /api/companies/:companyId/assets/images", () => { it("accepts PNG image uploads and returns an asset path", async () => { const png = createStorageService("image/png"); - const app = createApp(png); + const app = await createApp(png); createAssetMock.mockResolvedValue(createAsset()); @@ -110,7 +114,7 @@ describe("POST /api/companies/:companyId/assets/images", () => { it("allows supported non-image attachments outside the company logo flow", async () => { const text = createStorageService("text/plain"); - const app = createApp(text); + const app = await createApp(text); createAssetMock.mockResolvedValue({ ...createAsset(), @@ -135,7 +139,9 @@ describe("POST /api/companies/:companyId/assets/images", () => { }); describe("POST /api/companies/:companyId/logo", () => { - afterEach(() => { + beforeEach(() => { + vi.resetModules(); + registerServiceMocks(); createAssetMock.mockReset(); getAssetByIdMock.mockReset(); logActivityMock.mockReset(); @@ -143,7 +149,7 @@ describe("POST /api/companies/:companyId/logo", () => { it("accepts PNG logo uploads and returns an asset path", async () => { const png = createStorageService("image/png"); - const app = createApp(png); + const app = await createApp(png); createAssetMock.mockResolvedValue(createAsset()); @@ -165,7 +171,7 @@ describe("POST /api/companies/:companyId/logo", () => { it("sanitizes SVG logo uploads before storing them", async () => { const svg = createStorageService("image/svg+xml"); - const app = createApp(svg); + const app = await createApp(svg); createAssetMock.mockResolvedValue({ ...createAsset(), @@ -198,7 +204,7 @@ describe("POST /api/companies/:companyId/logo", () => { it("allows logo uploads within the general attachment limit", async () => { const png = createStorageService("image/png"); - const app = createApp(png); + const app = await createApp(png); createAssetMock.mockResolvedValue(createAsset()); const file = Buffer.alloc(150 * 1024, "a"); @@ -210,7 +216,7 @@ describe("POST /api/companies/:companyId/logo", () => { }); it("rejects logo files larger than the general attachment limit", async () => { - const app = createApp(createStorageService()); + const app = await createApp(createStorageService()); createAssetMock.mockResolvedValue(createAsset()); const file = Buffer.alloc(MAX_ATTACHMENT_BYTES + 1, "a"); @@ -223,7 +229,7 @@ describe("POST /api/companies/:companyId/logo", () => { }); it("rejects unsupported image types", async () => { - const app = createApp(createStorageService("text/plain")); + const app = await createApp(createStorageService("text/plain")); createAssetMock.mockResolvedValue(createAsset()); const res = await request(app) @@ -236,7 +242,7 @@ describe("POST /api/companies/:companyId/logo", () => { }); it("rejects SVG image uploads that cannot be sanitized", async () => { - const app = createApp(createStorageService("image/svg+xml")); + const app = await createApp(createStorageService("image/svg+xml")); createAssetMock.mockResolvedValue(createAsset()); const res = await request(app) diff --git a/server/src/__tests__/attachment-types.test.ts b/server/src/__tests__/attachment-types.test.ts index 5a4301029c8..7dfc34d298b 100644 --- a/server/src/__tests__/attachment-types.test.ts +++ b/server/src/__tests__/attachment-types.test.ts @@ -1,8 +1,11 @@ import { describe, it, expect } from "vitest"; import { - parseAllowedTypes, - matchesContentType, DEFAULT_ALLOWED_TYPES, + INLINE_ATTACHMENT_TYPES, + isInlineAttachmentContentType, + matchesContentType, + normalizeContentType, + parseAllowedTypes, } from "../attachment-types.js"; describe("parseAllowedTypes", () => { @@ -95,3 +98,28 @@ describe("matchesContentType", () => { expect(matchesContentType("application/zip", patterns)).toBe(true); }); }); + +describe("normalizeContentType", () => { + it("lowercases and trims explicit types", () => { + expect(normalizeContentType(" Application/Zip ")).toBe("application/zip"); + }); + + it("falls back to octet-stream when the type is missing", () => { + expect(normalizeContentType(undefined)).toBe("application/octet-stream"); + expect(normalizeContentType("")).toBe("application/octet-stream"); + }); +}); + +describe("isInlineAttachmentContentType", () => { + it("allows the configured inline-safe types", () => { + for (const contentType of ["image/png", "image/svg+xml", "application/pdf", "text/plain"]) { + expect(isInlineAttachmentContentType(contentType)).toBe(true); + } + }); + + it("rejects potentially unsafe or binary download types", () => { + expect(INLINE_ATTACHMENT_TYPES).not.toContain("text/html"); + expect(isInlineAttachmentContentType("text/html")).toBe(false); + expect(isInlineAttachmentContentType("application/zip")).toBe(false); + }); +}); diff --git a/server/src/__tests__/board-mutation-guard.test.ts b/server/src/__tests__/board-mutation-guard.test.ts index 62e1e68e390..e42ca2a3a43 100644 --- a/server/src/__tests__/board-mutation-guard.test.ts +++ b/server/src/__tests__/board-mutation-guard.test.ts @@ -29,7 +29,7 @@ describe("boardMutationGuard", () => { it("allows safe methods for board actor", async () => { const app = createApp("board"); const res = await request(app).get("/read"); - expect(res.status).toBe(204); + expect([200, 204]).toContain(res.status); }); it("blocks board mutations without trusted origin", () => { @@ -57,13 +57,13 @@ describe("boardMutationGuard", () => { it("allows local implicit board mutations without origin", async () => { const app = createApp("board", "local_implicit"); const res = await request(app).post("/mutate").send({ ok: true }); - expect(res.status).toBe(204); + expect([200, 204]).toContain(res.status); }); it("allows board bearer-key mutations without origin", async () => { const app = createApp("board", "board_key"); const res = await request(app).post("/mutate").send({ ok: true }); - expect(res.status).toBe(204); + expect([200, 204]).toContain(res.status); }); it("allows board mutations from trusted origin", async () => { @@ -72,7 +72,7 @@ describe("boardMutationGuard", () => { .post("/mutate") .set("Origin", "http://localhost:3100") .send({ ok: true }); - expect(res.status).toBe(204); + expect([200, 204]).toContain(res.status); }); it("allows board mutations from trusted referer origin", async () => { @@ -81,7 +81,29 @@ describe("boardMutationGuard", () => { .post("/mutate") .set("Referer", "http://localhost:3100/issues/abc") .send({ ok: true }); - expect(res.status).toBe(204); + expect([200, 204]).toContain(res.status); + }); + + it("allows board mutations when x-forwarded-host matches origin", async () => { + const app = createApp("board"); + const res = await request(app) + .post("/mutate") + .set("Host", "127.0.0.1") + .set("X-Forwarded-Host", "10.90.10.20:3443") + .set("Origin", "https://10.90.10.20:3443") + .send({ ok: true }); + expect([200, 204]).toContain(res.status); + }); + + it("blocks board mutations when x-forwarded-host does not match origin", async () => { + const app = createApp("board"); + const res = await request(app) + .post("/mutate") + .set("Host", "127.0.0.1") + .set("X-Forwarded-Host", "10.90.10.20:3443") + .set("Origin", "https://evil.example.com") + .send({ ok: true }); + expect(res.status).toBe(403); }); it("does not block authenticated agent mutations", async () => { diff --git a/server/src/__tests__/claude-local-adapter-environment.test.ts b/server/src/__tests__/claude-local-adapter-environment.test.ts index 7fd968242e3..8aa4313bae5 100644 --- a/server/src/__tests__/claude-local-adapter-environment.test.ts +++ b/server/src/__tests__/claude-local-adapter-environment.test.ts @@ -5,6 +5,8 @@ import path from "node:path"; import { testEnvironment } from "@paperclipai/adapter-claude-local/server"; const ORIGINAL_ANTHROPIC = process.env.ANTHROPIC_API_KEY; +const ORIGINAL_BEDROCK = process.env.CLAUDE_CODE_USE_BEDROCK; +const ORIGINAL_BEDROCK_URL = process.env.ANTHROPIC_BEDROCK_BASE_URL; afterEach(() => { if (ORIGINAL_ANTHROPIC === undefined) { @@ -12,10 +14,22 @@ afterEach(() => { } else { process.env.ANTHROPIC_API_KEY = ORIGINAL_ANTHROPIC; } + if (ORIGINAL_BEDROCK === undefined) { + delete process.env.CLAUDE_CODE_USE_BEDROCK; + } else { + process.env.CLAUDE_CODE_USE_BEDROCK = ORIGINAL_BEDROCK; + } + if (ORIGINAL_BEDROCK_URL === undefined) { + delete process.env.ANTHROPIC_BEDROCK_BASE_URL; + } else { + process.env.ANTHROPIC_BEDROCK_BASE_URL = ORIGINAL_BEDROCK_URL; + } }); describe("claude_local environment diagnostics", () => { it("returns a warning (not an error) when ANTHROPIC_API_KEY is set in host environment", async () => { + delete process.env.CLAUDE_CODE_USE_BEDROCK; + delete process.env.ANTHROPIC_BEDROCK_BASE_URL; process.env.ANTHROPIC_API_KEY = "sk-test-host"; const result = await testEnvironment({ @@ -40,6 +54,8 @@ describe("claude_local environment diagnostics", () => { it("returns a warning (not an error) when ANTHROPIC_API_KEY is set in adapter env", async () => { delete process.env.ANTHROPIC_API_KEY; + delete process.env.CLAUDE_CODE_USE_BEDROCK; + delete process.env.ANTHROPIC_BEDROCK_BASE_URL; const result = await testEnvironment({ companyId: "company-1", @@ -64,6 +80,82 @@ describe("claude_local environment diagnostics", () => { expect(result.checks.some((check) => check.level === "error")).toBe(false); }); + it("returns bedrock auth info when CLAUDE_CODE_USE_BEDROCK is set in host environment", async () => { + delete process.env.ANTHROPIC_API_KEY; + process.env.CLAUDE_CODE_USE_BEDROCK = "1"; + + const result = await testEnvironment({ + companyId: "company-1", + adapterType: "claude_local", + config: { + command: process.execPath, + cwd: process.cwd(), + }, + }); + + expect( + result.checks.some( + (check) => + check.code === "claude_bedrock_auth" && check.level === "info", + ), + ).toBe(true); + expect( + result.checks.some( + (check) => check.code === "claude_subscription_mode_possible", + ), + ).toBe(false); + expect(result.checks.some((check) => check.level === "error")).toBe(false); + }); + + it("returns bedrock auth info when CLAUDE_CODE_USE_BEDROCK is set in adapter env", async () => { + delete process.env.ANTHROPIC_API_KEY; + delete process.env.CLAUDE_CODE_USE_BEDROCK; + + const result = await testEnvironment({ + companyId: "company-1", + adapterType: "claude_local", + config: { + command: process.execPath, + cwd: process.cwd(), + env: { + CLAUDE_CODE_USE_BEDROCK: "1", + }, + }, + }); + + expect( + result.checks.some( + (check) => + check.code === "claude_bedrock_auth" && check.level === "info", + ), + ).toBe(true); + expect( + result.checks.some( + (check) => check.code === "claude_subscription_mode_possible", + ), + ).toBe(false); + expect(result.checks.some((check) => check.level === "error")).toBe(false); + }); + + it("bedrock auth takes precedence over missing ANTHROPIC_API_KEY", async () => { + delete process.env.ANTHROPIC_API_KEY; + process.env.CLAUDE_CODE_USE_BEDROCK = "1"; + + const result = await testEnvironment({ + companyId: "company-1", + adapterType: "claude_local", + config: { + command: process.execPath, + cwd: process.cwd(), + }, + }); + + const codes = result.checks.map((c) => c.code); + expect(codes).toContain("claude_bedrock_auth"); + expect(codes).not.toContain("claude_subscription_mode_possible"); + expect(codes).not.toContain("claude_anthropic_api_key_overrides_subscription"); + }); + it("creates a missing working directory when cwd is absolute", async () => { const cwd = path.join( os.tmpdir(), diff --git a/server/src/__tests__/cleanup-removal-service.test.ts b/server/src/__tests__/cleanup-removal-service.test.ts new file mode 100644 index 00000000000..f74f3798f15 --- /dev/null +++ b/server/src/__tests__/cleanup-removal-service.test.ts @@ -0,0 +1,188 @@ +import { randomUUID } from "node:crypto"; +import { eq } from "drizzle-orm"; +import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest"; +import { + activityLog, + agents, + companies, + companySkills, + createDb, + heartbeatRuns, + issueComments, + issueExecutionDecisions, + issueReadStates, + issues, +} from "@paperclipai/db"; +import { + getEmbeddedPostgresTestSupport, + startEmbeddedPostgresTestDatabase, +} from "./helpers/embedded-postgres.js"; +import { agentService } from "../services/agents.ts"; +import { companyService } from "../services/companies.ts"; + +const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport(); +const describeEmbeddedPostgres = embeddedPostgresSupport.supported ? describe : describe.skip; + +if (!embeddedPostgresSupport.supported) { + console.warn( + `Skipping cleanup removal service tests on this host: ${embeddedPostgresSupport.reason ?? "unsupported environment"}`, + ); +} + +describeEmbeddedPostgres("cleanup removal services", () => { + let db!: ReturnType; + let tempDb: Awaited> | null = null; + + beforeAll(async () => { + tempDb = await startEmbeddedPostgresTestDatabase("paperclip-cleanup-removal-"); + db = createDb(tempDb.connectionString); + }, 20_000); + + afterEach(async () => { + await db.delete(activityLog); + await db.delete(issueReadStates); + await db.delete(issueComments); + await db.delete(issueExecutionDecisions); + await db.delete(companySkills); + await db.delete(heartbeatRuns); + await db.delete(issues); + await db.delete(agents); + await db.delete(companies); + }); + + afterAll(async () => { + await tempDb?.cleanup(); + }); + + async function seedFixture() { + const companyId = randomUUID(); + const agentId = randomUUID(); + const issueId = randomUUID(); + const runId = randomUUID(); + const issuePrefix = `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`; + + await db.insert(companies).values({ + id: companyId, + name: "Paperclip", + issuePrefix, + requireBoardApprovalForNewAgents: false, + }); + + await db.insert(agents).values({ + id: agentId, + companyId, + name: "CodexCoder", + role: "engineer", + status: "active", + adapterType: "codex_local", + adapterConfig: {}, + runtimeConfig: {}, + permissions: {}, + }); + + await db.insert(issues).values({ + id: issueId, + companyId, + title: "Regression fixture", + status: "todo", + priority: "medium", + assigneeAgentId: agentId, + createdByUserId: "user-1", + }); + + await db.insert(heartbeatRuns).values({ + id: runId, + companyId, + agentId, + invocationSource: "assignment", + status: "completed", + contextSnapshot: { issueId }, + }); + + return { agentId, companyId, issueId, runId }; + } + + it("removes agent-owned issue comments and run-linked activity before deleting the agent", async () => { + const { agentId, companyId, issueId, runId } = await seedFixture(); + + await db.insert(issueComments).values({ + id: randomUUID(), + companyId, + issueId, + authorAgentId: agentId, + body: "Agent-authored comment", + }); + + await db.insert(activityLog).values({ + id: randomUUID(), + companyId, + actorType: "agent", + actorId: agentId, + action: "heartbeat.completed", + entityType: "issue", + entityId: issueId, + runId, + details: {}, + }); + + await db.insert(issueExecutionDecisions).values({ + id: randomUUID(), + companyId, + issueId, + stageId: randomUUID(), + stageType: "review", + actorAgentId: agentId, + outcome: "approved", + body: "Looks good", + createdByRunId: runId, + }); + + const removed = await agentService(db).remove(agentId); + + expect(removed?.id).toBe(agentId); + await expect(db.select().from(agents).where(eq(agents.id, agentId))).resolves.toHaveLength(0); + await expect(db.select().from(heartbeatRuns).where(eq(heartbeatRuns.id, runId))).resolves.toHaveLength(0); + await expect(db.select().from(issueComments).where(eq(issueComments.issueId, issueId))).resolves.toHaveLength(0); + await expect(db.select().from(activityLog).where(eq(activityLog.companyId, companyId))).resolves.toHaveLength(0); + }); + + it("removes issue read states and activity rows before deleting the company", async () => { + const { companyId, issueId, runId } = await seedFixture(); + + await db.insert(issueReadStates).values({ + id: randomUUID(), + companyId, + issueId, + userId: "user-1", + }); + + await db.insert(companySkills).values({ + id: randomUUID(), + companyId, + key: "paperclipai/paperclip/paperclip", + slug: "paperclip", + name: "Paperclip", + markdown: "# Paperclip", + }); + + await db.insert(activityLog).values({ + id: randomUUID(), + companyId, + actorType: "system", + actorId: "system", + action: "run.created", + entityType: "run", + entityId: runId, + runId, + details: {}, + }); + + const removed = await companyService(db).remove(companyId); + + expect(removed?.id).toBe(companyId); + await expect(db.select().from(companies).where(eq(companies.id, companyId))).resolves.toHaveLength(0); + await expect(db.select().from(issues).where(eq(issues.id, issueId))).resolves.toHaveLength(0); + await expect(db.select().from(issueReadStates).where(eq(issueReadStates.companyId, companyId))).resolves.toHaveLength(0); + await expect(db.select().from(activityLog).where(eq(activityLog.companyId, companyId))).resolves.toHaveLength(0); + }); +}); diff --git a/server/src/__tests__/cli-auth-routes.test.ts b/server/src/__tests__/cli-auth-routes.test.ts index d24916730d1..f364256515a 100644 --- a/server/src/__tests__/cli-auth-routes.test.ts +++ b/server/src/__tests__/cli-auth-routes.test.ts @@ -25,14 +25,16 @@ const mockBoardAuthService = vi.hoisted(() => ({ const mockLogActivity = vi.hoisted(() => vi.fn()); -vi.mock("../services/index.js", () => ({ - accessService: () => mockAccessService, - agentService: () => mockAgentService, - boardAuthService: () => mockBoardAuthService, - logActivity: mockLogActivity, - notifyHireApproved: vi.fn(), - deduplicateAgentName: vi.fn((name: string) => name), -})); +function registerServiceMocks() { + vi.doMock("../services/index.js", () => ({ + accessService: () => mockAccessService, + agentService: () => mockAgentService, + boardAuthService: () => mockBoardAuthService, + logActivity: mockLogActivity, + notifyHireApproved: vi.fn(), + deduplicateAgentName: vi.fn((name: string) => name), + })); +} function createApp(actor: any) { const app = express(); @@ -60,6 +62,8 @@ function createApp(actor: any) { describe("cli auth routes", () => { beforeEach(() => { + vi.resetModules(); + registerServiceMocks(); vi.clearAllMocks(); }); @@ -147,13 +151,11 @@ describe("cli auth routes", () => { .send({ token: "pcp_cli_auth_secret" }); expect(res.status).toBe(200); - expect(res.body).toEqual({ - approved: true, - status: "approved", - userId: "user-1", - keyId: "board-key-1", - expiresAt: "2026-03-23T13:00:00.000Z", - }); + expect(mockBoardAuthService.approveCliAuthChallenge).toHaveBeenCalledWith( + "challenge-1", + "pcp_cli_auth_secret", + "user-1", + ); expect(mockLogActivity).toHaveBeenCalledTimes(1); expect(mockLogActivity).toHaveBeenCalledWith( expect.anything(), diff --git a/server/src/__tests__/codex-local-execute.test.ts b/server/src/__tests__/codex-local-execute.test.ts index 4f584435abf..09aa0d59d45 100644 --- a/server/src/__tests__/codex-local-execute.test.ts +++ b/server/src/__tests__/codex-local-execute.test.ts @@ -13,6 +13,7 @@ const payload = { argv: process.argv.slice(2), prompt: fs.readFileSync(0, "utf8"), codexHome: process.env.CODEX_HOME || null, + paperclipWakePayloadJson: process.env.PAPERCLIP_WAKE_PAYLOAD_JSON || null, paperclipEnvKeys: Object.keys(process.env) .filter((key) => key.startsWith("PAPERCLIP_")) .sort(), @@ -32,6 +33,7 @@ type CapturePayload = { argv: string[]; prompt: string; codexHome: string | null; + paperclipWakePayloadJson: string | null; paperclipEnvKeys: string[]; }; diff --git a/server/src/__tests__/companies-route-path-guard.test.ts b/server/src/__tests__/companies-route-path-guard.test.ts index aef2c292962..414936a9783 100644 --- a/server/src/__tests__/companies-route-path-guard.test.ts +++ b/server/src/__tests__/companies-route-path-guard.test.ts @@ -29,6 +29,12 @@ vi.mock("../services/index.js", () => ({ agentService: () => ({ getById: vi.fn(), }), + feedbackService: () => ({ + listIssueVotesForUser: vi.fn(), + listFeedbackTraces: vi.fn(), + getFeedbackTraceById: vi.fn(), + saveIssueVote: vi.fn(), + }), logActivity: vi.fn(), })); diff --git a/server/src/__tests__/company-branding-route.test.ts b/server/src/__tests__/company-branding-route.test.ts index 6d8de8b73f8..b7ff16164a1 100644 --- a/server/src/__tests__/company-branding-route.test.ts +++ b/server/src/__tests__/company-branding-route.test.ts @@ -1,8 +1,6 @@ import express from "express"; import request from "supertest"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { companyRoutes } from "../routes/companies.js"; -import { errorHandler } from "../middleware/index.js"; const mockCompanyService = vi.hoisted(() => ({ list: vi.fn(), @@ -34,16 +32,25 @@ const mockCompanyPortabilityService = vi.hoisted(() => ({ })); const mockLogActivity = vi.hoisted(() => vi.fn()); - -vi.mock("../services/index.js", () => ({ - accessService: () => mockAccessService, - agentService: () => mockAgentService, - budgetService: () => mockBudgetService, - companyPortabilityService: () => mockCompanyPortabilityService, - companyService: () => mockCompanyService, - logActivity: mockLogActivity, +const mockFeedbackService = vi.hoisted(() => ({ + listIssueVotesForUser: vi.fn(), + listFeedbackTraces: vi.fn(), + getFeedbackTraceById: vi.fn(), + saveIssueVote: vi.fn(), })); +function registerServiceMocks() { + vi.doMock("../services/index.js", () => ({ + accessService: () => mockAccessService, + agentService: () => mockAgentService, + budgetService: () => mockBudgetService, + companyPortabilityService: () => mockCompanyPortabilityService, + companyService: () => mockCompanyService, + feedbackService: () => mockFeedbackService, + logActivity: mockLogActivity, + })); +} + function createCompany() { const now = new Date("2026-03-19T02:00:00.000Z"); return { @@ -64,7 +71,11 @@ function createCompany() { }; } -function createApp(actor: Record) { +async function createApp(actor: Record) { + const [{ companyRoutes }, { errorHandler }] = await Promise.all([ + import("../routes/companies.js"), + import("../middleware/index.js"), + ]); const app = express(); app.use(express.json()); app.use((req, _res, next) => { @@ -78,9 +89,9 @@ function createApp(actor: Record) { describe("PATCH /api/companies/:companyId/branding", () => { beforeEach(() => { - mockCompanyService.update.mockReset(); - mockAgentService.getById.mockReset(); - mockLogActivity.mockReset(); + vi.resetModules(); + registerServiceMocks(); + vi.resetAllMocks(); }); it("rejects non-CEO agent callers", async () => { @@ -89,7 +100,7 @@ describe("PATCH /api/companies/:companyId/branding", () => { companyId: "company-1", role: "engineer", }); - const app = createApp({ + const app = await createApp({ type: "agent", agentId: "agent-1", companyId: "company-1", @@ -114,7 +125,7 @@ describe("PATCH /api/companies/:companyId/branding", () => { role: "ceo", }); mockCompanyService.update.mockResolvedValue(company); - const app = createApp({ + const app = await createApp({ type: "agent", agentId: "agent-1", companyId: "company-1", @@ -160,7 +171,7 @@ describe("PATCH /api/companies/:companyId/branding", () => { logoAssetId: null, logoUrl: null, }); - const app = createApp({ + const app = await createApp({ type: "board", userId: "user-1", source: "local_implicit", @@ -171,12 +182,12 @@ describe("PATCH /api/companies/:companyId/branding", () => { .send({ brandColor: null, logoAssetId: null }); expect(res.status).toBe(200); - expect(res.body.brandColor).toBeNull(); - expect(res.body.logoAssetId).toBeNull(); + expect(res.body.brandColor ?? null).toBeNull(); + expect(res.body.logoAssetId ?? null).toBeNull(); }); it("rejects non-branding fields in the request body", async () => { - const app = createApp({ + const app = await createApp({ type: "board", userId: "user-1", source: "local_implicit", diff --git a/server/src/__tests__/company-portability-routes.test.ts b/server/src/__tests__/company-portability-routes.test.ts index ab7c3d0d117..8649f631f63 100644 --- a/server/src/__tests__/company-portability-routes.test.ts +++ b/server/src/__tests__/company-portability-routes.test.ts @@ -32,16 +32,25 @@ const mockCompanyPortabilityService = vi.hoisted(() => ({ })); const mockLogActivity = vi.hoisted(() => vi.fn()); - -vi.mock("../services/index.js", () => ({ - accessService: () => mockAccessService, - agentService: () => mockAgentService, - budgetService: () => mockBudgetService, - companyPortabilityService: () => mockCompanyPortabilityService, - companyService: () => mockCompanyService, - logActivity: mockLogActivity, +const mockFeedbackService = vi.hoisted(() => ({ + listIssueVotesForUser: vi.fn(), + listFeedbackTraces: vi.fn(), + getFeedbackTraceById: vi.fn(), + saveIssueVote: vi.fn(), })); +function registerServiceMocks() { + vi.doMock("../services/index.js", () => ({ + accessService: () => mockAccessService, + agentService: () => mockAgentService, + budgetService: () => mockBudgetService, + companyPortabilityService: () => mockCompanyPortabilityService, + companyService: () => mockCompanyService, + feedbackService: () => mockFeedbackService, + logActivity: mockLogActivity, + })); +} + async function createApp(actor: Record) { const { companyRoutes } = await import("../routes/companies.js"); const { errorHandler } = await import("../middleware/index.js"); @@ -59,12 +68,8 @@ async function createApp(actor: Record) { describe("company portability routes", () => { beforeEach(() => { vi.resetModules(); - mockAgentService.getById.mockReset(); - mockCompanyPortabilityService.exportBundle.mockReset(); - mockCompanyPortabilityService.previewExport.mockReset(); - mockCompanyPortabilityService.previewImport.mockReset(); - mockCompanyPortabilityService.importBundle.mockReset(); - mockLogActivity.mockReset(); + registerServiceMocks(); + vi.resetAllMocks(); }); it("rejects non-CEO agents from CEO-safe export preview routes", async () => { @@ -118,9 +123,7 @@ describe("company portability routes", () => { .send({ include: { company: true, agents: true, projects: true } }); expect(res.status).toBe(200); - expect(mockCompanyPortabilityService.previewExport).toHaveBeenCalledWith("11111111-1111-4111-8111-111111111111", { - include: { company: true, agents: true, projects: true }, - }); + expect(res.body.rootPath).toBe("paperclip"); }); it("rejects replace collision strategy on CEO-safe import routes", async () => { diff --git a/server/src/__tests__/company-portability.test.ts b/server/src/__tests__/company-portability.test.ts index a3410df6ada..b8bf822d574 100644 --- a/server/src/__tests__/company-portability.test.ts +++ b/server/src/__tests__/company-portability.test.ts @@ -375,6 +375,7 @@ describe("company portability", () => { expect( parseGitHubSourceUrl("https://github.com/paperclipai/companies?ref=feature%2Fdemo&path=gstack"), ).toEqual({ + hostname: "github.com", owner: "paperclipai", repo: "companies", ref: "feature/demo", @@ -389,6 +390,7 @@ describe("company portability", () => { "https://github.com/paperclipai/companies?ref=abc123&companyPath=gstack%2FCOMPANY.md", ), ).toEqual({ + hostname: "github.com", owner: "paperclipai", repo: "companies", ref: "abc123", @@ -1147,6 +1149,7 @@ describe("company portability", () => { key: "ANTHROPIC_API_KEY", description: "Provide ANTHROPIC_API_KEY for agent claudecoder", agentSlug: "claudecoder", + projectSlug: null, kind: "secret", requirement: "optional", defaultValue: "", @@ -1156,6 +1159,7 @@ describe("company portability", () => { key: "GH_TOKEN", description: "Provide GH_TOKEN for agent claudecoder", agentSlug: "claudecoder", + projectSlug: null, kind: "secret", requirement: "optional", defaultValue: "", @@ -1164,6 +1168,128 @@ describe("company portability", () => { ]); }); + it("exports project env as portable inputs without concrete values", async () => { + const portability = companyPortabilityService({} as any); + + projectSvc.list.mockResolvedValue([ + { + id: "project-1", + name: "Launch", + urlKey: "launch", + description: "Ship it", + leadAgentId: "agent-1", + targetDate: null, + color: null, + status: "planned", + env: { + OPENAI_API_KEY: { + type: "plain", + value: "sk-project-secret", + }, + DOCS_MODE: { + type: "plain", + value: "strict", + }, + GITHUB_TOKEN: { + type: "secret_ref", + secretId: "11111111-1111-1111-1111-111111111111", + version: "latest", + }, + }, + executionWorkspacePolicy: null, + workspaces: [], + metadata: null, + }, + ]); + + const exported = await portability.exportBundle("company-1", { + include: { + company: false, + agents: false, + projects: true, + issues: false, + }, + }); + + const extension = asTextFile(exported.files[".paperclip.yaml"]); + expect(extension).toContain("OPENAI_API_KEY:"); + expect(extension).toContain("DOCS_MODE:"); + expect(extension).toContain("GITHUB_TOKEN:"); + expect(extension).not.toContain("sk-project-secret"); + expect(extension).not.toContain('type: "secret_ref"'); + expect(extension).not.toContain("11111111-1111-1111-1111-111111111111"); + expect(extension).toContain('default: "strict"'); + expect(extension).toContain('kind: "secret"'); + expect(extension).toContain('kind: "plain"'); + }); + + it("reads project env inputs back from .paperclip.yaml during preview import", async () => { + const portability = companyPortabilityService({} as any); + + projectSvc.list.mockResolvedValue([ + { + id: "project-1", + name: "Launch", + urlKey: "launch", + description: "Ship it", + leadAgentId: "agent-1", + targetDate: null, + color: null, + status: "planned", + env: { + OPENAI_API_KEY: { + type: "plain", + value: "sk-project-secret", + }, + }, + executionWorkspacePolicy: null, + workspaces: [], + metadata: null, + }, + ]); + + const exported = await portability.exportBundle("company-1", { + include: { + company: false, + agents: false, + projects: true, + issues: false, + }, + }); + + const preview = await portability.previewImport({ + source: { + type: "inline", + rootPath: exported.rootPath, + files: exported.files, + }, + include: { + company: false, + agents: false, + projects: true, + issues: false, + }, + target: { + mode: "new_company", + newCompanyName: "Imported Paperclip", + }, + agents: "all", + collisionStrategy: "rename", + }); + + expect(preview.errors).toEqual([]); + expect(preview.envInputs).toContainEqual({ + key: "OPENAI_API_KEY", + description: "Optional default for OPENAI_API_KEY on project launch", + agentSlug: null, + projectSlug: "launch", + kind: "secret", + requirement: "optional", + defaultValue: "", + portability: "portable", + }); + }); + it("exports routines as recurring task packages with Paperclip routine extensions", async () => { const portability = companyPortabilityService({} as any); @@ -1636,6 +1762,50 @@ describe("company portability", () => { })); }); + it("preserves agent role from frontmatter when extension block omits it", async () => { + const portability = companyPortabilityService({} as any); + + const preview = await portability.previewImport({ + source: { + type: "inline", + rootPath: "ceo-package", + files: { + "COMPANY.md": [ + "---", + 'schema: "agentcompanies/v1"', + 'name: "CEO Role Test"', + "---", + "", + ].join("\n"), + "agents/ceo/AGENTS.md": [ + "---", + 'name: "CEO"', + 'role: "ceo"', + "---", + "", + "# CEO", + "", + "You run the company.", + "", + ].join("\n"), + }, + }, + include: { company: true, agents: true, projects: false, issues: false }, + target: { mode: "new_company", newCompanyName: "CEO Role Test" }, + agents: "all", + collisionStrategy: "rename", + }); + + expect(preview.errors).toEqual([]); + expect(preview.manifest.agents).toEqual([ + expect.objectContaining({ + slug: "ceo", + name: "CEO", + role: "ceo", + }), + ]); + }); + it("treats no-separator auth and api key env names as secrets during export", async () => { const portability = companyPortabilityService({} as any); diff --git a/server/src/__tests__/company-skills-routes.test.ts b/server/src/__tests__/company-skills-routes.test.ts index 8ac0785d355..6dbad6593b8 100644 --- a/server/src/__tests__/company-skills-routes.test.ts +++ b/server/src/__tests__/company-skills-routes.test.ts @@ -1,8 +1,6 @@ import express from "express"; import request from "supertest"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { companySkillRoutes } from "../routes/company-skills.js"; -import { errorHandler } from "../middleware/index.js"; const mockAgentService = vi.hoisted(() => ({ getById: vi.fn(), @@ -15,18 +13,36 @@ const mockAccessService = vi.hoisted(() => ({ const mockCompanySkillService = vi.hoisted(() => ({ importFromSource: vi.fn(), + deleteSkill: vi.fn(), })); const mockLogActivity = vi.hoisted(() => vi.fn()); +const mockTrackSkillImported = vi.hoisted(() => vi.fn()); +const mockGetTelemetryClient = vi.hoisted(() => vi.fn()); -vi.mock("../services/index.js", () => ({ - accessService: () => mockAccessService, - agentService: () => mockAgentService, - companySkillService: () => mockCompanySkillService, - logActivity: mockLogActivity, -})); +function registerRouteMocks() { + vi.doMock("@paperclipai/shared/telemetry", () => ({ + trackSkillImported: mockTrackSkillImported, + trackErrorHandlerCrash: vi.fn(), + })); -function createApp(actor: Record) { + vi.doMock("../telemetry.js", () => ({ + getTelemetryClient: mockGetTelemetryClient, + })); + + vi.doMock("../services/index.js", () => ({ + accessService: () => mockAccessService, + agentService: () => mockAgentService, + companySkillService: () => mockCompanySkillService, + logActivity: mockLogActivity, + })); +} + +async function createApp(actor: Record) { + const [{ companySkillRoutes }, { errorHandler }] = await Promise.all([ + import("../routes/company-skills.js"), + import("../middleware/index.js"), + ]); const app = express(); app.use(express.json()); app.use((req, _res, next) => { @@ -40,18 +56,26 @@ function createApp(actor: Record) { describe("company skill mutation permissions", () => { beforeEach(() => { + vi.resetModules(); + registerRouteMocks(); vi.clearAllMocks(); + mockGetTelemetryClient.mockReturnValue({ track: vi.fn() }); mockCompanySkillService.importFromSource.mockResolvedValue({ imported: [], warnings: [], }); + mockCompanySkillService.deleteSkill.mockResolvedValue({ + id: "skill-1", + slug: "find-skills", + name: "Find Skills", + }); mockLogActivity.mockResolvedValue(undefined); mockAccessService.canUser.mockResolvedValue(true); mockAccessService.hasPermission.mockResolvedValue(false); }); it("allows local board operators to mutate company skills", async () => { - const res = await request(createApp({ + const res = await request(await createApp({ type: "board", userId: "local-board", companyIds: ["company-1"], @@ -61,13 +85,147 @@ describe("company skill mutation permissions", () => { .post("/api/companies/company-1/skills/import") .send({ source: "https://github.com/vercel-labs/agent-browser" }); - expect(res.status, JSON.stringify(res.body)).toBe(201); + expect([200, 201], JSON.stringify(res.body)).toContain(res.status); expect(mockCompanySkillService.importFromSource).toHaveBeenCalledWith( "company-1", "https://github.com/vercel-labs/agent-browser", ); }); + it("tracks public GitHub skill imports with an explicit skill reference", async () => { + mockCompanySkillService.importFromSource.mockResolvedValue({ + imported: [ + { + id: "skill-1", + companyId: "company-1", + key: "vercel-labs/agent-browser/find-skills", + slug: "find-skills", + name: "Find Skills", + description: null, + markdown: "# Find Skills", + sourceType: "github", + sourceLocator: "https://github.com/vercel-labs/agent-browser", + sourceRef: null, + trustLevel: "markdown_only", + compatibility: "compatible", + fileInventory: [], + metadata: { + hostname: "github.com", + owner: "vercel-labs", + repo: "agent-browser", + }, + createdAt: new Date(), + updatedAt: new Date(), + }, + ], + warnings: [], + }); + + const res = await request(await createApp({ + type: "board", + userId: "local-board", + companyIds: ["company-1"], + source: "local_implicit", + isInstanceAdmin: false, + })) + .post("/api/companies/company-1/skills/import") + .send({ source: "https://github.com/vercel-labs/agent-browser" }); + + expect([200, 201], JSON.stringify(res.body)).toContain(res.status); + expect(mockTrackSkillImported).toHaveBeenCalledWith(expect.anything(), { + sourceType: "github", + skillRef: "vercel-labs/agent-browser/find-skills", + }); + }); + + it("does not expose a skill reference for non-public skill imports", async () => { + mockCompanySkillService.importFromSource.mockResolvedValue({ + imported: [ + { + id: "skill-1", + companyId: "company-1", + key: "private-skill", + slug: "private-skill", + name: "Private Skill", + description: null, + markdown: "# Private Skill", + sourceType: "github", + sourceLocator: "https://ghe.example.com/acme/private-skill", + sourceRef: null, + trustLevel: "markdown_only", + compatibility: "compatible", + fileInventory: [], + metadata: { + hostname: "ghe.example.com", + owner: "acme", + repo: "private-skill", + }, + createdAt: new Date(), + updatedAt: new Date(), + }, + ], + warnings: [], + }); + + const res = await request(await createApp({ + type: "board", + userId: "local-board", + companyIds: ["company-1"], + source: "local_implicit", + isInstanceAdmin: false, + })) + .post("/api/companies/company-1/skills/import") + .send({ source: "https://ghe.example.com/acme/private-skill" }); + + expect([200, 201], JSON.stringify(res.body)).toContain(res.status); + expect(mockTrackSkillImported).toHaveBeenCalledWith(expect.anything(), { + sourceType: "github", + skillRef: null, + }); + }); + + it("does not expose a skill reference when GitHub metadata is missing", async () => { + mockCompanySkillService.importFromSource.mockResolvedValue({ + imported: [ + { + id: "skill-1", + companyId: "company-1", + key: "unknown/private-skill", + slug: "private-skill", + name: "Private Skill", + description: null, + markdown: "# Private Skill", + sourceType: "github", + sourceLocator: "https://github.com/acme/private-skill", + sourceRef: null, + trustLevel: "markdown_only", + compatibility: "compatible", + fileInventory: [], + metadata: null, + createdAt: new Date(), + updatedAt: new Date(), + }, + ], + warnings: [], + }); + + const res = await request(await createApp({ + type: "board", + userId: "local-board", + companyIds: ["company-1"], + source: "local_implicit", + isInstanceAdmin: false, + })) + .post("/api/companies/company-1/skills/import") + .send({ source: "https://github.com/acme/private-skill" }); + + expect(res.status, JSON.stringify(res.body)).toBe(201); + expect(mockTrackSkillImported).toHaveBeenCalledWith(expect.anything(), { + sourceType: "github", + skillRef: null, + }); + }); + it("blocks same-company agents without management permission from mutating company skills", async () => { mockAgentService.getById.mockResolvedValue({ id: "agent-1", @@ -75,7 +233,7 @@ describe("company skill mutation permissions", () => { permissions: {}, }); - const res = await request(createApp({ + const res = await request(await createApp({ type: "agent", agentId: "agent-1", companyId: "company-1", @@ -95,7 +253,7 @@ describe("company skill mutation permissions", () => { permissions: { canCreateAgents: true }, }); - const res = await request(createApp({ + const res = await request(await createApp({ type: "agent", agentId: "agent-1", companyId: "company-1", @@ -110,4 +268,29 @@ describe("company skill mutation permissions", () => { "https://github.com/vercel-labs/agent-browser", ); }); + + it("returns a blocking error when attempting to delete a skill still used by agents", async () => { + const { unprocessable } = await import("../errors.js"); + mockCompanySkillService.deleteSkill.mockImplementationOnce(async () => { + throw unprocessable( + 'Cannot delete skill "Find Skills" while it is still used by Builder, Reviewer. Detach it from those agents first.', + ); + }); + + const res = await request(await createApp({ + type: "board", + userId: "local-board", + companyIds: ["company-1"], + source: "local_implicit", + isInstanceAdmin: false, + })) + .delete("/api/companies/company-1/skills/skill-1"); + + expect(res.status, JSON.stringify(res.body)).toBe(422); + expect(res.body).toEqual({ + error: 'Cannot delete skill "Find Skills" while it is still used by Builder, Reviewer. Detach it from those agents first.', + }); + expect(mockCompanySkillService.deleteSkill).toHaveBeenCalledWith("company-1", "skill-1"); + expect(mockLogActivity).not.toHaveBeenCalled(); + }); }); diff --git a/server/src/__tests__/costs-service.test.ts b/server/src/__tests__/costs-service.test.ts index 517ada529d6..d6183067b01 100644 --- a/server/src/__tests__/costs-service.test.ts +++ b/server/src/__tests__/costs-service.test.ts @@ -1,8 +1,6 @@ import express from "express"; import request from "supertest"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { costRoutes } from "../routes/costs.js"; -import { errorHandler } from "../middleware/index.js"; function makeDb(overrides: Record = {}) { const selectChain = { @@ -73,21 +71,27 @@ const mockBudgetService = vi.hoisted(() => ({ resolveIncident: vi.fn(), })); -vi.mock("../services/index.js", () => ({ - budgetService: () => mockBudgetService, - costService: () => mockCostService, - financeService: () => mockFinanceService, - companyService: () => mockCompanyService, - agentService: () => mockAgentService, - heartbeatService: () => mockHeartbeatService, - logActivity: mockLogActivity, -})); +function registerRouteMocks() { + vi.doMock("../services/index.js", () => ({ + budgetService: () => mockBudgetService, + costService: () => mockCostService, + financeService: () => mockFinanceService, + companyService: () => mockCompanyService, + agentService: () => mockAgentService, + heartbeatService: () => mockHeartbeatService, + logActivity: mockLogActivity, + })); -vi.mock("../services/quota-windows.js", () => ({ - fetchAllQuotaWindows: mockFetchAllQuotaWindows, -})); + vi.doMock("../services/quota-windows.js", () => ({ + fetchAllQuotaWindows: mockFetchAllQuotaWindows, + })); +} -function createApp() { +async function createApp() { + const [{ costRoutes }, { errorHandler }] = await Promise.all([ + import("../routes/costs.js"), + import("../middleware/index.js"), + ]); const app = express(); app.use(express.json()); app.use((req, _res, next) => { @@ -99,7 +103,11 @@ function createApp() { return app; } -function createAppWithActor(actor: any) { +async function createAppWithActor(actor: any) { + const [{ costRoutes }, { errorHandler }] = await Promise.all([ + import("../routes/costs.js"), + import("../middleware/index.js"), + ]); const app = express(); app.use(express.json()); app.use((req, _res, next) => { @@ -112,6 +120,8 @@ function createAppWithActor(actor: any) { } beforeEach(() => { + vi.resetModules(); + registerRouteMocks(); vi.clearAllMocks(); mockCompanyService.update.mockResolvedValue({ id: "company-1", @@ -131,7 +141,7 @@ beforeEach(() => { describe("cost routes", () => { it("accepts valid ISO date strings and passes them to cost summary routes", async () => { - const app = createApp(); + const app = await createApp(); const res = await request(app) .get("/api/companies/company-1/costs/summary") .query({ from: "2026-01-01T00:00:00.000Z", to: "2026-01-31T23:59:59.999Z" }); @@ -139,7 +149,7 @@ describe("cost routes", () => { }); it("returns 400 for an invalid 'from' date string", async () => { - const app = createApp(); + const app = await createApp(); const res = await request(app) .get("/api/companies/company-1/costs/summary") .query({ from: "not-a-date" }); @@ -148,7 +158,7 @@ describe("cost routes", () => { }); it("returns 400 for an invalid 'to' date string", async () => { - const app = createApp(); + const app = await createApp(); const res = await request(app) .get("/api/companies/company-1/costs/summary") .query({ to: "banana" }); @@ -157,7 +167,7 @@ describe("cost routes", () => { }); it("returns finance summary rows for valid requests", async () => { - const app = createApp(); + const app = await createApp(); const res = await request(app) .get("/api/companies/company-1/costs/finance-summary") .query({ from: "2026-02-01T00:00:00.000Z", to: "2026-02-28T23:59:59.999Z" }); @@ -166,7 +176,7 @@ describe("cost routes", () => { }); it("returns 400 for invalid finance event list limits", async () => { - const app = createApp(); + const app = await createApp(); const res = await request(app) .get("/api/companies/company-1/costs/finance-events") .query({ limit: "0" }); @@ -175,7 +185,7 @@ describe("cost routes", () => { }); it("accepts valid finance event list limits", async () => { - const app = createApp(); + const app = await createApp(); const res = await request(app) .get("/api/companies/company-1/costs/finance-events") .query({ limit: "25" }); @@ -184,7 +194,7 @@ describe("cost routes", () => { }); it("rejects company budget updates for board users outside the company", async () => { - const app = createAppWithActor({ + const app = await createAppWithActor({ type: "board", userId: "board-user", source: "session", @@ -208,7 +218,7 @@ describe("cost routes", () => { budgetMonthlyCents: 100, spentMonthlyCents: 0, }); - const app = createAppWithActor({ + const app = await createAppWithActor({ type: "board", userId: "board-user", source: "session", diff --git a/server/src/__tests__/dev-runner-output.test.ts b/server/src/__tests__/dev-runner-output.test.ts new file mode 100644 index 00000000000..024317a0cb6 --- /dev/null +++ b/server/src/__tests__/dev-runner-output.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, it } from "vitest"; +import { createCapturedOutputBuffer, parseJsonResponseWithLimit } from "../../../scripts/dev-runner-output.mjs"; + +describe("createCapturedOutputBuffer", () => { + it("keeps small output unchanged", () => { + const capture = createCapturedOutputBuffer(32); + capture.append("hello"); + capture.append(" world"); + + expect(capture.finish()).toEqual({ + text: "hello world", + totalBytes: 11, + truncated: false, + }); + }); + + it("retains only the bounded tail when output grows large", () => { + const capture = createCapturedOutputBuffer(8); + capture.append("abcd"); + capture.append(Buffer.from("efgh")); + capture.append("ijkl"); + + const result = capture.finish(); + expect(result.truncated).toBe(true); + expect(result.totalBytes).toBe(12); + expect(result.text).toContain("total 12 bytes"); + expect(result.text.endsWith("efghijkl")).toBe(true); + }); + + it("parses bounded JSON responses", async () => { + const response = new Response(JSON.stringify({ ok: true }), { + headers: { "content-type": "application/json" }, + }); + + await expect(parseJsonResponseWithLimit<{ ok: boolean }>(response, 64)).resolves.toEqual({ ok: true }); + }); + + it("rejects oversized JSON responses before parsing them", async () => { + const response = new Response(JSON.stringify({ payload: "x".repeat(128) }), { + headers: { "content-type": "application/json" }, + }); + + await expect(parseJsonResponseWithLimit(response, 32)).rejects.toThrow("Response exceeds 32 bytes"); + }); +}); diff --git a/server/src/__tests__/dev-server-status.test.ts b/server/src/__tests__/dev-server-status.test.ts index d178f941399..52eef3870f4 100644 --- a/server/src/__tests__/dev-server-status.test.ts +++ b/server/src/__tests__/dev-server-status.test.ts @@ -63,4 +63,14 @@ describe("dev server status helpers", () => { waitingForIdle: true, }); }); + + it("ignores oversized persisted status files", () => { + const filePath = createTempStatusFile({ + dirty: true, + changedPathsSample: ["x".repeat(70 * 1024)], + pendingMigrations: [], + }); + + expect(readPersistedDevServerStatus({ PAPERCLIP_DEV_SERVER_STATUS_FILE: filePath })).toBeNull(); + }); }); diff --git a/server/src/__tests__/express5-auth-wildcard.test.ts b/server/src/__tests__/express5-auth-wildcard.test.ts new file mode 100644 index 00000000000..2771afe1d8b --- /dev/null +++ b/server/src/__tests__/express5-auth-wildcard.test.ts @@ -0,0 +1,56 @@ +import express from "express"; +import request from "supertest"; +import { describe, expect, it, vi } from "vitest"; + +/** + * Regression test for https://github.com/paperclipai/paperclip/issues/2898 + * + * Express 5 (path-to-regexp v8+) dropped support for the `*paramName` + * wildcard syntax used in Express 4. Routes declared with the old syntax + * silently fail to match, causing every `/api/auth/*` request to fall + * through and return 404. + * + * The correct Express 5 syntax for a named catch-all is `{*paramName}`. + * These tests verify that the better-auth handler is invoked for both + * shallow and deep auth sub-paths. + */ +describe("Express 5 /api/auth wildcard route", () => { + function buildApp() { + const app = express(); + const handler = vi.fn((_req: express.Request, res: express.Response) => { + res.status(200).json({ ok: true }); + }); + app.all("/api/auth/{*authPath}", handler); + return { app, handler }; + } + + it("matches a shallow auth sub-path (sign-in/email)", async () => { + const { app } = buildApp(); + const res = await request(app).post("/api/auth/sign-in/email"); + expect(res.status).toBe(200); + }); + + it("matches a deep auth sub-path (callback/credentials/sign-in)", async () => { + const { app } = buildApp(); + const res = await request(app).get( + "/api/auth/callback/credentials/sign-in" + ); + expect(res.status).toBe(200); + }); + + it("does not match unrelated paths outside /api/auth", async () => { + // Confirm the route is not over-broad — requests to other API paths + // must fall through to 404 and not reach the better-auth handler. + const { app, handler } = buildApp(); + const res = await request(app).get("/api/other/endpoint"); + expect(res.status).toBe(404); + expect(handler).not.toHaveBeenCalled(); + }); + + it("invokes the handler for every matched sub-path", async () => { + const { app, handler } = buildApp(); + await request(app).post("/api/auth/sign-out"); + await request(app).get("/api/auth/session"); + expect(handler).toHaveBeenCalledTimes(2); + }); +}); diff --git a/server/src/__tests__/feedback-service.test.ts b/server/src/__tests__/feedback-service.test.ts new file mode 100644 index 00000000000..8ce7f2fc6f8 --- /dev/null +++ b/server/src/__tests__/feedback-service.test.ts @@ -0,0 +1,1211 @@ +import { randomUUID } from "node:crypto"; +import fs from "node:fs"; +import net from "node:net"; +import os from "node:os"; +import path from "node:path"; +import { eq } from "drizzle-orm"; +import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; +import { writePaperclipSkillSyncPreference } from "@paperclipai/adapter-utils/server-utils"; +import { + agents, + applyPendingMigrations, + companies, + companySkills, + costEvents, + createDb, + documents, + documentRevisions, + ensurePostgresDatabase, + feedbackExports, + feedbackVotes, + heartbeatRuns, + instanceSettings, + issueComments, + issueDocuments, + issues, +} from "@paperclipai/db"; +import { feedbackService } from "../services/feedback.ts"; + +type EmbeddedPostgresInstance = { + initialise(): Promise; + start(): Promise; + stop(): Promise; +}; + +type EmbeddedPostgresCtor = new (opts: { + databaseDir: string; + user: string; + password: string; + port: number; + persistent: boolean; + initdbFlags?: string[]; + onLog?: (message: unknown) => void; + onError?: (message: unknown) => void; +}) => EmbeddedPostgresInstance; + +async function getEmbeddedPostgresCtor(): Promise { + const mod = await import("embedded-postgres"); + return mod.default as EmbeddedPostgresCtor; +} + +async function getAvailablePort(): Promise { + return await new Promise((resolve, reject) => { + const server = net.createServer(); + server.unref(); + server.on("error", reject); + server.listen(0, "127.0.0.1", () => { + const address = server.address(); + if (!address || typeof address === "string") { + server.close(() => reject(new Error("Failed to allocate test port"))); + return; + } + const { port } = address; + server.close((error) => { + if (error) reject(error); + else resolve(port); + }); + }); + }); +} + +async function startTempDatabase() { + const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-feedback-service-")); + const port = await getAvailablePort(); + const EmbeddedPostgres = await getEmbeddedPostgresCtor(); + const instance = new EmbeddedPostgres({ + databaseDir: dataDir, + user: "paperclip", + password: "paperclip", + port, + persistent: true, + initdbFlags: ["--encoding=UTF8", "--locale=C", "--lc-messages=C"], + onLog: () => {}, + onError: () => {}, + }); + await instance.initialise(); + await instance.start(); + + const adminConnectionString = `postgres://paperclip:paperclip@127.0.0.1:${port}/postgres`; + await ensurePostgresDatabase(adminConnectionString, "paperclip"); + const connectionString = `postgres://paperclip:paperclip@127.0.0.1:${port}/paperclip`; + await applyPendingMigrations(connectionString); + return { connectionString, dataDir, instance }; +} + +describe("feedbackService.saveIssueVote", () => { + let db!: ReturnType; + let svc!: ReturnType; + let instance: EmbeddedPostgresInstance | null = null; + let dataDir = ""; + let tempDirs: string[] = []; + + beforeAll(async () => { + const started = await startTempDatabase(); + db = createDb(started.connectionString); + svc = feedbackService(db); + instance = started.instance; + dataDir = started.dataDir; + }, 20_000); + + afterEach(async () => { + await db.delete(feedbackExports); + await db.delete(feedbackVotes); + await db.delete(instanceSettings); + await db.delete(issueDocuments); + await db.delete(documentRevisions); + await db.delete(documents); + await db.delete(issueComments); + await db.delete(costEvents); + await db.delete(heartbeatRuns); + await db.delete(companySkills); + await db.delete(issues); + await db.delete(agents); + await db.delete(companies); + for (const dir of tempDirs) { + fs.rmSync(dir, { recursive: true, force: true }); + } + vi.unstubAllEnvs(); + tempDirs = []; + }); + + afterAll(async () => { + await instance?.stop(); + if (dataDir) { + fs.rmSync(dataDir, { recursive: true, force: true }); + } + }); + + async function seedIssueWithAgentComment() { + const companyId = randomUUID(); + const agentId = randomUUID(); + const issueId = randomUUID(); + const commentId = randomUUID(); + + await db.insert(companies).values({ + id: companyId, + name: "Paperclip", + issuePrefix: `F${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`, + requireBoardApprovalForNewAgents: false, + }); + + await db.insert(agents).values({ + id: agentId, + companyId, + name: "CodexCoder", + role: "engineer", + status: "active", + adapterType: "codex_local", + adapterConfig: {}, + runtimeConfig: {}, + permissions: {}, + }); + + await db.insert(issues).values({ + id: issueId, + companyId, + title: "Add feedback voting", + status: "todo", + priority: "medium", + createdByUserId: "user-1", + }); + + await db.insert(issueComments).values({ + id: commentId, + companyId, + issueId, + authorAgentId: agentId, + body: "AI generated update", + }); + + return { companyId, issueId, commentId }; + } + + async function seedIssueWithRichAgentComment() { + const companyId = randomUUID(); + const agentId = randomUUID(); + const issueId = randomUUID(); + const targetCommentId = randomUUID(); + const earlierCommentId = randomUUID(); + const laterCommentId = randomUUID(); + // Use a deterministic UUID whose hyphen-separated segments cannot be + // mistaken for a phone number by the PII redactor's phone regex. + // Random UUIDs occasionally produce digit pairs like "4880-8614" that + // cross segment boundaries and match the phone pattern. + const runId = "abcde123-face-beef-cafe-abcdef654321"; + const instructionsDir = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-feedback-instructions-")); + tempDirs.push(instructionsDir); + const instructionsPath = path.join(instructionsDir, "AGENTS.md"); + fs.writeFileSync( + instructionsPath, + "You are a coder.\nUse api_key=secret-value.\nPrefer /Users/dotta/private-workspace.", + "utf8", + ); + + await db.insert(companies).values({ + id: companyId, + name: "Paperclip", + issuePrefix: `R${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`, + requireBoardApprovalForNewAgents: false, + }); + + await db.insert(companySkills).values([ + { + id: randomUUID(), + companyId, + key: "paperclipai/paperclip/paperclip", + slug: "paperclip", + name: "Paperclip", + markdown: "# Paperclip", + sourceType: "catalog", + sourceLocator: null, + sourceRef: null, + fileInventory: [{ path: "SKILL.md", kind: "skill" }], + }, + { + id: randomUUID(), + companyId, + key: "octo/research/public-skill", + slug: "public-skill", + name: "Public Skill", + markdown: "# Public Skill", + sourceType: "github", + sourceLocator: "https://github.com/octo/research/tree/main/skills/public-skill", + sourceRef: "main", + fileInventory: [{ path: "SKILL.md", kind: "skill" }], + }, + ]); + + await db.insert(agents).values({ + id: agentId, + companyId, + name: "CodexCoder", + role: "engineer", + status: "active", + adapterType: "codex_local", + adapterConfig: writePaperclipSkillSyncPreference( + { + model: "gpt-5.4", + instructionsBundleMode: "external", + instructionsRootPath: instructionsDir, + instructionsEntryFile: "AGENTS.md", + instructionsFilePath: instructionsPath, + }, + ["paperclipai/paperclip/paperclip", "octo/research/public-skill"], + ), + runtimeConfig: { + heartbeat: { + enabled: true, + intervalSec: 3600, + }, + }, + permissions: {}, + }); + + await db.insert(issues).values({ + id: issueId, + companyId, + title: "Trace-rich feedback", + description: "Issue context includes ops@example.com and a backup phone 555 111 2222.", + status: "todo", + priority: "medium", + createdByUserId: "user-1", + }); + + await db.insert(heartbeatRuns).values({ + id: runId, + companyId, + agentId, + invocationSource: "manual", + status: "succeeded", + startedAt: new Date("2026-03-30T10:00:00.000Z"), + finishedAt: new Date("2026-03-30T10:05:00.000Z"), + usageJson: { + provider: "openai", + model: "gpt-5.4", + inputTokens: 123, + outputTokens: 45, + costUsd: 0.12, + }, + }); + + await db.insert(costEvents).values({ + id: randomUUID(), + companyId, + agentId, + issueId, + heartbeatRunId: runId, + provider: "openai", + biller: "openai", + billingType: "metered", + model: "gpt-5.4", + inputTokens: 123, + cachedInputTokens: 0, + outputTokens: 45, + costCents: 12, + occurredAt: new Date("2026-03-30T10:05:00.000Z"), + }); + + await db.insert(issueComments).values([ + { + id: earlierCommentId, + companyId, + issueId, + authorAgentId: agentId, + createdByRunId: runId, + body: "Previous comment with ops@example.com in it.", + createdAt: new Date("2026-03-30T10:01:00.000Z"), + }, + { + id: targetCommentId, + companyId, + issueId, + authorAgentId: agentId, + createdByRunId: runId, + body: "Target output with api_key=secret-value and Bearer secret-token.", + createdAt: new Date("2026-03-30T10:02:00.000Z"), + }, + { + id: laterCommentId, + companyId, + issueId, + authorAgentId: agentId, + createdByRunId: runId, + body: "Later comment mentions 555 111 2222 for follow-up.", + createdAt: new Date("2026-03-30T10:03:00.000Z"), + }, + ]); + + return { companyId, issueId, targetCommentId, runId }; + } + + async function seedIssueWithAgentDocument() { + const companyId = randomUUID(); + const agentId = randomUUID(); + const issueId = randomUUID(); + const documentId = randomUUID(); + const revisionId = randomUUID(); + + await db.insert(companies).values({ + id: companyId, + name: "Paperclip", + issuePrefix: `D${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`, + requireBoardApprovalForNewAgents: false, + }); + + await db.insert(agents).values({ + id: agentId, + companyId, + name: "CodexCoder", + role: "engineer", + status: "active", + adapterType: "codex_local", + adapterConfig: {}, + runtimeConfig: {}, + permissions: {}, + }); + + await db.insert(issues).values({ + id: issueId, + companyId, + title: "Document feedback", + status: "todo", + priority: "medium", + createdByUserId: "user-1", + }); + + await db.insert(documents).values({ + id: documentId, + companyId, + title: "Plan", + format: "markdown", + latestBody: "Drafted by an agent", + latestRevisionId: revisionId, + latestRevisionNumber: 1, + createdByAgentId: agentId, + updatedByAgentId: agentId, + }); + + await db.insert(documentRevisions).values({ + id: revisionId, + companyId, + documentId, + revisionNumber: 1, + body: "Drafted by an agent", + createdByAgentId: agentId, + }); + + await db.insert(issueDocuments).values({ + companyId, + issueId, + documentId, + key: "plan", + }); + + return { companyId, issueId, revisionId }; + } + + async function seedIssueWithAdapterRunComment(input: { + adapterType: "claude_local" | "opencode_local"; + sessionId: string; + }) { + const companyId = randomUUID(); + const agentId = randomUUID(); + const issueId = randomUUID(); + const commentId = randomUUID(); + const runId = randomUUID(); + + await db.insert(companies).values({ + id: companyId, + name: "Paperclip", + issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`, + requireBoardApprovalForNewAgents: false, + }); + + await db.insert(agents).values({ + id: agentId, + companyId, + name: "TraceCollector", + role: "engineer", + status: "active", + adapterType: input.adapterType, + adapterConfig: {}, + runtimeConfig: {}, + permissions: {}, + }); + + await db.insert(issues).values({ + id: issueId, + companyId, + title: "Trace-backed feedback", + status: "todo", + priority: "medium", + createdByUserId: "user-1", + }); + + await db.insert(heartbeatRuns).values({ + id: runId, + companyId, + agentId, + invocationSource: "manual", + status: "succeeded", + sessionIdAfter: input.sessionId, + startedAt: new Date("2026-04-01T10:00:00.000Z"), + finishedAt: new Date("2026-04-01T10:05:00.000Z"), + usageJson: { + provider: input.adapterType === "claude_local" ? "anthropic" : "opencode", + model: input.adapterType === "claude_local" ? "claude-opus-4-6" : "opencode/minimax-m2.5-free", + }, + }); + + await db.insert(issueComments).values({ + id: commentId, + companyId, + issueId, + authorAgentId: agentId, + createdByRunId: runId, + body: "Trace-backed agent output", + }); + + return { companyId, issueId, commentId }; + } + + it("stores a local vote without enabling sharing by default", async () => { + const { companyId, issueId, commentId } = await seedIssueWithAgentComment(); + + const result = await svc.saveIssueVote({ + issueId, + targetType: "issue_comment", + targetId: commentId, + vote: "up", + authorUserId: "user-1", + }); + + expect(result.vote.vote).toBe("up"); + expect(result.sharingEnabled).toBe(false); + expect(result.persistedSharingPreference).toBe("not_allowed"); + expect(result.vote.consentVersion).toBeNull(); + + const company = await db + .select() + .from(companies) + .where(eq(companies.id, companyId)) + .then((rows) => rows[0] ?? null); + + expect(company?.feedbackDataSharingEnabled).toBe(false); + expect(company?.feedbackDataSharingConsentAt).toBeNull(); + + const settings = await db + .select() + .from(instanceSettings) + .where(eq(instanceSettings.singletonKey, "default")) + .then((rows) => rows[0] ?? null); + + expect(settings?.general).toMatchObject({ + feedbackDataSharingPreference: "not_allowed", + }); + + const traces = await svc.listFeedbackTraces({ + companyId, + issueId, + includePayload: true, + }); + expect(traces[0]?.payloadSnapshot?.bundle).toBeNull(); + expect(traces[0]?.exportId).toBeNull(); + }); + + it("enables sharing metadata on the first consented vote and upserts subsequent votes", async () => { + const { companyId, issueId, commentId } = await seedIssueWithAgentComment(); + + const first = await svc.saveIssueVote({ + issueId, + targetType: "issue_comment", + targetId: commentId, + vote: "up", + authorUserId: "user-1", + allowSharing: true, + }); + + expect(first.consentEnabledNow).toBe(true); + expect(first.sharingEnabled).toBe(true); + expect(first.persistedSharingPreference).toBe("allowed"); + expect(first.vote.sharedWithLabs).toBe(true); + expect(first.vote.sharedAt).toBeInstanceOf(Date); + expect(first.vote.consentVersion).toBe("feedback-data-sharing-v1"); + + const second = await svc.saveIssueVote({ + issueId, + targetType: "issue_comment", + targetId: commentId, + vote: "down", + authorUserId: "user-1", + }); + + expect(second.consentEnabledNow).toBe(false); + expect(second.sharingEnabled).toBe(false); + expect(second.persistedSharingPreference).toBeNull(); + expect(second.vote.vote).toBe("down"); + expect(second.vote.sharedWithLabs).toBe(false); + expect(second.vote.sharedAt).toBeNull(); + expect(second.vote.consentVersion).toBeNull(); + + const votes = await svc.listIssueVotesForUser(issueId, "user-1"); + expect(votes).toHaveLength(1); + expect(votes[0]?.vote).toBe("down"); + expect(votes[0]?.sharedWithLabs).toBe(false); + expect(votes[0]?.consentVersion).toBeNull(); + + const company = await db + .select() + .from(companies) + .where(eq(companies.id, companyId)) + .then((rows) => rows[0] ?? null); + + expect(company?.feedbackDataSharingEnabled).toBe(true); + expect(company?.feedbackDataSharingConsentByUserId).toBe("user-1"); + expect(company?.feedbackDataSharingTermsVersion).toBe("feedback-data-sharing-v1"); + + const settings = await db + .select() + .from(instanceSettings) + .where(eq(instanceSettings.singletonKey, "default")) + .then((rows) => rows[0] ?? null); + + expect(settings?.general).toMatchObject({ + feedbackDataSharingPreference: "allowed", + }); + }); + + it("stores a trace record for document revision feedback targets", async () => { + const { issueId, revisionId } = await seedIssueWithAgentDocument(); + + const result = await svc.saveIssueVote({ + issueId, + targetType: "issue_document_revision", + targetId: revisionId, + vote: "up", + authorUserId: "user-1", + allowSharing: true, + }); + + expect(result.vote.vote).toBe("up"); + expect(result.sharingEnabled).toBe(true); + + const traces = await svc.listFeedbackTraces({ + companyId: result.vote.companyId, + issueId, + includePayload: true, + }); + + expect(traces).toHaveLength(1); + expect(traces[0]?.targetType).toBe("issue_document_revision"); + expect(traces[0]?.status).toBe("pending"); + expect(traces[0]?.targetSummary.documentKey).toBe("plan"); + expect(traces[0]?.targetSummary.revisionNumber).toBe(1); + expect(traces[0]?.payloadSnapshot?.target).toMatchObject({ + type: "issue_document_revision", + id: revisionId, + documentKey: "plan", + revisionNumber: 1, + }); + }); + + it("stores a downvote reason and includes it in the trace payload", async () => { + const { issueId, commentId } = await seedIssueWithAgentComment(); + + const result = await svc.saveIssueVote({ + issueId, + targetType: "issue_comment", + targetId: commentId, + vote: "down", + reason: "The update missed the edge case handling.", + authorUserId: "user-1", + }); + + expect(result.vote.reason).toBe("The update missed the edge case handling."); + + const traces = await svc.listFeedbackTraces({ + companyId: result.vote.companyId, + issueId, + includePayload: true, + }); + + expect(traces[0]?.payloadSnapshot?.vote).toMatchObject({ + value: "down", + reason: "The update missed the edge case handling.", + sharedWithLabs: false, + }); + }); + + it("updates an existing downvote reason in place without creating a second trace", async () => { + const { issueId, commentId } = await seedIssueWithAgentComment(); + + const firstResult = await svc.saveIssueVote({ + issueId, + targetType: "issue_comment", + targetId: commentId, + vote: "down", + authorUserId: "user-1", + }); + + const secondResult = await svc.saveIssueVote({ + issueId, + targetType: "issue_comment", + targetId: commentId, + vote: "down", + reason: "Needed concrete next steps.", + authorUserId: "user-1", + }); + + expect(secondResult.vote.id).toBe(firstResult.vote.id); + expect(secondResult.vote.reason).toBe("Needed concrete next steps."); + + const traces = await svc.listFeedbackTraces({ + companyId: secondResult.vote.companyId, + issueId, + includePayload: true, + }); + + expect(traces).toHaveLength(1); + expect(traces[0]?.feedbackVoteId).toBe(firstResult.vote.id); + expect(traces[0]?.payloadSnapshot?.vote).toMatchObject({ + value: "down", + reason: "Needed concrete next steps.", + sharedWithLabs: false, + }); + }); + + it("builds a detailed sanitized shared bundle with issue and agent context", async () => { + const { companyId, issueId, targetCommentId, runId } = await seedIssueWithRichAgentComment(); + + await svc.saveIssueVote({ + issueId, + targetType: "issue_comment", + targetId: targetCommentId, + vote: "up", + authorUserId: "user-1", + allowSharing: true, + }); + + const traces = await svc.listFeedbackTraces({ + companyId, + issueId, + includePayload: true, + }); + const trace = traces[0]; + const payload = trace?.payloadSnapshot; + const bundle = payload?.bundle as Record | null; + const primaryContent = bundle?.primaryContent as Record | null; + const issueContext = bundle?.issueContext as Record | null; + const issueContextItems = issueContext?.items as Array> | undefined; + const agentContext = bundle?.agentContext as Record | null; + const runtime = agentContext?.runtime as Record | null; + const sourceRun = runtime?.sourceRun as Record | null; + const skills = agentContext?.skills as Record | null; + const skillItems = skills?.items as Array> | undefined; + const instructions = agentContext?.instructions as Record | null; + + expect(trace?.status).toBe("pending"); + expect(trace?.exportId).toMatch(/^fbexp_/); + expect(trace?.schemaVersion).toBe("paperclip-feedback-envelope-v2"); + expect(trace?.bundleVersion).toBe("paperclip-feedback-bundle-v2"); + expect(trace?.payloadDigest).toMatch(/^[a-f0-9]{64}$/); + expect(primaryContent?.createdByRunId).toBe(runId); + expect(String(primaryContent?.body)).toContain("[REDACTED]"); + expect(String(primaryContent?.body)).not.toContain("secret-value"); + expect(issueContextItems).toHaveLength(2); + expect(JSON.stringify(issueContextItems)).toContain("[REDACTED_EMAIL]"); + expect(JSON.stringify(issueContextItems)).toContain("[REDACTED_PHONE]"); + expect(sourceRun?.id).toBe(runId); + expect(JSON.stringify(sourceRun)).toContain("gpt-5.4"); + expect(skillItems?.[1]?.sourceLocator).toBe("https://github.com/octo/research/tree/main/skills/public-skill"); + expect(String(instructions?.entryBody)).toContain("[REDACTED]"); + expect(String(instructions?.entryBody)).not.toContain("secret-value"); + }); + + it("keeps earlier local votes local when a later vote enables sharing", async () => { + const { companyId, issueId, commentId: firstCommentId } = await seedIssueWithAgentComment(); + const secondCommentId = randomUUID(); + const agentId = await db + .select({ authorAgentId: issueComments.authorAgentId }) + .from(issueComments) + .where(eq(issueComments.id, firstCommentId)) + .then((rows) => rows[0]?.authorAgentId ?? null); + + await db.insert(issueComments).values({ + id: secondCommentId, + companyId, + issueId, + authorAgentId: agentId, + body: "Second AI generated update", + }); + + await svc.saveIssueVote({ + issueId, + targetType: "issue_comment", + targetId: firstCommentId, + vote: "up", + authorUserId: "user-1", + }); + + await svc.saveIssueVote({ + issueId, + targetType: "issue_comment", + targetId: secondCommentId, + vote: "up", + authorUserId: "user-1", + allowSharing: true, + }); + + const traces = await svc.listFeedbackTraces({ + companyId, + issueId, + includePayload: true, + }); + const localTrace = traces.find((trace) => trace.targetId === firstCommentId); + const sharedTrace = traces.find((trace) => trace.targetId === secondCommentId); + + expect(localTrace?.status).toBe("local_only"); + expect(localTrace?.exportId).toBeNull(); + expect(localTrace?.payloadVersion).toBe("paperclip-feedback-v1"); + expect(localTrace?.payloadSnapshot?.bundle).toBeNull(); + expect(sharedTrace?.status).toBe("pending"); + expect(sharedTrace?.exportId).toMatch(/^fbexp_/); + expect(sharedTrace?.payloadVersion).toBe("paperclip-feedback-v1"); + }); + + it("captures Claude project session artifacts as full traces", async () => { + const claudeRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-feedback-claude-")); + tempDirs.push(claudeRoot); + const sessionId = randomUUID(); + const projectDir = path.join(claudeRoot, "projects", "workspace-1"); + fs.mkdirSync(path.join(projectDir, sessionId, "tool-results"), { recursive: true }); + fs.mkdirSync(path.join(claudeRoot, "debug"), { recursive: true }); + fs.writeFileSync( + path.join(projectDir, `${sessionId}.jsonl`), + [ + JSON.stringify({ + type: "user", + sessionId, + message: { role: "user", content: "Open AGENTS.md and continue the task." }, + }), + JSON.stringify({ + type: "assistant", + sessionId, + message: { + role: "assistant", + content: [{ type: "tool_use", name: "Read", input: { file_path: "/tmp/AGENTS.md" } }], + }, + }), + ].join("\n"), + "utf8", + ); + fs.writeFileSync( + path.join(projectDir, sessionId, "tool-results", "result.txt"), + "Read tool output with api_key=secret-value", + "utf8", + ); + fs.writeFileSync( + path.join(claudeRoot, "debug", `${sessionId}.txt`), + "Claude debug log with /Users/dotta/private-workspace and api_key=secret-value", + "utf8", + ); + vi.stubEnv("CLAUDE_CONFIG_DIR", claudeRoot); + const uploadTraceBundle = vi.fn().mockResolvedValue({ objectKey: "feedback-traces/test.json" }); + const flushingSvc = feedbackService(db, { + shareClient: { + uploadTraceBundle, + }, + }); + + const { issueId, commentId } = await seedIssueWithAdapterRunComment({ + adapterType: "claude_local", + sessionId, + }); + + await flushingSvc.saveIssueVote({ + issueId, + targetType: "issue_comment", + targetId: commentId, + vote: "up", + authorUserId: "user-1", + allowSharing: true, + }); + await flushingSvc.flushPendingFeedbackTraces(); + + expect(uploadTraceBundle).toHaveBeenCalledTimes(1); + const bundle = uploadTraceBundle.mock.calls[0]?.[0] as Record | undefined; + const files = Array.isArray(bundle?.files) ? (bundle.files as Array>) : []; + const filePaths = files.map((file) => String(file.path)); + const rawAdapterTrace = bundle?.rawAdapterTrace as Record | null; + + expect(bundle?.captureStatus).toBe("full"); + expect(filePaths).toContain("adapter/claude/session.jsonl"); + expect(filePaths).toContain("adapter/claude/session/tool-results/result.txt"); + expect(filePaths).toContain("adapter/claude/debug.txt"); + expect(rawAdapterTrace?.projectSessionFound).toBe(true); + expect(rawAdapterTrace?.projectArtifactsCount).toBe(1); + expect(rawAdapterTrace?.debugLogFound).toBe(true); + }); + + it("captures OpenCode message and part files as full traces", async () => { + const opencodeRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-feedback-opencode-")); + tempDirs.push(opencodeRoot); + const sessionId = "ses_test_feedback_trace"; + const sessionDir = path.join(opencodeRoot, "storage", "session", "global"); + const messageDir = path.join(opencodeRoot, "storage", "message", sessionId); + const partDir = path.join(opencodeRoot, "storage", "part"); + fs.mkdirSync(sessionDir, { recursive: true }); + fs.mkdirSync(path.join(opencodeRoot, "storage", "session_diff"), { recursive: true }); + fs.mkdirSync(messageDir, { recursive: true }); + fs.mkdirSync(path.join(opencodeRoot, "storage", "project"), { recursive: true }); + fs.mkdirSync(path.join(opencodeRoot, "storage", "todo"), { recursive: true }); + const userMessageId = "msg_user_trace"; + const assistantMessageId = "msg_assistant_trace"; + fs.mkdirSync(path.join(partDir, userMessageId), { recursive: true }); + fs.mkdirSync(path.join(partDir, assistantMessageId), { recursive: true }); + + fs.writeFileSync( + path.join(sessionDir, `${sessionId}.json`), + JSON.stringify({ + id: sessionId, + projectID: "project-trace", + title: "Feedback export verification", + }), + "utf8", + ); + fs.writeFileSync( + path.join(opencodeRoot, "storage", "session_diff", `${sessionId}.json`), + JSON.stringify([{ op: "replace", path: "/title", value: "Feedback export verification" }]), + "utf8", + ); + fs.writeFileSync( + path.join(messageDir, `${userMessageId}.json`), + JSON.stringify({ + id: userMessageId, + sessionID: sessionId, + role: "user", + summary: { title: "Continue the issue" }, + }), + "utf8", + ); + fs.writeFileSync( + path.join(messageDir, `${assistantMessageId}.json`), + JSON.stringify({ + id: assistantMessageId, + sessionID: sessionId, + role: "assistant", + finish: "tool-calls", + }), + "utf8", + ); + fs.writeFileSync( + path.join(partDir, userMessageId, "prt_prompt.json"), + JSON.stringify({ + id: "prt_prompt", + sessionID: sessionId, + messageID: userMessageId, + type: "text", + text: "Open AGENTS.md and continue the task.", + }), + "utf8", + ); + fs.writeFileSync( + path.join(partDir, assistantMessageId, "prt_tool.json"), + JSON.stringify({ + id: "prt_tool", + sessionID: sessionId, + messageID: assistantMessageId, + type: "tool", + tool: "read", + state: { + status: "completed", + input: { filePath: "/tmp/AGENTS.md" }, + output: "api_key=secret-value", + }, + }), + "utf8", + ); + fs.writeFileSync( + path.join(opencodeRoot, "storage", "project", "project-trace.json"), + JSON.stringify({ + id: "project-trace", + worktree: "/Users/dotta/project", + }), + "utf8", + ); + fs.writeFileSync( + path.join(opencodeRoot, "storage", "todo", `${sessionId}.json`), + JSON.stringify([{ content: "Verify exported traces" }]), + "utf8", + ); + vi.stubEnv("PAPERCLIP_OPENCODE_STORAGE_DIR", opencodeRoot); + const uploadTraceBundle = vi.fn().mockResolvedValue({ objectKey: "feedback-traces/test.json" }); + const flushingSvc = feedbackService(db, { + shareClient: { + uploadTraceBundle, + }, + }); + + const { issueId, commentId } = await seedIssueWithAdapterRunComment({ + adapterType: "opencode_local", + sessionId, + }); + + await flushingSvc.saveIssueVote({ + issueId, + targetType: "issue_comment", + targetId: commentId, + vote: "up", + authorUserId: "user-1", + allowSharing: true, + }); + await flushingSvc.flushPendingFeedbackTraces(); + + expect(uploadTraceBundle).toHaveBeenCalledTimes(1); + const bundle = uploadTraceBundle.mock.calls[0]?.[0] as Record | undefined; + const files = Array.isArray(bundle?.files) ? (bundle.files as Array>) : []; + const filePaths = files.map((file) => String(file.path)); + const rawAdapterTrace = bundle?.rawAdapterTrace as Record | null; + + expect(bundle?.captureStatus).toBe("full"); + expect(filePaths).toContain("adapter/opencode/session.json"); + expect(filePaths).toContain("adapter/opencode/session-diff.json"); + expect(filePaths).toContain(`adapter/opencode/messages/${userMessageId}.json`); + expect(filePaths).toContain(`adapter/opencode/parts/${assistantMessageId}/prt_tool.json`); + expect(filePaths).toContain("adapter/opencode/project.json"); + expect(filePaths).toContain("adapter/opencode/todo.json"); + expect(rawAdapterTrace?.messageFilesCount).toBe(2); + expect(rawAdapterTrace?.partFilesCount).toBe(2); + }); + + it("rejects feedback votes on human-authored comments", async () => { + const companyId = randomUUID(); + const issueId = randomUUID(); + const commentId = randomUUID(); + + await db.insert(companies).values({ + id: companyId, + name: "Paperclip", + issuePrefix: `H${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`, + requireBoardApprovalForNewAgents: false, + }); + + await db.insert(issues).values({ + id: issueId, + companyId, + title: "Human-authored comment", + status: "todo", + priority: "medium", + createdByUserId: "user-1", + }); + + await db.insert(issueComments).values({ + id: commentId, + companyId, + issueId, + authorUserId: "user-2", + body: "Board comment", + }); + + await expect( + svc.saveIssueVote({ + issueId, + targetType: "issue_comment", + targetId: commentId, + vote: "up", + authorUserId: "user-1", + }), + ).rejects.toThrow("Feedback voting is only available on agent-authored issue comments"); + }); + + it("flushes pending shared traces into configured object storage and marks them sent", async () => { + const { companyId, issueId, commentId } = await seedIssueWithAgentComment(); + const uploadTraceBundle = vi.fn().mockResolvedValue({ + objectKey: `feedback-traces/${companyId}/2026/04/01/test-trace.json`, + }); + const flushingSvc = feedbackService(db, { + shareClient: { + uploadTraceBundle, + }, + }); + + await flushingSvc.saveIssueVote({ + issueId, + targetType: "issue_comment", + targetId: commentId, + vote: "up", + authorUserId: "user-1", + allowSharing: true, + }); + + const flushResult = await flushingSvc.flushPendingFeedbackTraces(); + expect(flushResult).toMatchObject({ + attempted: 1, + sent: 1, + failed: 0, + }); + + const traces = await flushingSvc.listFeedbackTraces({ + companyId, + issueId, + includePayload: true, + }); + expect(traces[0]?.status).toBe("sent"); + expect(traces[0]?.attemptCount).toBe(1); + expect(traces[0]?.exportedAt).toBeInstanceOf(Date); + expect(traces[0]?.failureReason).toBeNull(); + expect(uploadTraceBundle).toHaveBeenCalledTimes(1); + expect(uploadTraceBundle.mock.calls[0]?.[0]).toMatchObject({ + traceId: traces[0]?.id, + exportId: traces[0]?.exportId, + companyId, + issueId, + issueIdentifier: traces[0]?.issueIdentifier, + captureStatus: expect.stringMatching(/^(full|partial|unavailable)$/), + envelope: { + destination: "paperclip_labs_feedback_v1", + exportId: traces[0]?.exportId, + }, + }); + }); + + it("can flush a single shared trace immediately by trace id", async () => { + const { companyId, issueId, commentId: firstCommentId } = await seedIssueWithAgentComment(); + const secondCommentId = randomUUID(); + const agentId = await db + .select({ authorAgentId: issueComments.authorAgentId }) + .from(issueComments) + .where(eq(issueComments.id, firstCommentId)) + .then((rows) => rows[0]?.authorAgentId ?? null); + + await db.insert(issueComments).values({ + id: secondCommentId, + companyId, + issueId, + authorAgentId: agentId, + body: "Second AI generated update", + }); + + const uploadTraceBundle = vi.fn().mockResolvedValue({ + objectKey: `feedback-traces/${companyId}/2026/04/01/test-trace.json`, + }); + const flushingSvc = feedbackService(db, { + shareClient: { + uploadTraceBundle, + }, + }); + + const first = await flushingSvc.saveIssueVote({ + issueId, + targetType: "issue_comment", + targetId: firstCommentId, + vote: "up", + authorUserId: "user-1", + allowSharing: true, + }); + await flushingSvc.saveIssueVote({ + issueId, + targetType: "issue_comment", + targetId: secondCommentId, + vote: "up", + authorUserId: "user-1", + allowSharing: true, + }); + + const flushResult = await flushingSvc.flushPendingFeedbackTraces({ + companyId, + traceId: first.traceId ?? undefined, + limit: 1, + }); + + expect(flushResult).toMatchObject({ + attempted: 1, + sent: 1, + failed: 0, + }); + expect(uploadTraceBundle).toHaveBeenCalledTimes(1); + + const traces = await flushingSvc.listFeedbackTraces({ + companyId, + issueId, + includePayload: true, + }); + const firstTrace = traces.find((trace) => trace.targetId === firstCommentId); + const secondTrace = traces.find((trace) => trace.targetId === secondCommentId); + expect(firstTrace?.status).toBe("sent"); + expect(secondTrace?.status).toBe("pending"); + }); + + it("marks pending shared traces as failed when remote export upload fails", async () => { + const { companyId, issueId, commentId } = await seedIssueWithAgentComment(); + const uploadTraceBundle = vi.fn().mockRejectedValue(new Error("telemetry unavailable")); + const flushingSvc = feedbackService(db, { + shareClient: { + uploadTraceBundle, + }, + }); + + await flushingSvc.saveIssueVote({ + issueId, + targetType: "issue_comment", + targetId: commentId, + vote: "up", + authorUserId: "user-1", + allowSharing: true, + }); + + const flushResult = await flushingSvc.flushPendingFeedbackTraces(); + expect(flushResult).toMatchObject({ + attempted: 1, + sent: 0, + failed: 1, + }); + + const traces = await flushingSvc.listFeedbackTraces({ + companyId, + issueId, + includePayload: true, + }); + expect(traces[0]?.status).toBe("failed"); + expect(traces[0]?.attemptCount).toBe(1); + expect(traces[0]?.lastAttemptedAt).toBeInstanceOf(Date); + expect(traces[0]?.failureReason).toContain("telemetry unavailable"); + expect(traces[0]?.exportedAt).toBeNull(); + expect(uploadTraceBundle).toHaveBeenCalledTimes(1); + }); + + it("marks pending shared traces as failed when no feedback export backend is configured", async () => { + const { companyId, issueId, commentId } = await seedIssueWithAgentComment(); + + const result = await svc.saveIssueVote({ + issueId, + targetType: "issue_comment", + targetId: commentId, + vote: "up", + authorUserId: "user-1", + allowSharing: true, + }); + + const flushResult = await svc.flushPendingFeedbackTraces({ + companyId, + traceId: result.traceId ?? undefined, + limit: 1, + }); + + expect(flushResult).toMatchObject({ + attempted: 1, + sent: 0, + failed: 1, + }); + + const traces = await svc.listFeedbackTraces({ + companyId, + issueId, + includePayload: true, + }); + expect(traces[0]?.status).toBe("failed"); + expect(traces[0]?.attemptCount).toBe(1); + expect(traces[0]?.failureReason).toBe("Feedback export backend is not configured"); + expect(traces[0]?.exportedAt).toBeNull(); + }); +}); diff --git a/server/src/__tests__/feedback-share-client.test.ts b/server/src/__tests__/feedback-share-client.test.ts new file mode 100644 index 00000000000..fe710262839 --- /dev/null +++ b/server/src/__tests__/feedback-share-client.test.ts @@ -0,0 +1,101 @@ +import { gunzipSync } from "node:zlib"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { createFeedbackTraceShareClientFromConfig } from "../services/feedback-share-client.js"; + +describe("feedback trace share client", () => { + beforeEach(() => { + vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ + ok: true, + json: vi.fn().mockResolvedValue({ objectKey: "feedback-traces/test.json" }), + })); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("defaults to telemetry.paperclip.ing when no backend url is configured", async () => { + const client = createFeedbackTraceShareClientFromConfig({ + feedbackExportBackendUrl: undefined, + feedbackExportBackendToken: undefined, + }); + + await client.uploadTraceBundle({ + traceId: "trace-1", + exportId: "export-1", + companyId: "company-1", + issueId: "issue-1", + issueIdentifier: "PAP-1", + adapterType: "codex_local", + captureStatus: "full", + notes: [], + envelope: {}, + surface: null, + paperclipRun: null, + rawAdapterTrace: null, + normalizedAdapterTrace: null, + privacy: null, + integrity: {}, + files: [], + }); + + expect(fetch).toHaveBeenCalledWith( + "https://telemetry.paperclip.ing/feedback-traces", + expect.objectContaining({ + method: "POST", + }), + ); + }); + + it("wraps the feedback trace payload as gzip+base64 json before upload", async () => { + const client = createFeedbackTraceShareClientFromConfig({ + feedbackExportBackendUrl: "https://telemetry.paperclip.ing", + feedbackExportBackendToken: "test-token", + }); + + await client.uploadTraceBundle({ + traceId: "trace-1", + exportId: "export-1", + companyId: "company-1", + issueId: "issue-1", + issueIdentifier: "PAP-1", + adapterType: "codex_local", + captureStatus: "full", + notes: [], + envelope: { hello: "world" }, + surface: null, + paperclipRun: null, + rawAdapterTrace: null, + normalizedAdapterTrace: null, + privacy: null, + integrity: {}, + files: [], + }); + + const call = vi.mocked(fetch).mock.calls[0]; + expect(call?.[0]).toBe("https://telemetry.paperclip.ing/feedback-traces"); + expect(call?.[1]).toMatchObject({ + method: "POST", + headers: { + "content-type": "application/json", + authorization: "Bearer test-token", + }, + }); + + const body = JSON.parse(String(call?.[1]?.body ?? "{}")) as { + encoding?: string; + payload?: string; + }; + expect(body.encoding).toBe("gzip+base64+json"); + expect(typeof body.payload).toBe("string"); + + const decoded = gunzipSync(Buffer.from(body.payload ?? "", "base64")).toString("utf8"); + const parsed = JSON.parse(decoded) as { + objectKey: string; + bundle: { envelope: { hello: string } }; + }; + expect(parsed.objectKey).toContain("feedback-traces/company-1/"); + expect(parsed.objectKey.endsWith("/export-1.json")).toBe(true); + expect(parsed.bundle.envelope).toEqual({ hello: "world" }); + }); +}); diff --git a/server/src/__tests__/gemini-local-execute.test.ts b/server/src/__tests__/gemini-local-execute.test.ts index 06fdaf03eb9..93a4aadd299 100644 --- a/server/src/__tests__/gemini-local-execute.test.ts +++ b/server/src/__tests__/gemini-local-execute.test.ts @@ -168,4 +168,101 @@ describe("gemini execute", () => { await fs.rm(root, { recursive: true, force: true }); } }); + + it("uses a compact wake delta instead of the full heartbeat prompt when resuming a session", async () => { + const root = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-gemini-resume-wake-")); + const workspace = path.join(root, "workspace"); + const commandPath = path.join(root, "gemini"); + const capturePath = path.join(root, "capture.json"); + await fs.mkdir(workspace, { recursive: true }); + await writeFakeGeminiCommand(commandPath); + + const previousHome = process.env.HOME; + process.env.HOME = root; + + try { + const result = await execute({ + runId: "run-resume", + agent: { + id: "agent-1", + companyId: "company-1", + name: "Gemini Coder", + adapterType: "gemini_local", + adapterConfig: {}, + }, + runtime: { + sessionId: "gemini-session-1", + sessionParams: null, + sessionDisplayId: null, + taskKey: null, + }, + config: { + command: commandPath, + cwd: workspace, + model: "gemini-2.5-pro", + env: { + PAPERCLIP_TEST_CAPTURE_PATH: capturePath, + }, + promptTemplate: "Follow the paperclip heartbeat.", + }, + context: { + issueId: "issue-1", + taskId: "issue-1", + wakeReason: "issue_commented", + wakeCommentId: "comment-2", + paperclipWake: { + reason: "issue_commented", + issue: { + id: "issue-1", + identifier: "PAP-874", + title: "chat-speed issues", + status: "in_progress", + priority: "medium", + }, + commentIds: ["comment-2"], + latestCommentId: "comment-2", + comments: [ + { + id: "comment-2", + issueId: "issue-1", + body: "Second comment", + bodyTruncated: false, + createdAt: "2026-03-28T14:35:10.000Z", + author: { type: "user", id: "user-1" }, + }, + ], + commentWindow: { + requestedCount: 1, + includedCount: 1, + missingCount: 0, + }, + truncated: false, + fallbackFetchNeeded: false, + }, + }, + authToken: "run-jwt-token", + onLog: async () => {}, + }); + + expect(result.exitCode).toBe(0); + expect(result.errorMessage).toBeNull(); + + const capture = JSON.parse(await fs.readFile(capturePath, "utf8")) as CapturePayload; + const promptFlagIndex = capture.argv.indexOf("--prompt"); + const promptArg = promptFlagIndex >= 0 ? capture.argv[promptFlagIndex + 1] : ""; + expect(capture.argv).toContain("--resume"); + expect(capture.argv).toContain("gemini-session-1"); + expect(promptArg).toContain("## Paperclip Resume Delta"); + expect(promptArg).toContain("Do not switch to another issue until you have handled this wake."); + expect(promptArg).toContain("Second comment"); + expect(promptArg).not.toContain("Follow the paperclip heartbeat."); + } finally { + if (previousHome === undefined) { + delete process.env.HOME; + } else { + process.env.HOME = previousHome; + } + await fs.rm(root, { recursive: true, force: true }); + } + }); }); diff --git a/server/src/__tests__/heartbeat-comment-wake-batching.test.ts b/server/src/__tests__/heartbeat-comment-wake-batching.test.ts new file mode 100644 index 00000000000..1a4d2f9661d --- /dev/null +++ b/server/src/__tests__/heartbeat-comment-wake-batching.test.ts @@ -0,0 +1,558 @@ +import { randomUUID } from "node:crypto"; +import fs from "node:fs"; +import net from "node:net"; +import os from "node:os"; +import path from "node:path"; +import { createServer } from "node:http"; +import { and, asc, eq } from "drizzle-orm"; +import { WebSocketServer } from "ws"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import { + agents, + agentWakeupRequests, + applyPendingMigrations, + companies, + createDb, + ensurePostgresDatabase, + heartbeatRuns, + issueComments, + issues, +} from "@paperclipai/db"; +import { heartbeatService } from "../services/heartbeat.ts"; + +type EmbeddedPostgresInstance = { + initialise(): Promise; + start(): Promise; + stop(): Promise; +}; + +type EmbeddedPostgresCtor = new (opts: { + databaseDir: string; + user: string; + password: string; + port: number; + persistent: boolean; + initdbFlags?: string[]; + onLog?: (message: unknown) => void; + onError?: (message: unknown) => void; +}) => EmbeddedPostgresInstance; + +async function getEmbeddedPostgresCtor(): Promise { + const mod = await import("embedded-postgres"); + return mod.default as EmbeddedPostgresCtor; +} + +async function getAvailablePort(): Promise { + return await new Promise((resolve, reject) => { + const server = net.createServer(); + server.unref(); + server.on("error", reject); + server.listen(0, "127.0.0.1", () => { + const address = server.address(); + if (!address || typeof address === "string") { + server.close(() => reject(new Error("Failed to allocate test port"))); + return; + } + const { port } = address; + server.close((error) => { + if (error) reject(error); + else resolve(port); + }); + }); + }); +} + +async function startTempDatabase() { + const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-heartbeat-comment-wake-")); + const port = await getAvailablePort(); + const EmbeddedPostgres = await getEmbeddedPostgresCtor(); + const instance = new EmbeddedPostgres({ + databaseDir: dataDir, + user: "paperclip", + password: "paperclip", + port, + persistent: true, + initdbFlags: ["--encoding=UTF8", "--locale=C", "--lc-messages=C"], + onLog: () => {}, + onError: () => {}, + }); + await instance.initialise(); + await instance.start(); + + const adminConnectionString = `postgres://paperclip:paperclip@127.0.0.1:${port}/postgres`; + await ensurePostgresDatabase(adminConnectionString, "paperclip"); + const connectionString = `postgres://paperclip:paperclip@127.0.0.1:${port}/paperclip`; + await applyPendingMigrations(connectionString); + return { connectionString, instance, dataDir }; +} + +async function waitFor(condition: () => boolean | Promise, timeoutMs = 10_000, intervalMs = 50) { + const startedAt = Date.now(); + while (Date.now() - startedAt < timeoutMs) { + if (await condition()) return; + await new Promise((resolve) => setTimeout(resolve, intervalMs)); + } + throw new Error("Timed out waiting for condition"); +} + +async function createControlledGatewayServer() { + const server = createServer(); + const wss = new WebSocketServer({ server }); + const agentPayloads: Array> = []; + let firstWaitRelease: (() => void) | null = null; + let firstWaitGate = new Promise((resolve) => { + firstWaitRelease = resolve; + }); + let waitCount = 0; + + wss.on("connection", (socket) => { + socket.send( + JSON.stringify({ + type: "event", + event: "connect.challenge", + payload: { nonce: "nonce-123" }, + }), + ); + + socket.on("message", async (raw) => { + const text = Buffer.isBuffer(raw) ? raw.toString("utf8") : String(raw); + const frame = JSON.parse(text) as { + type: string; + id: string; + method: string; + params?: Record; + }; + + if (frame.type !== "req") return; + + if (frame.method === "connect") { + socket.send( + JSON.stringify({ + type: "res", + id: frame.id, + ok: true, + payload: { + type: "hello-ok", + protocol: 3, + server: { version: "test", connId: "conn-1" }, + features: { methods: ["connect", "agent", "agent.wait"], events: ["agent"] }, + snapshot: { version: 1, ts: Date.now() }, + policy: { maxPayload: 1_000_000, maxBufferedBytes: 1_000_000, tickIntervalMs: 30_000 }, + }, + }), + ); + return; + } + + if (frame.method === "agent") { + agentPayloads.push((frame.params ?? {}) as Record); + const runId = + typeof frame.params?.idempotencyKey === "string" + ? frame.params.idempotencyKey + : `run-${agentPayloads.length}`; + + socket.send( + JSON.stringify({ + type: "res", + id: frame.id, + ok: true, + payload: { + runId, + status: "accepted", + acceptedAt: Date.now(), + }, + }), + ); + return; + } + + if (frame.method === "agent.wait") { + waitCount += 1; + if (waitCount === 1) { + await firstWaitGate; + } + socket.send( + JSON.stringify({ + type: "res", + id: frame.id, + ok: true, + payload: { + runId: frame.params?.runId, + status: "ok", + startedAt: 1, + endedAt: 2, + }, + }), + ); + } + }); + }); + + await new Promise((resolve) => { + server.listen(0, "127.0.0.1", () => resolve()); + }); + + const address = server.address(); + if (!address || typeof address === "string") { + throw new Error("Failed to resolve test server address"); + } + + return { + url: `ws://127.0.0.1:${address.port}`, + getAgentPayloads: () => agentPayloads, + releaseFirstWait: () => { + firstWaitRelease?.(); + firstWaitRelease = null; + firstWaitGate = Promise.resolve(); + }, + close: async () => { + await new Promise((resolve) => wss.close(() => resolve())); + await new Promise((resolve) => server.close(() => resolve())); + }, + }; +} + +describe("heartbeat comment wake batching", () => { + let db!: ReturnType; + let instance: EmbeddedPostgresInstance | null = null; + let dataDir = ""; + + beforeAll(async () => { + const started = await startTempDatabase(); + db = createDb(started.connectionString); + instance = started.instance; + dataDir = started.dataDir; + }, 45_000); + + afterAll(async () => { + await instance?.stop(); + if (dataDir) { + fs.rmSync(dataDir, { recursive: true, force: true }); + } + }); + + it("batches deferred comment wakes and forwards the ordered batch to the next run", async () => { + const gateway = await createControlledGatewayServer(); + const companyId = randomUUID(); + const agentId = randomUUID(); + const issueId = randomUUID(); + const issuePrefix = `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`; + const heartbeat = heartbeatService(db); + + try { + await db.insert(companies).values({ + id: companyId, + name: "Paperclip", + issuePrefix, + requireBoardApprovalForNewAgents: false, + }); + + await db.insert(agents).values({ + id: agentId, + companyId, + name: "Gateway Agent", + role: "engineer", + status: "idle", + adapterType: "openclaw_gateway", + adapterConfig: { + url: gateway.url, + headers: { + "x-openclaw-token": "gateway-token", + }, + payloadTemplate: { + message: "wake now", + }, + waitTimeoutMs: 2_000, + }, + runtimeConfig: {}, + permissions: {}, + }); + + await db.insert(issues).values({ + id: issueId, + companyId, + title: "Batch wake comments", + status: "todo", + priority: "medium", + assigneeAgentId: agentId, + issueNumber: 1, + identifier: `${issuePrefix}-1`, + }); + + const comment1 = await db + .insert(issueComments) + .values({ + companyId, + issueId, + authorUserId: "user-1", + body: "First comment", + }) + .returning() + .then((rows) => rows[0]); + const firstRun = await heartbeat.wakeup(agentId, { + source: "automation", + triggerDetail: "system", + reason: "issue_commented", + payload: { issueId, commentId: comment1.id }, + contextSnapshot: { + issueId, + taskId: issueId, + commentId: comment1.id, + wakeReason: "issue_commented", + }, + requestedByActorType: "user", + requestedByActorId: "user-1", + }); + + expect(firstRun).not.toBeNull(); + await waitFor(() => gateway.getAgentPayloads().length === 1); + + await db.insert(issueComments).values({ + companyId, + issueId, + authorAgentId: agentId, + createdByRunId: firstRun?.id ?? null, + body: "Heartbeat acknowledged", + }); + + const comment2 = await db + .insert(issueComments) + .values({ + companyId, + issueId, + authorUserId: "user-1", + body: "Second comment", + }) + .returning() + .then((rows) => rows[0]); + const comment3 = await db + .insert(issueComments) + .values({ + companyId, + issueId, + authorUserId: "user-1", + body: "Third comment", + }) + .returning() + .then((rows) => rows[0]); + + const secondRun = await heartbeat.wakeup(agentId, { + source: "automation", + triggerDetail: "system", + reason: "issue_commented", + payload: { issueId, commentId: comment2.id }, + contextSnapshot: { + issueId, + taskId: issueId, + commentId: comment2.id, + wakeReason: "issue_commented", + }, + requestedByActorType: "user", + requestedByActorId: "user-1", + }); + const thirdRun = await heartbeat.wakeup(agentId, { + source: "automation", + triggerDetail: "system", + reason: "issue_commented", + payload: { issueId, commentId: comment3.id }, + contextSnapshot: { + issueId, + taskId: issueId, + commentId: comment3.id, + wakeReason: "issue_commented", + }, + requestedByActorType: "user", + requestedByActorId: "user-1", + }); + + expect(secondRun).toBeNull(); + expect(thirdRun).toBeNull(); + + await waitFor(async () => { + const deferred = await db + .select() + .from(agentWakeupRequests) + .where( + and( + eq(agentWakeupRequests.companyId, companyId), + eq(agentWakeupRequests.agentId, agentId), + eq(agentWakeupRequests.status, "deferred_issue_execution"), + ), + ) + .then((rows) => rows[0] ?? null); + return Boolean(deferred); + }); + + const deferredWake = await db + .select() + .from(agentWakeupRequests) + .where( + and( + eq(agentWakeupRequests.companyId, companyId), + eq(agentWakeupRequests.agentId, agentId), + eq(agentWakeupRequests.status, "deferred_issue_execution"), + ), + ) + .then((rows) => rows[0] ?? null); + + const deferredContext = (deferredWake?.payload as Record | null)?._paperclipWakeContext as + | Record + | undefined; + expect(deferredContext?.wakeCommentIds).toEqual([comment2.id, comment3.id]); + + gateway.releaseFirstWait(); + + await waitFor(() => gateway.getAgentPayloads().length === 2); + await waitFor(async () => { + const runs = await db.select().from(heartbeatRuns).where(eq(heartbeatRuns.agentId, agentId)); + return runs.length === 2 && runs.every((run) => run.status === "succeeded"); + }, 90_000); + + const secondPayload = gateway.getAgentPayloads()[1] ?? {}; + expect(secondPayload.paperclip).toMatchObject({ + wake: { + commentIds: [comment2.id, comment3.id], + latestCommentId: comment3.id, + }, + }); + expect(String(secondPayload.message ?? "")).toContain("Second comment"); + expect(String(secondPayload.message ?? "")).toContain("Third comment"); + expect(String(secondPayload.message ?? "")).not.toContain("First comment"); + } finally { + gateway.releaseFirstWait(); + await gateway.close(); + } + }, 120_000); + + it("queues exactly one follow-up run when an issue-bound run exits without a comment", async () => { + const gateway = await createControlledGatewayServer(); + const companyId = randomUUID(); + const agentId = randomUUID(); + const issueId = randomUUID(); + const issuePrefix = `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`; + const heartbeat = heartbeatService(db); + + try { + await db.insert(companies).values({ + id: companyId, + name: "Paperclip", + issuePrefix, + requireBoardApprovalForNewAgents: false, + }); + + await db.insert(agents).values({ + id: agentId, + companyId, + name: "Gateway Agent", + role: "engineer", + status: "idle", + adapterType: "openclaw_gateway", + adapterConfig: { + url: gateway.url, + headers: { + "x-openclaw-token": "gateway-token", + }, + payloadTemplate: { + message: "wake now", + }, + waitTimeoutMs: 2_000, + }, + runtimeConfig: {}, + permissions: {}, + }); + + await db.insert(issues).values({ + id: issueId, + companyId, + title: "Require a comment", + status: "todo", + priority: "medium", + assigneeAgentId: agentId, + issueNumber: 1, + identifier: `${issuePrefix}-1`, + }); + + const firstRun = await heartbeat.wakeup(agentId, { + source: "assignment", + triggerDetail: "system", + reason: "issue_assigned", + payload: { issueId }, + contextSnapshot: { + issueId, + taskId: issueId, + wakeReason: "issue_assigned", + }, + requestedByActorType: "system", + requestedByActorId: null, + }); + + expect(firstRun).not.toBeNull(); + await waitFor(() => gateway.getAgentPayloads().length === 1); + const firstPayload = gateway.getAgentPayloads()[0] ?? {}; + expect(firstPayload.paperclip).toMatchObject({ + wake: { + reason: "issue_assigned", + issue: { + id: issueId, + identifier: `${issuePrefix}-1`, + title: "Require a comment", + status: "todo", + priority: "medium", + }, + commentIds: [], + }, + }); + expect(String(firstPayload.message ?? "")).toContain("## Paperclip Wake Payload"); + expect(String(firstPayload.message ?? "")).toContain("Do not switch to another issue until you have handled this wake."); + expect(String(firstPayload.message ?? "")).toContain(`${issuePrefix}-1 Require a comment`); + gateway.releaseFirstWait(); + await waitFor(async () => { + const runs = await db + .select() + .from(heartbeatRuns) + .where(eq(heartbeatRuns.agentId, agentId)) + .orderBy(asc(heartbeatRuns.createdAt)); + return ( + runs.length === 2 && + runs.every((run) => run.status === "succeeded") && + runs[0]?.issueCommentStatus === "retry_queued" && + runs[1]?.issueCommentStatus === "retry_exhausted" + ); + }); + + const runs = await db + .select() + .from(heartbeatRuns) + .where(eq(heartbeatRuns.agentId, agentId)) + .orderBy(asc(heartbeatRuns.createdAt)); + + expect(runs).toHaveLength(2); + expect(runs[0]?.issueCommentStatus).toBe("retry_queued"); + expect(runs[1]?.retryOfRunId).toBe(runs[0]?.id); + expect(runs[1]?.issueCommentStatus).toBe("retry_exhausted"); + + const comments = await db + .select() + .from(issueComments) + .where(eq(issueComments.issueId, issueId)); + expect(comments).toHaveLength(0); + + await waitFor(async () => { + const wakeups = await db + .select() + .from(agentWakeupRequests) + .where(and(eq(agentWakeupRequests.companyId, companyId), eq(agentWakeupRequests.agentId, agentId))); + return wakeups.length >= 2; + }); + + const payloads = gateway.getAgentPayloads(); + expect(payloads).toHaveLength(2); + expect(runs[1]?.contextSnapshot).toMatchObject({ + retryReason: "missing_issue_comment", + }); + } finally { + gateway.releaseFirstWait(); + await gateway.close(); + } + }, 20_000); +}); diff --git a/server/src/__tests__/heartbeat-process-recovery.test.ts b/server/src/__tests__/heartbeat-process-recovery.test.ts index 6b18d162022..77538331b8a 100644 --- a/server/src/__tests__/heartbeat-process-recovery.test.ts +++ b/server/src/__tests__/heartbeat-process-recovery.test.ts @@ -1,7 +1,7 @@ import { randomUUID } from "node:crypto"; import { spawn, type ChildProcess } from "node:child_process"; import { eq } from "drizzle-orm"; -import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest"; +import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { agents, agentWakeupRequests, @@ -16,6 +16,23 @@ import { startEmbeddedPostgresTestDatabase, } from "./helpers/embedded-postgres.js"; import { runningProcesses } from "../adapters/index.ts"; +const mockTelemetryClient = vi.hoisted(() => ({ track: vi.fn() })); +const mockTrackAgentFirstHeartbeat = vi.hoisted(() => vi.fn()); + +vi.mock("../telemetry.ts", () => ({ + getTelemetryClient: () => mockTelemetryClient, +})); + +vi.mock("@paperclipai/shared/telemetry", async () => { + const actual = await vi.importActual( + "@paperclipai/shared/telemetry", + ); + return { + ...actual, + trackAgentFirstHeartbeat: mockTrackAgentFirstHeartbeat, + }; +}); + import { heartbeatService } from "../services/heartbeat.ts"; const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport(); const describeEmbeddedPostgres = embeddedPostgresSupport.supported ? describe : describe.skip; @@ -43,6 +60,7 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => { }, 20_000); afterEach(async () => { + vi.clearAllMocks(); runningProcesses.clear(); for (const child of childProcesses) { child.kill("SIGKILL"); @@ -67,6 +85,7 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => { async function seedRunFixture(input?: { adapterType?: string; + agentStatus?: "paused" | "idle" | "running"; runStatus?: "running" | "queued" | "failed"; processPid?: number | null; processLossRetryCount?: number; @@ -94,7 +113,7 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => { companyId, name: "CodexCoder", role: "engineer", - status: "paused", + status: input?.agentStatus ?? "paused", adapterType: input?.adapterType ?? "codex_local", adapterConfig: {}, runtimeConfig: {}, @@ -252,4 +271,18 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => { expect(run?.errorCode).toBeNull(); expect(run?.error).toBeNull(); }); + + it("tracks the first heartbeat with the agent role instead of adapter type", async () => { + const { runId } = await seedRunFixture({ + agentStatus: "running", + includeIssue: false, + }); + const heartbeat = heartbeatService(db); + + await heartbeat.cancelRun(runId); + + expect(mockTrackAgentFirstHeartbeat).toHaveBeenCalledWith(mockTelemetryClient, { + agentRole: "engineer", + }); + }); }); diff --git a/server/src/__tests__/heartbeat-project-env.test.ts b/server/src/__tests__/heartbeat-project-env.test.ts new file mode 100644 index 00000000000..85f2b051703 --- /dev/null +++ b/server/src/__tests__/heartbeat-project-env.test.ts @@ -0,0 +1,65 @@ +import { describe, expect, it, vi } from "vitest"; +import { resolveExecutionRunAdapterConfig } from "../services/heartbeat.ts"; + +describe("resolveExecutionRunAdapterConfig", () => { + it("overlays project env on top of agent env and unions secret keys", async () => { + const resolveAdapterConfigForRuntime = vi.fn().mockResolvedValue({ + config: { + env: { + SHARED_KEY: "agent", + AGENT_ONLY: "agent-only", + }, + other: "value", + }, + secretKeys: new Set(["AGENT_SECRET"]), + }); + const resolveEnvBindings = vi.fn().mockResolvedValue({ + env: { + SHARED_KEY: "project", + PROJECT_ONLY: "project-only", + }, + secretKeys: new Set(["PROJECT_SECRET"]), + }); + + const result = await resolveExecutionRunAdapterConfig({ + companyId: "company-1", + executionRunConfig: { env: { SHARED_KEY: "agent" } }, + projectEnv: { SHARED_KEY: "project" }, + secretsSvc: { + resolveAdapterConfigForRuntime, + resolveEnvBindings, + } as any, + }); + + expect(result.resolvedConfig).toMatchObject({ + other: "value", + env: { + SHARED_KEY: "project", + AGENT_ONLY: "agent-only", + PROJECT_ONLY: "project-only", + }, + }); + expect(Array.from(result.secretKeys).sort()).toEqual(["AGENT_SECRET", "PROJECT_SECRET"]); + }); + + it("skips project env resolution when the project has no bindings", async () => { + const resolveAdapterConfigForRuntime = vi.fn().mockResolvedValue({ + config: { env: { AGENT_ONLY: "agent-only" } }, + secretKeys: new Set(), + }); + const resolveEnvBindings = vi.fn(); + + const result = await resolveExecutionRunAdapterConfig({ + companyId: "company-1", + executionRunConfig: { env: { AGENT_ONLY: "agent-only" } }, + projectEnv: null, + secretsSvc: { + resolveAdapterConfigForRuntime, + resolveEnvBindings, + } as any, + }); + + expect(result.resolvedConfig.env).toEqual({ AGENT_ONLY: "agent-only" }); + expect(resolveEnvBindings).not.toHaveBeenCalled(); + }); +}); diff --git a/server/src/__tests__/heartbeat-run-summary.test.ts b/server/src/__tests__/heartbeat-run-summary.test.ts index ec6bc2d9210..79efdabe703 100644 --- a/server/src/__tests__/heartbeat-run-summary.test.ts +++ b/server/src/__tests__/heartbeat-run-summary.test.ts @@ -1,5 +1,8 @@ import { describe, expect, it } from "vitest"; -import { summarizeHeartbeatRunResultJson } from "../services/heartbeat-run-summary.js"; +import { + summarizeHeartbeatRunResultJson, + buildHeartbeatRunIssueComment, +} from "../services/heartbeat-run-summary.js"; describe("summarizeHeartbeatRunResultJson", () => { it("truncates text fields and preserves cost aliases", () => { @@ -31,3 +34,24 @@ describe("summarizeHeartbeatRunResultJson", () => { expect(summarizeHeartbeatRunResultJson({ nested: { only: "ignored" } })).toBeNull(); }); }); + +describe("buildHeartbeatRunIssueComment", () => { + it("uses the final summary text for issue comments on successful runs", () => { + const comment = buildHeartbeatRunIssueComment({ + summary: "## Summary\n\n- fixed deploy config\n- posted issue update", + }); + + expect(comment).toContain("## Summary"); + expect(comment).toContain("- fixed deploy config"); + expect(comment).not.toContain("Run summary"); + }); + + it("falls back to result or message when summary is missing", () => { + expect(buildHeartbeatRunIssueComment({ result: "done" })).toBe("done"); + expect(buildHeartbeatRunIssueComment({ message: "completed" })).toBe("completed"); + }); + + it("returns null when there is no usable final text", () => { + expect(buildHeartbeatRunIssueComment({ costUsd: 1.2 })).toBeNull(); + }); +}); diff --git a/server/src/__tests__/heartbeat-workspace-session.test.ts b/server/src/__tests__/heartbeat-workspace-session.test.ts index 47d37c53309..23a58088855 100644 --- a/server/src/__tests__/heartbeat-workspace-session.test.ts +++ b/server/src/__tests__/heartbeat-workspace-session.test.ts @@ -6,7 +6,10 @@ import { applyPersistedExecutionWorkspaceConfig, buildRealizedExecutionWorkspaceFromPersisted, buildExplicitResumeSessionOverride, + deriveTaskKeyWithHeartbeatFallback, + extractWakeCommentIds, formatRuntimeWorkspaceWarningLog, + mergeCoalescedContextSnapshot, prioritizeProjectWorkspaceCandidatesForRun, parseSessionCompactionPolicy, resolveRuntimeSessionParamsForWorkspace, @@ -231,6 +234,18 @@ describe("shouldResetTaskSessionForWake", () => { expect(shouldResetTaskSessionForWake({ wakeReason: "issue_assigned" })).toBe(true); }); + it("resets session context on execution review wakes", () => { + expect(shouldResetTaskSessionForWake({ wakeReason: "execution_review_requested" })).toBe(true); + }); + + it("resets session context on execution approval wakes", () => { + expect(shouldResetTaskSessionForWake({ wakeReason: "execution_approval_requested" })).toBe(true); + }); + + it("resets session context on execution changes-requested wakes", () => { + expect(shouldResetTaskSessionForWake({ wakeReason: "execution_changes_requested" })).toBe(true); + }); + it("preserves session context on timer heartbeats", () => { expect(shouldResetTaskSessionForWake({ wakeSource: "timer" })).toBe(false); }); @@ -290,6 +305,60 @@ describe("shouldResetTaskSessionForWake", () => { }); }); +describe("deriveTaskKeyWithHeartbeatFallback", () => { + it("returns explicit taskKey when present", () => { + expect(deriveTaskKeyWithHeartbeatFallback({ taskKey: "issue-123" }, null)).toBe("issue-123"); + }); + + it("returns explicit issueId when no taskKey", () => { + expect(deriveTaskKeyWithHeartbeatFallback({ issueId: "issue-456" }, null)).toBe("issue-456"); + }); + + it("returns __heartbeat__ for timer wakes with no explicit key", () => { + expect(deriveTaskKeyWithHeartbeatFallback({ wakeSource: "timer" }, null)).toBe("__heartbeat__"); + }); + + it("prefers explicit key over heartbeat fallback even on timer wakes", () => { + expect( + deriveTaskKeyWithHeartbeatFallback({ wakeSource: "timer", taskKey: "issue-789" }, null), + ).toBe("issue-789"); + }); + + it("returns null for non-timer wakes with no explicit key", () => { + expect(deriveTaskKeyWithHeartbeatFallback({ wakeSource: "on_demand" }, null)).toBeNull(); + }); + + it("returns null for empty context", () => { + expect(deriveTaskKeyWithHeartbeatFallback({}, null)).toBeNull(); + }); +}); + +describe("comment wake batching", () => { + it("preserves ordered wake comment ids when coalescing queued follow-up wakes", () => { + const merged = mergeCoalescedContextSnapshot( + { + issueId: "issue-1", + wakeReason: "issue_commented", + wakeCommentId: "comment-1", + wakeCommentIds: ["comment-1"], + paperclipWake: { + latestCommentId: "comment-1", + }, + }, + { + issueId: "issue-1", + wakeReason: "issue_commented", + wakeCommentId: "comment-2", + }, + ); + + expect(extractWakeCommentIds(merged)).toEqual(["comment-1", "comment-2"]); + expect(merged.commentId).toBe("comment-2"); + expect(merged.wakeCommentId).toBe("comment-2"); + expect(merged.paperclipWake).toBeUndefined(); + }); +}); + describe("buildExplicitResumeSessionOverride", () => { it("reuses saved task session params when they belong to the selected failed run", () => { const result = buildExplicitResumeSessionOverride({ diff --git a/server/src/__tests__/hire-hook.test.ts b/server/src/__tests__/hire-hook.test.ts index 0a2cbbfdf25..b08e04bc3cc 100644 --- a/server/src/__tests__/hire-hook.test.ts +++ b/server/src/__tests__/hire-hook.test.ts @@ -4,14 +4,14 @@ import { notifyHireApproved } from "../services/hire-hook.js"; // Mock the registry so we control whether the adapter has onHireApproved and what it does. vi.mock("../adapters/registry.js", () => ({ - findServerAdapter: vi.fn(), + findActiveServerAdapter: vi.fn(), })); vi.mock("../services/activity-log.js", () => ({ logActivity: vi.fn().mockResolvedValue(undefined), })); -const { findServerAdapter } = await import("../adapters/registry.js"); +const { findActiveServerAdapter } = await import("../adapters/registry.js"); const { logActivity } = await import("../services/activity-log.js"); function mockDbWithAgent(agent: { id: string; companyId: string; name: string; adapterType: string; adapterConfig?: Record }): Db { @@ -39,7 +39,7 @@ afterEach(() => { describe("notifyHireApproved", () => { it("writes success activity when adapter hook returns ok", async () => { - vi.mocked(findServerAdapter).mockReturnValue({ + vi.mocked(findActiveServerAdapter).mockReturnValue({ type: "openclaw_gateway", onHireApproved: vi.fn().mockResolvedValue({ ok: true }), } as any); @@ -88,11 +88,11 @@ describe("notifyHireApproved", () => { }), ).resolves.toBeUndefined(); - expect(findServerAdapter).not.toHaveBeenCalled(); + expect(findActiveServerAdapter).not.toHaveBeenCalled(); }); it("does nothing when adapter has no onHireApproved", async () => { - vi.mocked(findServerAdapter).mockReturnValue({ type: "process" } as any); + vi.mocked(findActiveServerAdapter).mockReturnValue({ type: "process" } as any); const db = mockDbWithAgent({ id: "a1", @@ -110,12 +110,12 @@ describe("notifyHireApproved", () => { }), ).resolves.toBeUndefined(); - expect(findServerAdapter).toHaveBeenCalledWith("process"); + expect(findActiveServerAdapter).toHaveBeenCalledWith("process"); expect(logActivity).not.toHaveBeenCalled(); }); it("logs failed result when adapter onHireApproved returns ok=false", async () => { - vi.mocked(findServerAdapter).mockReturnValue({ + vi.mocked(findActiveServerAdapter).mockReturnValue({ type: "openclaw_gateway", onHireApproved: vi.fn().mockResolvedValue({ ok: false, error: "HTTP 500", detail: { status: 500 } }), } as any); @@ -147,7 +147,7 @@ describe("notifyHireApproved", () => { }); it("does not throw when adapter onHireApproved throws (non-fatal)", async () => { - vi.mocked(findServerAdapter).mockReturnValue({ + vi.mocked(findActiveServerAdapter).mockReturnValue({ type: "openclaw_gateway", onHireApproved: vi.fn().mockRejectedValue(new Error("Network error")), } as any); diff --git a/server/src/__tests__/inbox-dismissals.test.ts b/server/src/__tests__/inbox-dismissals.test.ts new file mode 100644 index 00000000000..c6360a219ff --- /dev/null +++ b/server/src/__tests__/inbox-dismissals.test.ts @@ -0,0 +1,212 @@ +import { randomUUID } from "node:crypto"; +import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest"; +import { + agents, + approvals, + companies, + createDb, + heartbeatRuns, + inboxDismissals, + invites, + joinRequests, +} from "@paperclipai/db"; +import { + getEmbeddedPostgresTestSupport, + startEmbeddedPostgresTestDatabase, +} from "./helpers/embedded-postgres.js"; +import { inboxDismissalService } from "../services/inbox-dismissals.ts"; +import { sidebarBadgeService } from "../services/sidebar-badges.ts"; + +const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport(); +const describeEmbeddedPostgres = embeddedPostgresSupport.supported ? describe : describe.skip; + +if (!embeddedPostgresSupport.supported) { + console.warn( + `Skipping embedded Postgres inbox dismissal tests on this host: ${embeddedPostgresSupport.reason ?? "unsupported environment"}`, + ); +} + +describeEmbeddedPostgres("inbox dismissals", () => { + let db!: ReturnType; + let dismissalsSvc!: ReturnType; + let badgesSvc!: ReturnType; + let tempDb: Awaited> | null = null; + + beforeAll(async () => { + tempDb = await startEmbeddedPostgresTestDatabase("paperclip-inbox-dismissals-"); + db = createDb(tempDb.connectionString); + dismissalsSvc = inboxDismissalService(db); + badgesSvc = sidebarBadgeService(db); + }, 20_000); + + afterEach(async () => { + await db.delete(inboxDismissals); + await db.delete(joinRequests); + await db.delete(invites); + await db.delete(heartbeatRuns); + await db.delete(approvals); + await db.delete(agents); + await db.delete(companies); + }); + + afterAll(async () => { + await tempDb?.cleanup(); + }); + + it("upserts a single dismissal record per user and inbox item key", async () => { + const companyId = randomUUID(); + const userId = "board-user"; + const firstDismissedAt = new Date("2026-03-11T01:00:00.000Z"); + const secondDismissedAt = new Date("2026-03-11T02:00:00.000Z"); + + await db.insert(companies).values({ + id: companyId, + name: "Paperclip", + issuePrefix: "PAP", + requireBoardApprovalForNewAgents: false, + }); + + await dismissalsSvc.dismiss(companyId, userId, "approval:approval-1", firstDismissedAt); + await dismissalsSvc.dismiss(companyId, userId, "approval:approval-1", secondDismissedAt); + + const dismissals = await dismissalsSvc.list(companyId, userId); + + expect(dismissals).toHaveLength(1); + expect(dismissals[0]?.itemKey).toBe("approval:approval-1"); + expect(new Date(dismissals[0]?.dismissedAt ?? 0).toISOString()).toBe(secondDismissedAt.toISOString()); + }); + + it("honors dismissal timestamps and resurfaces approvals with newer activity", async () => { + const companyId = randomUUID(); + const userId = "board-user"; + const primaryAgentId = randomUUID(); + const secondaryAgentId = randomUUID(); + const hiddenApprovalId = randomUUID(); + const resurfacedApprovalId = randomUUID(); + const inviteId = randomUUID(); + const hiddenJoinRequestId = randomUUID(); + const hiddenRunId = randomUUID(); + const visibleRunId = randomUUID(); + + await db.insert(companies).values({ + id: companyId, + name: "Paperclip", + issuePrefix: "PAP", + requireBoardApprovalForNewAgents: false, + }); + + await db.insert(agents).values([ + { + id: primaryAgentId, + companyId, + name: "Primary", + role: "engineer", + status: "active", + adapterType: "codex_local", + adapterConfig: {}, + runtimeConfig: {}, + permissions: {}, + }, + { + id: secondaryAgentId, + companyId, + name: "Secondary", + role: "engineer", + status: "active", + adapterType: "codex_local", + adapterConfig: {}, + runtimeConfig: {}, + permissions: {}, + }, + ]); + + await db.insert(approvals).values([ + { + id: hiddenApprovalId, + companyId, + type: "hire_agent", + status: "pending", + payload: {}, + updatedAt: new Date("2026-03-11T01:00:00.000Z"), + }, + { + id: resurfacedApprovalId, + companyId, + type: "hire_agent", + status: "revision_requested", + payload: {}, + updatedAt: new Date("2026-03-11T03:00:00.000Z"), + }, + ]); + + await db.insert(invites).values({ + id: inviteId, + companyId, + inviteType: "company_join", + tokenHash: "hash-1", + allowedJoinTypes: "both", + expiresAt: new Date("2026-03-12T00:00:00.000Z"), + }); + + await db.insert(joinRequests).values({ + id: hiddenJoinRequestId, + inviteId, + companyId, + requestType: "human", + status: "pending_approval", + requestIp: "127.0.0.1", + createdAt: new Date("2026-03-11T01:00:00.000Z"), + updatedAt: new Date("2026-03-11T01:00:00.000Z"), + }); + + await db.insert(heartbeatRuns).values([ + { + id: hiddenRunId, + companyId, + agentId: primaryAgentId, + invocationSource: "assignment", + status: "failed", + createdAt: new Date("2026-03-11T01:00:00.000Z"), + updatedAt: new Date("2026-03-11T01:00:00.000Z"), + }, + { + id: visibleRunId, + companyId, + agentId: secondaryAgentId, + invocationSource: "assignment", + status: "timed_out", + createdAt: new Date("2026-03-11T04:00:00.000Z"), + updatedAt: new Date("2026-03-11T04:00:00.000Z"), + }, + ]); + + await dismissalsSvc.dismiss(companyId, userId, `approval:${hiddenApprovalId}`, new Date("2026-03-11T02:00:00.000Z")); + await dismissalsSvc.dismiss(companyId, userId, `approval:${resurfacedApprovalId}`, new Date("2026-03-11T02:00:00.000Z")); + await dismissalsSvc.dismiss(companyId, userId, `join:${hiddenJoinRequestId}`, new Date("2026-03-11T02:00:00.000Z")); + await dismissalsSvc.dismiss(companyId, userId, `run:${hiddenRunId}`, new Date("2026-03-11T02:00:00.000Z")); + + const dismissedAtByKey = new Map( + (await dismissalsSvc.list(companyId, userId)).map((dismissal) => [ + dismissal.itemKey, + new Date(dismissal.dismissedAt).getTime(), + ]), + ); + + const badges = await badgesSvc.get(companyId, { + dismissals: dismissedAtByKey, + joinRequests: [{ + id: hiddenJoinRequestId, + createdAt: new Date("2026-03-11T01:00:00.000Z"), + updatedAt: new Date("2026-03-11T01:00:00.000Z"), + }], + unreadTouchedIssues: 1, + }); + + expect(badges).toEqual({ + inbox: 3, + approvals: 1, + failedRuns: 1, + joinRequests: 0, + }); + }); +}); diff --git a/server/src/__tests__/instance-settings-routes.test.ts b/server/src/__tests__/instance-settings-routes.test.ts index 9668d1bfb93..b09d8393dbe 100644 --- a/server/src/__tests__/instance-settings-routes.test.ts +++ b/server/src/__tests__/instance-settings-routes.test.ts @@ -1,8 +1,6 @@ import express from "express"; import request from "supertest"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { errorHandler } from "../middleware/index.js"; -import { instanceSettingsRoutes } from "../routes/instance-settings.js"; const mockInstanceSettingsService = vi.hoisted(() => ({ getGeneral: vi.fn(), @@ -13,12 +11,18 @@ const mockInstanceSettingsService = vi.hoisted(() => ({ })); const mockLogActivity = vi.hoisted(() => vi.fn()); -vi.mock("../services/index.js", () => ({ - instanceSettingsService: () => mockInstanceSettingsService, - logActivity: mockLogActivity, -})); +function registerRouteMocks() { + vi.doMock("../services/index.js", () => ({ + instanceSettingsService: () => mockInstanceSettingsService, + logActivity: mockLogActivity, + })); +} -function createApp(actor: any) { +async function createApp(actor: any) { + const [{ instanceSettingsRoutes }, { errorHandler }] = await Promise.all([ + import("../routes/instance-settings.js"), + import("../middleware/index.js"), + ]); const app = express(); app.use(express.json()); app.use((req, _res, next) => { @@ -32,9 +36,13 @@ function createApp(actor: any) { describe("instance settings routes", () => { beforeEach(() => { + vi.resetModules(); + registerRouteMocks(); vi.clearAllMocks(); mockInstanceSettingsService.getGeneral.mockResolvedValue({ censorUsernameInLogs: false, + keyboardShortcuts: false, + feedbackDataSharingPreference: "prompt", }); mockInstanceSettingsService.getExperimental.mockResolvedValue({ enableIsolatedWorkspaces: false, @@ -44,6 +52,8 @@ describe("instance settings routes", () => { id: "instance-settings-1", general: { censorUsernameInLogs: true, + keyboardShortcuts: true, + feedbackDataSharingPreference: "allowed", }, }); mockInstanceSettingsService.updateExperimental.mockResolvedValue({ @@ -57,7 +67,7 @@ describe("instance settings routes", () => { }); it("allows local board users to read and update experimental settings", async () => { - const app = createApp({ + const app = await createApp({ type: "board", userId: "local-board", source: "local_implicit", @@ -83,7 +93,7 @@ describe("instance settings routes", () => { }); it("allows local board users to update guarded dev-server auto-restart", async () => { - const app = createApp({ + const app = await createApp({ type: "board", userId: "local-board", source: "local_implicit", @@ -101,7 +111,7 @@ describe("instance settings routes", () => { }); it("allows local board users to read and update general settings", async () => { - const app = createApp({ + const app = await createApp({ type: "board", userId: "local-board", source: "local_implicit", @@ -110,21 +120,31 @@ describe("instance settings routes", () => { const getRes = await request(app).get("/api/instance/settings/general"); expect(getRes.status).toBe(200); - expect(getRes.body).toEqual({ censorUsernameInLogs: false }); + expect(getRes.body).toEqual({ + censorUsernameInLogs: false, + keyboardShortcuts: false, + feedbackDataSharingPreference: "prompt", + }); const patchRes = await request(app) .patch("/api/instance/settings/general") - .send({ censorUsernameInLogs: true }); + .send({ + censorUsernameInLogs: true, + keyboardShortcuts: true, + feedbackDataSharingPreference: "allowed", + }); expect(patchRes.status).toBe(200); expect(mockInstanceSettingsService.updateGeneral).toHaveBeenCalledWith({ censorUsernameInLogs: true, + keyboardShortcuts: true, + feedbackDataSharingPreference: "allowed", }); expect(mockLogActivity).toHaveBeenCalledTimes(2); }); - it("rejects non-admin board users", async () => { - const app = createApp({ + it("allows non-admin board users to read general settings", async () => { + const app = await createApp({ type: "board", userId: "user-1", source: "session", @@ -134,12 +154,29 @@ describe("instance settings routes", () => { const res = await request(app).get("/api/instance/settings/general"); + expect(res.status).toBe(200); + expect(mockInstanceSettingsService.getGeneral).toHaveBeenCalled(); + }); + + it("rejects non-admin board users from updating general settings", async () => { + const app = await createApp({ + type: "board", + userId: "user-1", + source: "session", + isInstanceAdmin: false, + companyIds: ["company-1"], + }); + + const res = await request(app) + .patch("/api/instance/settings/general") + .send({ censorUsernameInLogs: true, keyboardShortcuts: true }); + expect(res.status).toBe(403); - expect(mockInstanceSettingsService.getGeneral).not.toHaveBeenCalled(); + expect(mockInstanceSettingsService.updateGeneral).not.toHaveBeenCalled(); }); it("rejects agent callers", async () => { - const app = createApp({ + const app = await createApp({ type: "agent", agentId: "agent-1", companyId: "company-1", @@ -148,7 +185,7 @@ describe("instance settings routes", () => { const res = await request(app) .patch("/api/instance/settings/general") - .send({ censorUsernameInLogs: true }); + .send({ feedbackDataSharingPreference: "not_allowed" }); expect(res.status).toBe(403); expect(mockInstanceSettingsService.updateGeneral).not.toHaveBeenCalled(); diff --git a/server/src/__tests__/issue-activity-events-routes.test.ts b/server/src/__tests__/issue-activity-events-routes.test.ts new file mode 100644 index 00000000000..d4e99a8e54f --- /dev/null +++ b/server/src/__tests__/issue-activity-events-routes.test.ts @@ -0,0 +1,244 @@ +import express from "express"; +import request from "supertest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { errorHandler } from "../middleware/index.js"; +import { issueRoutes } from "../routes/issues.js"; +import { normalizeIssueExecutionPolicy } from "../services/issue-execution-policy.ts"; + +const mockIssueService = vi.hoisted(() => ({ + getById: vi.fn(), + assertCheckoutOwner: vi.fn(), + update: vi.fn(), + addComment: vi.fn(), + findMentionedAgents: vi.fn(), + getRelationSummaries: vi.fn(), + listWakeableBlockedDependents: vi.fn(), + getWakeableParentAfterChildCompletion: vi.fn(), +})); + +const mockLogActivity = vi.hoisted(() => vi.fn(async () => undefined)); + +vi.mock("../services/index.js", () => ({ + accessService: () => ({ + canUser: vi.fn(async () => false), + hasPermission: vi.fn(async () => false), + }), + agentService: () => ({ + getById: vi.fn(async () => null), + }), + documentService: () => ({}), + executionWorkspaceService: () => ({}), + feedbackService: () => ({ + listIssueVotesForUser: vi.fn(async () => []), + saveIssueVote: vi.fn(async () => ({ vote: null, consentEnabledNow: false, sharingEnabled: false })), + }), + goalService: () => ({}), + heartbeatService: () => ({ + wakeup: vi.fn(async () => undefined), + reportRunActivity: vi.fn(async () => undefined), + getRun: vi.fn(async () => null), + getActiveRunForAgent: vi.fn(async () => null), + cancelRun: vi.fn(async () => null), + }), + instanceSettingsService: () => ({ + get: vi.fn(async () => ({ + id: "instance-settings-1", + general: { + censorUsernameInLogs: false, + feedbackDataSharingPreference: "prompt", + }, + })), + listCompanyIds: vi.fn(async () => ["company-1"]), + }), + issueApprovalService: () => ({}), + issueService: () => mockIssueService, + logActivity: mockLogActivity, + projectService: () => ({}), + routineService: () => ({ + syncRunStatusForIssue: vi.fn(async () => undefined), + }), + workProductService: () => ({}), +})); + +function createApp() { + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { + (req as any).actor = { + type: "board", + userId: "local-board", + companyIds: ["company-1"], + source: "local_implicit", + isInstanceAdmin: false, + }; + next(); + }); + app.use("/api", issueRoutes({} as any, {} as any)); + app.use(errorHandler); + return app; +} + +function makeIssue() { + return { + id: "11111111-1111-4111-8111-111111111111", + companyId: "company-1", + status: "todo", + assigneeAgentId: "22222222-2222-4222-8222-222222222222", + assigneeUserId: null, + createdByUserId: "local-board", + identifier: "PAP-580", + title: "Activity event issue", + executionPolicy: null, + executionState: null, + }; +} + +describe("issue activity event routes", () => { + beforeEach(() => { + vi.clearAllMocks(); + mockIssueService.assertCheckoutOwner.mockResolvedValue({ adoptedFromRunId: null }); + mockIssueService.findMentionedAgents.mockResolvedValue([]); + mockIssueService.getRelationSummaries.mockResolvedValue({ blockedBy: [], blocks: [] }); + mockIssueService.listWakeableBlockedDependents.mockResolvedValue([]); + mockIssueService.getWakeableParentAfterChildCompletion.mockResolvedValue(null); + }); + + it("logs blocker activity with added and removed issue summaries", async () => { + const issue = makeIssue(); + mockIssueService.getById.mockResolvedValue(issue); + mockIssueService.getRelationSummaries + .mockResolvedValueOnce({ + blockedBy: [ + { + id: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + identifier: "PAP-10", + title: "Old blocker", + status: "todo", + priority: "medium", + assigneeAgentId: null, + assigneeUserId: null, + }, + ], + blocks: [], + }) + .mockResolvedValueOnce({ + blockedBy: [ + { + id: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + identifier: "PAP-11", + title: "New blocker", + status: "todo", + priority: "medium", + assigneeAgentId: null, + assigneeUserId: null, + }, + ], + blocks: [], + }); + mockIssueService.update.mockImplementation(async (_id: string, patch: Record) => ({ + ...issue, + ...patch, + updatedAt: new Date(), + })); + + const res = await request(createApp()) + .patch("/api/issues/11111111-1111-4111-8111-111111111111") + .send({ blockedByIssueIds: ["bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"] }); + + expect(res.status).toBe(200); + expect(mockLogActivity).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + action: "issue.blockers_updated", + details: expect.objectContaining({ + addedBlockedByIssueIds: ["bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"], + removedBlockedByIssueIds: ["aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"], + addedBlockedByIssues: [ + { + id: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + identifier: "PAP-11", + title: "New blocker", + }, + ], + removedBlockedByIssues: [ + { + id: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + identifier: "PAP-10", + title: "Old blocker", + }, + ], + }), + }), + ); + }); + + it("logs explicit reviewer and approver activity when execution policy participants change", async () => { + const existingPolicy = normalizeIssueExecutionPolicy({ + stages: [ + { + id: "11111111-1111-4111-8111-111111111111", + type: "review", + participants: [{ type: "agent", agentId: "11111111-2222-4333-8444-555555555555" }], + }, + { + id: "22222222-2222-4222-8222-222222222222", + type: "approval", + participants: [{ type: "agent", agentId: "66666666-7777-4888-8999-aaaaaaaaaaaa" }], + }, + ], + })!; + const nextPolicy = normalizeIssueExecutionPolicy({ + stages: [ + { + id: "11111111-1111-4111-8111-111111111111", + type: "review", + participants: [{ type: "agent", agentId: "bbbbbbbb-cccc-4ddd-8eee-ffffffffffff" }], + }, + { + id: "22222222-2222-4222-8222-222222222222", + type: "approval", + participants: [{ type: "user", userId: "local-board" }], + }, + ], + })!; + const issue = { + ...makeIssue(), + executionPolicy: existingPolicy, + }; + mockIssueService.getById.mockResolvedValue(issue); + mockIssueService.update.mockImplementation(async (_id: string, patch: Record) => ({ + ...issue, + ...patch, + executionPolicy: patch.executionPolicy, + updatedAt: new Date(), + })); + + const res = await request(createApp()) + .patch("/api/issues/11111111-1111-4111-8111-111111111111") + .send({ executionPolicy: nextPolicy }); + + expect(res.status).toBe(200); + expect(mockLogActivity).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + action: "issue.reviewers_updated", + details: expect.objectContaining({ + participants: [{ type: "agent", agentId: "bbbbbbbb-cccc-4ddd-8eee-ffffffffffff", userId: null }], + addedParticipants: [{ type: "agent", agentId: "bbbbbbbb-cccc-4ddd-8eee-ffffffffffff", userId: null }], + removedParticipants: [{ type: "agent", agentId: "11111111-2222-4333-8444-555555555555", userId: null }], + }), + }), + ); + expect(mockLogActivity).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + action: "issue.approvers_updated", + details: expect.objectContaining({ + participants: [{ type: "user", agentId: null, userId: "local-board" }], + addedParticipants: [{ type: "user", agentId: null, userId: "local-board" }], + removedParticipants: [{ type: "agent", agentId: "66666666-7777-4888-8999-aaaaaaaaaaaa", userId: null }], + }), + }), + ); + }); +}); diff --git a/server/src/__tests__/issue-attachment-routes.test.ts b/server/src/__tests__/issue-attachment-routes.test.ts new file mode 100644 index 00000000000..e7234e51ebf --- /dev/null +++ b/server/src/__tests__/issue-attachment-routes.test.ts @@ -0,0 +1,199 @@ +import { Readable } from "node:stream"; +import express from "express"; +import request from "supertest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import type { StorageService } from "../storage/types.js"; + +const mockIssueService = vi.hoisted(() => ({ + getById: vi.fn(), + getByIdentifier: vi.fn(), + createAttachment: vi.fn(), + getAttachmentById: vi.fn(), +})); + +const mockLogActivity = vi.hoisted(() => vi.fn(async () => undefined)); + +function registerRouteMocks() { + vi.doMock("@paperclipai/shared/telemetry", () => ({ + trackAgentTaskCompleted: vi.fn(), + trackErrorHandlerCrash: vi.fn(), + })); + + vi.doMock("../telemetry.js", () => ({ + getTelemetryClient: vi.fn(() => ({ track: vi.fn() })), + })); + + vi.doMock("../services/index.js", () => ({ + accessService: () => ({ + canUser: vi.fn(), + hasPermission: vi.fn(), + }), + agentService: () => ({ + getById: vi.fn(), + }), + documentService: () => ({}), + executionWorkspaceService: () => ({}), + feedbackService: () => ({ + listIssueVotesForUser: vi.fn(async () => []), + saveIssueVote: vi.fn(async () => ({ vote: null, consentEnabledNow: false, sharingEnabled: false })), + }), + goalService: () => ({}), + heartbeatService: () => ({ + wakeup: vi.fn(async () => undefined), + reportRunActivity: vi.fn(async () => undefined), + getRun: vi.fn(async () => null), + getActiveRunForAgent: vi.fn(async () => null), + cancelRun: vi.fn(async () => null), + }), + instanceSettingsService: () => ({ + get: vi.fn(async () => ({ + id: "instance-settings-1", + general: { + censorUsernameInLogs: false, + feedbackDataSharingPreference: "prompt", + }, + })), + listCompanyIds: vi.fn(async () => ["company-1"]), + }), + issueApprovalService: () => ({}), + issueService: () => mockIssueService, + logActivity: mockLogActivity, + projectService: () => ({}), + routineService: () => ({ + syncRunStatusForIssue: vi.fn(async () => undefined), + }), + workProductService: () => ({}), + })); +} + +function createStorageService(): StorageService { + return { + provider: "local_disk", + putFile: vi.fn(async (input) => ({ + provider: "local_disk", + objectKey: `${input.namespace}/${input.originalFilename ?? "upload"}`, + contentType: input.contentType, + byteSize: input.body.length, + sha256: "sha256-sample", + originalFilename: input.originalFilename, + })), + getObject: vi.fn(async () => ({ + stream: Readable.from(Buffer.from("test")), + contentLength: 4, + })), + headObject: vi.fn(), + deleteObject: vi.fn(), + }; +} + +async function createApp(storage: StorageService) { + const [{ errorHandler }, { issueRoutes }] = await Promise.all([ + import("../middleware/index.js"), + import("../routes/issues.js"), + ]); + const app = express(); + app.use((req, _res, next) => { + (req as any).actor = { + type: "board", + userId: "local-board", + companyIds: ["company-1"], + source: "local_implicit", + isInstanceAdmin: false, + }; + next(); + }); + app.use("/api", issueRoutes({} as any, storage)); + app.use(errorHandler); + return app; +} + +function makeAttachment(contentType: string, originalFilename: string) { + const now = new Date("2026-01-01T00:00:00.000Z"); + return { + id: "attachment-1", + companyId: "company-1", + issueId: "11111111-1111-4111-8111-111111111111", + issueCommentId: null, + assetId: "asset-1", + provider: "local_disk", + objectKey: `issues/issue-1/${originalFilename}`, + contentType, + byteSize: 4, + sha256: "sha256-sample", + originalFilename, + createdByAgentId: null, + createdByUserId: "local-board", + createdAt: now, + updatedAt: now, + }; +} + +describe("issue attachment routes", () => { + beforeEach(() => { + vi.resetModules(); + registerRouteMocks(); + vi.clearAllMocks(); + }); + + it("accepts zip uploads for issue attachments", async () => { + const storage = createStorageService(); + mockIssueService.getById.mockResolvedValue({ + id: "11111111-1111-4111-8111-111111111111", + companyId: "company-1", + identifier: "PAP-1", + }); + mockIssueService.createAttachment.mockResolvedValue(makeAttachment("application/zip", "bundle.zip")); + + const app = await createApp(storage); + const res = await request(app) + .post("/api/companies/company-1/issues/11111111-1111-4111-8111-111111111111/attachments") + .attach("file", Buffer.from("zip"), { filename: "bundle.zip", contentType: "application/zip" }); + + expect(res.status).toBe(201); + const putFileCall = vi.mocked(storage.putFile).mock.calls[0]?.[0]; + expect(putFileCall).toMatchObject({ + companyId: "company-1", + namespace: "issues/11111111-1111-4111-8111-111111111111", + originalFilename: "bundle.zip", + contentType: "application/zip", + }); + expect(Buffer.isBuffer(putFileCall?.body)).toBe(true); + expect(mockIssueService.createAttachment).toHaveBeenCalledWith( + expect.objectContaining({ + issueId: "11111111-1111-4111-8111-111111111111", + contentType: "application/zip", + originalFilename: "bundle.zip", + }), + ); + expect(res.body.contentType).toBe("application/zip"); + }); + + it("serves html attachments as downloads with nosniff", async () => { + const storage = createStorageService(); + mockIssueService.getAttachmentById.mockResolvedValue(makeAttachment("text/html", "report.html")); + + const app = await createApp(storage); + const res = await request(app).get("/api/attachments/attachment-1/content"); + + expect(res.status).toBe(200); + expect([ + undefined, + 'attachment; filename="report.html"', + ]).toContain(res.headers["content-disposition"]); + expect(res.headers["x-content-type-options"]).toBe("nosniff"); + }); + + it("keeps image attachments inline for previews", async () => { + const storage = createStorageService(); + mockIssueService.getAttachmentById.mockResolvedValue(makeAttachment("image/png", "preview.png")); + + const app = await createApp(storage); + const res = await request(app).get("/api/attachments/attachment-1/content"); + + expect(res.status).toBe(200); + expect([ + undefined, + 'inline; filename="preview.png"', + ]).toContain(res.headers["content-disposition"]); + }); +}); diff --git a/server/src/__tests__/issue-closed-workspace-routes.test.ts b/server/src/__tests__/issue-closed-workspace-routes.test.ts new file mode 100644 index 00000000000..8772ff60e40 --- /dev/null +++ b/server/src/__tests__/issue-closed-workspace-routes.test.ts @@ -0,0 +1,193 @@ +import express from "express"; +import request from "supertest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const issueId = "11111111-1111-4111-8111-111111111111"; +const closedWorkspaceId = "33333333-3333-4333-8333-333333333333"; +const nextWorkspaceId = "44444444-4444-4444-8444-444444444444"; +const agentId = "22222222-2222-4222-8222-222222222222"; + +const mockIssueService = vi.hoisted(() => ({ + getById: vi.fn(), + update: vi.fn(), + checkout: vi.fn(), + addComment: vi.fn(), +})); + +const mockExecutionWorkspaceService = vi.hoisted(() => ({ + getById: vi.fn(), +})); + +const mockAccessService = vi.hoisted(() => ({ + canUser: vi.fn(), + hasPermission: vi.fn(), +})); + +const mockHeartbeatService = vi.hoisted(() => ({ + wakeup: vi.fn(async () => undefined), + reportRunActivity: vi.fn(async () => undefined), + getRun: vi.fn(async () => null), + getActiveRunForAgent: vi.fn(async () => null), + cancelRun: vi.fn(async () => null), +})); + +const mockProjectService = vi.hoisted(() => ({ + getById: vi.fn(async () => null), +})); + +const mockLogActivity = vi.hoisted(() => vi.fn(async () => undefined)); + +function registerServiceMocks() { + vi.doMock("@paperclipai/shared/telemetry", () => ({ + trackAgentTaskCompleted: vi.fn(), + trackErrorHandlerCrash: vi.fn(), + })); + + vi.doMock("../telemetry.js", () => ({ + getTelemetryClient: vi.fn(() => ({ track: vi.fn() })), + })); + + vi.doMock("../services/index.js", () => ({ + accessService: () => mockAccessService, + agentService: () => ({ + getById: vi.fn(async () => null), + }), + documentService: () => ({}), + executionWorkspaceService: () => mockExecutionWorkspaceService, + feedbackService: () => ({ + listIssueVotesForUser: vi.fn(async () => []), + saveIssueVote: vi.fn(async () => ({ vote: null, consentEnabledNow: false, sharingEnabled: false })), + }), + goalService: () => ({ + getDefaultCompanyGoal: vi.fn(async () => null), + getById: vi.fn(async () => null), + }), + heartbeatService: () => mockHeartbeatService, + instanceSettingsService: () => ({ + get: vi.fn(async () => ({ + id: "instance-settings-1", + general: { + censorUsernameInLogs: false, + feedbackDataSharingPreference: "prompt", + }, + })), + listCompanyIds: vi.fn(async () => ["company-1"]), + }), + issueApprovalService: () => ({}), + issueService: () => mockIssueService, + logActivity: mockLogActivity, + projectService: () => mockProjectService, + routineService: () => ({ + syncRunStatusForIssue: vi.fn(async () => undefined), + }), + workProductService: () => ({}), + })); +} + +async function createApp() { + const [{ issueRoutes }, { errorHandler }] = await Promise.all([ + import("../routes/issues.js"), + import("../middleware/index.js"), + ]); + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { + (req as any).actor = { + type: "board", + userId: "local-board", + companyIds: ["company-1"], + source: "local_implicit", + isInstanceAdmin: false, + }; + next(); + }); + app.use("/api", issueRoutes({} as any, {} as any)); + app.use(errorHandler); + return app; +} + +function makeIssue() { + return { + id: issueId, + companyId: "company-1", + status: "todo", + priority: "medium", + assigneeAgentId: agentId, + assigneeUserId: null, + createdByUserId: "local-board", + identifier: "PAP-1085", + title: "Closed worktree issue", + projectId: null, + executionRunId: null, + checkoutRunId: null, + executionWorkspaceId: closedWorkspaceId, + }; +} + +function makeClosedWorkspace() { + return { + id: closedWorkspaceId, + name: "PAP-1085-fix-worktree-guard", + mode: "isolated_workspace", + status: "archived", + closedAt: new Date("2026-04-04T17:00:00.000Z"), + }; +} + +describe("closed isolated workspace issue routes", () => { + beforeEach(() => { + vi.resetModules(); + registerServiceMocks(); + vi.clearAllMocks(); + mockIssueService.getById.mockResolvedValue(makeIssue()); + mockExecutionWorkspaceService.getById.mockResolvedValue(makeClosedWorkspace()); + }); + + it("rejects new issue comments when the linked isolated workspace is closed", async () => { + const res = await request(await createApp()) + .post(`/api/issues/${issueId}/comments`) + .send({ body: "hello" }); + + expect(res.status).toBe(409); + expect(res.body.error).toContain("closed workspace"); + expect(mockIssueService.addComment).not.toHaveBeenCalled(); + }); + + it("rejects comment updates when the linked isolated workspace is closed", async () => { + const res = await request(await createApp()) + .patch(`/api/issues/${issueId}`) + .send({ comment: "hello" }); + + expect(res.status).toBe(409); + expect(res.body.error).toContain("closed workspace"); + expect(mockIssueService.update).not.toHaveBeenCalled(); + expect(mockIssueService.addComment).not.toHaveBeenCalled(); + }); + + it("rejects checkout when the linked isolated workspace is closed", async () => { + const res = await request(await createApp()) + .post(`/api/issues/${issueId}/checkout`) + .send({ + agentId, + expectedStatuses: ["todo", "backlog", "blocked"], + }); + + expect(res.status).toBe(409); + expect(res.body.error).toContain("closed workspace"); + expect(mockIssueService.checkout).not.toHaveBeenCalled(); + }); + + it("still allows non-comment board updates so the issue can be moved to a new workspace", async () => { + mockIssueService.update.mockResolvedValue({ + ...makeIssue(), + executionWorkspaceId: nextWorkspaceId, + }); + + const res = await request(await createApp()) + .patch(`/api/issues/${issueId}`) + .send({ executionWorkspaceId: nextWorkspaceId }); + + expect(res.status).toBe(200); + expect(res.body.executionWorkspaceId).toBe(nextWorkspaceId); + }); +}); diff --git a/server/src/__tests__/issue-comment-reopen-routes.test.ts b/server/src/__tests__/issue-comment-reopen-routes.test.ts index 21bb44aa3bb..d205417b2ab 100644 --- a/server/src/__tests__/issue-comment-reopen-routes.test.ts +++ b/server/src/__tests__/issue-comment-reopen-routes.test.ts @@ -1,14 +1,15 @@ import express from "express"; import request from "supertest"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { issueRoutes } from "../routes/issues.js"; -import { errorHandler } from "../middleware/index.js"; const mockIssueService = vi.hoisted(() => ({ getById: vi.fn(), + assertCheckoutOwner: vi.fn(), update: vi.fn(), addComment: vi.fn(), findMentionedAgents: vi.fn(), + listWakeableBlockedDependents: vi.fn(), + getWakeableParentAfterChildCompletion: vi.fn(), })); const mockAccessService = vi.hoisted(() => ({ @@ -29,29 +30,70 @@ const mockAgentService = vi.hoisted(() => ({ })); const mockLogActivity = vi.hoisted(() => vi.fn(async () => undefined)); - -vi.mock("../services/index.js", () => ({ - accessService: () => mockAccessService, - agentService: () => mockAgentService, - documentService: () => ({}), - executionWorkspaceService: () => ({}), - goalService: () => ({}), - heartbeatService: () => mockHeartbeatService, - issueApprovalService: () => ({}), - issueService: () => mockIssueService, - logActivity: mockLogActivity, - projectService: () => ({}), - routineService: () => ({ - syncRunStatusForIssue: vi.fn(async () => undefined), - }), - workProductService: () => ({}), +const mockTxInsertValues = vi.hoisted(() => vi.fn(async () => undefined)); +const mockTxInsert = vi.hoisted(() => vi.fn(() => ({ values: mockTxInsertValues }))); +const mockTx = vi.hoisted(() => ({ + insert: mockTxInsert, })); +const mockDb = vi.hoisted(() => ({ + transaction: vi.fn(async (fn: (tx: typeof mockTx) => Promise) => fn(mockTx)), +})); + +function registerServiceMocks() { + vi.doMock("@paperclipai/shared/telemetry", () => ({ + trackAgentTaskCompleted: vi.fn(), + trackErrorHandlerCrash: vi.fn(), + })); + + vi.doMock("../telemetry.js", () => ({ + getTelemetryClient: vi.fn(() => ({ track: vi.fn() })), + })); + + vi.doMock("../services/index.js", () => ({ + accessService: () => mockAccessService, + agentService: () => mockAgentService, + documentService: () => ({}), + executionWorkspaceService: () => ({}), + feedbackService: () => ({ + listIssueVotesForUser: vi.fn(async () => []), + saveIssueVote: vi.fn(async () => ({ vote: null, consentEnabledNow: false, sharingEnabled: false })), + }), + goalService: () => ({}), + heartbeatService: () => mockHeartbeatService, + instanceSettingsService: () => ({ + get: vi.fn(async () => ({ + id: "instance-settings-1", + general: { + censorUsernameInLogs: false, + feedbackDataSharingPreference: "prompt", + }, + })), + listCompanyIds: vi.fn(async () => ["company-1"]), + }), + issueApprovalService: () => ({}), + issueService: () => mockIssueService, + logActivity: mockLogActivity, + projectService: () => ({}), + routineService: () => ({ + syncRunStatusForIssue: vi.fn(async () => undefined), + }), + workProductService: () => ({}), + })); +} function createApp() { const app = express(); app.use(express.json()); + return app; +} + +async function installActor(app: express.Express, actor?: Record) { + const [{ issueRoutes }, { errorHandler }] = await Promise.all([ + import("../routes/issues.js"), + import("../middleware/index.js"), + ]); app.use((req, _res, next) => { - (req as any).actor = { + (req as any).actor = actor ?? { type: "board", userId: "local-board", companyIds: ["company-1"], @@ -60,11 +102,22 @@ function createApp() { }; next(); }); - app.use("/api", issueRoutes({} as any, {} as any)); + app.use("/api", issueRoutes(mockDb as any, {} as any)); app.use(errorHandler); return app; } +async function normalizePolicy(input: { + stages: Array<{ + id: string; + type: "review" | "approval"; + participants: Array<{ type: "agent"; agentId: string } | { type: "user"; userId: string }>; + }>; +}) { + const { normalizeIssueExecutionPolicy } = await import("../services/issue-execution-policy.js"); + return normalizeIssueExecutionPolicy(input); +} + function makeIssue(status: "todo" | "done") { return { id: "11111111-1111-4111-8111-111111111111", @@ -80,7 +133,36 @@ function makeIssue(status: "todo" | "done") { describe("issue comment reopen routes", () => { beforeEach(() => { - vi.clearAllMocks(); + vi.resetModules(); + registerServiceMocks(); + mockIssueService.getById.mockReset(); + mockIssueService.assertCheckoutOwner.mockReset(); + mockIssueService.update.mockReset(); + mockIssueService.addComment.mockReset(); + mockIssueService.findMentionedAgents.mockReset(); + mockIssueService.listWakeableBlockedDependents.mockReset(); + mockIssueService.getWakeableParentAfterChildCompletion.mockReset(); + mockAccessService.canUser.mockReset(); + mockAccessService.hasPermission.mockReset(); + mockHeartbeatService.wakeup.mockReset(); + mockHeartbeatService.reportRunActivity.mockReset(); + mockHeartbeatService.getRun.mockReset(); + mockHeartbeatService.getActiveRunForAgent.mockReset(); + mockHeartbeatService.cancelRun.mockReset(); + mockAgentService.getById.mockReset(); + mockLogActivity.mockReset(); + mockTxInsertValues.mockReset(); + mockTxInsert.mockReset(); + mockDb.transaction.mockReset(); + mockTxInsertValues.mockResolvedValue(undefined); + mockTxInsert.mockImplementation(() => ({ values: mockTxInsertValues })); + mockDb.transaction.mockImplementation(async (fn: (tx: typeof mockTx) => Promise) => fn(mockTx)); + mockHeartbeatService.wakeup.mockResolvedValue(undefined); + mockHeartbeatService.reportRunActivity.mockResolvedValue(undefined); + mockHeartbeatService.getRun.mockResolvedValue(null); + mockHeartbeatService.getActiveRunForAgent.mockResolvedValue(null); + mockHeartbeatService.cancelRun.mockResolvedValue(null); + mockLogActivity.mockResolvedValue(undefined); mockIssueService.addComment.mockResolvedValue({ id: "comment-1", issueId: "11111111-1111-4111-8111-111111111111", @@ -92,6 +174,12 @@ describe("issue comment reopen routes", () => { authorUserId: "local-board", }); mockIssueService.findMentionedAgents.mockResolvedValue([]); + mockIssueService.listWakeableBlockedDependents.mockResolvedValue([]); + mockIssueService.getWakeableParentAfterChildCompletion.mockResolvedValue(null); + mockIssueService.assertCheckoutOwner.mockResolvedValue({ adoptedFromRunId: null }); + mockAccessService.canUser.mockResolvedValue(false); + mockAccessService.hasPermission.mockResolvedValue(false); + mockAgentService.getById.mockResolvedValue(null); }); it("treats reopen=true as a no-op when the issue is already open", async () => { @@ -101,14 +189,12 @@ describe("issue comment reopen routes", () => { ...patch, })); - const res = await request(createApp()) + const res = await request(await installActor(createApp())) .patch("/api/issues/11111111-1111-4111-8111-111111111111") .send({ comment: "hello", reopen: true, assigneeAgentId: "33333333-3333-4333-8333-333333333333" }); expect(res.status).toBe(200); - expect(mockIssueService.update).toHaveBeenCalledWith("11111111-1111-4111-8111-111111111111", { - assigneeAgentId: "33333333-3333-4333-8333-333333333333", - }); + expect(res.body.assigneeAgentId).toBe("33333333-3333-4333-8333-333333333333"); expect(mockLogActivity).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ @@ -125,15 +211,20 @@ describe("issue comment reopen routes", () => { ...patch, })); - const res = await request(createApp()) + const res = await request(await installActor(createApp())) .patch("/api/issues/11111111-1111-4111-8111-111111111111") .send({ comment: "hello", reopen: true, assigneeAgentId: "33333333-3333-4333-8333-333333333333" }); expect(res.status).toBe(200); - expect(mockIssueService.update).toHaveBeenCalledWith("11111111-1111-4111-8111-111111111111", { - assigneeAgentId: "33333333-3333-4333-8333-333333333333", - status: "todo", - }); + expect(mockIssueService.update).toHaveBeenCalledWith( + "11111111-1111-4111-8111-111111111111", + expect.objectContaining({ + assigneeAgentId: "33333333-3333-4333-8333-333333333333", + status: "todo", + actorAgentId: null, + actorUserId: "local-board", + }), + ); expect(mockLogActivity).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ diff --git a/server/src/__tests__/issue-dependency-wakeups-routes.test.ts b/server/src/__tests__/issue-dependency-wakeups-routes.test.ts new file mode 100644 index 00000000000..16d7cd14930 --- /dev/null +++ b/server/src/__tests__/issue-dependency-wakeups-routes.test.ts @@ -0,0 +1,212 @@ +import express from "express"; +import request from "supertest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { issueRoutes } from "../routes/issues.js"; + +const mockWakeup = vi.hoisted(() => vi.fn(async () => undefined)); +const mockIssueService = vi.hoisted(() => ({ + getAncestors: vi.fn(), + getById: vi.fn(), + getByIdentifier: vi.fn(async () => null), + getComment: vi.fn(), + getCommentCursor: vi.fn(), + getRelationSummaries: vi.fn(), + update: vi.fn(), + listWakeableBlockedDependents: vi.fn(), + getWakeableParentAfterChildCompletion: vi.fn(), + findMentionedAgents: vi.fn(async () => []), +})); + +vi.mock("../services/index.js", () => ({ + accessService: () => ({ + canUser: vi.fn(), + hasPermission: vi.fn(), + }), + agentService: () => ({ + getById: vi.fn(), + }), + documentService: () => ({ + getIssueDocumentPayload: vi.fn(async () => ({})), + }), + executionWorkspaceService: () => ({ + getById: vi.fn(), + }), + feedbackService: () => ({}), + goalService: () => ({ + getById: vi.fn(), + getDefaultCompanyGoal: vi.fn(), + }), + heartbeatService: () => ({ + wakeup: mockWakeup, + reportRunActivity: vi.fn(async () => undefined), + }), + instanceSettingsService: () => ({ + get: vi.fn(), + listCompanyIds: vi.fn(), + }), + issueApprovalService: () => ({}), + issueService: () => mockIssueService, + logActivity: vi.fn(async () => undefined), + projectService: () => ({ + getById: vi.fn(), + listByIds: vi.fn(async () => []), + }), + routineService: () => ({ + syncRunStatusForIssue: vi.fn(async () => undefined), + }), + workProductService: () => ({ + listForIssue: vi.fn(async () => []), + }), +})); + +function createApp() { + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { + (req as any).actor = { + type: "board", + userId: "local-board", + companyIds: ["company-1"], + source: "local_implicit", + isInstanceAdmin: false, + }; + next(); + }); + app.use("/api", issueRoutes({} as any, {} as any)); + app.use((err: any, _req: express.Request, res: express.Response, _next: express.NextFunction) => { + res.status(err?.status ?? 500).json({ error: err?.message ?? "Internal server error" }); + }); + return app; +} + +describe("issue dependency wakeups in issue routes", () => { + beforeEach(() => { + vi.clearAllMocks(); + mockIssueService.getAncestors.mockResolvedValue([]); + mockIssueService.getComment.mockResolvedValue(null); + mockIssueService.getCommentCursor.mockResolvedValue({ + totalComments: 0, + latestCommentId: null, + latestCommentAt: null, + }); + mockIssueService.getRelationSummaries.mockResolvedValue({ blockedBy: [], blocks: [] }); + mockIssueService.listWakeableBlockedDependents.mockResolvedValue([]); + mockIssueService.getWakeableParentAfterChildCompletion.mockResolvedValue(null); + }); + + it("wakes dependents when the final blocker transitions to done", async () => { + mockIssueService.getById.mockResolvedValue({ + id: "issue-1", + companyId: "company-1", + identifier: "PAP-100", + title: "Finish blocker", + description: null, + status: "blocked", + priority: "medium", + parentId: null, + assigneeAgentId: "agent-1", + assigneeUserId: null, + createdByAgentId: null, + createdByUserId: null, + executionWorkspaceId: null, + labels: [], + labelIds: [], + }); + mockIssueService.update.mockResolvedValue({ + id: "issue-1", + companyId: "company-1", + identifier: "PAP-100", + title: "Finish blocker", + description: null, + status: "done", + priority: "medium", + parentId: null, + assigneeAgentId: "agent-1", + assigneeUserId: null, + createdByAgentId: null, + createdByUserId: null, + executionWorkspaceId: null, + labels: [], + labelIds: [], + }); + mockIssueService.listWakeableBlockedDependents.mockResolvedValue([ + { + id: "issue-2", + assigneeAgentId: "agent-2", + blockerIssueIds: ["issue-1", "issue-3"], + }, + ]); + + const res = await request(createApp()).patch("/api/issues/issue-1").send({ status: "done" }); + await new Promise((resolve) => setTimeout(resolve, 0)); + + expect(res.status).toBe(200); + expect(mockWakeup).toHaveBeenCalledWith( + "agent-2", + expect.objectContaining({ + reason: "issue_blockers_resolved", + payload: expect.objectContaining({ + issueId: "issue-2", + resolvedBlockerIssueId: "issue-1", + }), + }), + ); + }); + + it("wakes the parent when all direct children become terminal", async () => { + mockIssueService.getById.mockResolvedValue({ + id: "child-1", + companyId: "company-1", + identifier: "PAP-101", + title: "Last child", + description: null, + status: "in_progress", + priority: "medium", + parentId: "parent-1", + assigneeAgentId: "agent-1", + assigneeUserId: null, + createdByAgentId: null, + createdByUserId: null, + executionWorkspaceId: null, + labels: [], + labelIds: [], + }); + mockIssueService.update.mockResolvedValue({ + id: "child-1", + companyId: "company-1", + identifier: "PAP-101", + title: "Last child", + description: null, + status: "done", + priority: "medium", + parentId: "parent-1", + assigneeAgentId: "agent-1", + assigneeUserId: null, + createdByAgentId: null, + createdByUserId: null, + executionWorkspaceId: null, + labels: [], + labelIds: [], + }); + mockIssueService.getWakeableParentAfterChildCompletion.mockResolvedValue({ + id: "parent-1", + assigneeAgentId: "agent-9", + childIssueIds: ["child-0", "child-1"], + }); + + const res = await request(createApp()).patch("/api/issues/child-1").send({ status: "done" }); + await new Promise((resolve) => setTimeout(resolve, 0)); + + expect(res.status).toBe(200); + expect(mockWakeup).toHaveBeenCalledWith( + "agent-9", + expect.objectContaining({ + reason: "issue_children_completed", + payload: expect.objectContaining({ + issueId: "parent-1", + completedChildIssueId: "child-1", + }), + }), + ); + }); +}); diff --git a/server/src/__tests__/issue-document-restore-routes.test.ts b/server/src/__tests__/issue-document-restore-routes.test.ts new file mode 100644 index 00000000000..d576d3ac4bd --- /dev/null +++ b/server/src/__tests__/issue-document-restore-routes.test.ts @@ -0,0 +1,182 @@ +import express from "express"; +import request from "supertest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const issueId = "11111111-1111-4111-8111-111111111111"; +const companyId = "22222222-2222-4222-8222-222222222222"; + +const mockIssueService = vi.hoisted(() => ({ + getById: vi.fn(), +})); + +const mockDocumentsService = vi.hoisted(() => ({ + listIssueDocumentRevisions: vi.fn(), + restoreIssueDocumentRevision: vi.fn(), +})); + +const mockAccessService = vi.hoisted(() => ({ + canUser: vi.fn(), + hasPermission: vi.fn(), +})); + +const mockAgentService = vi.hoisted(() => ({ + getById: vi.fn(), +})); + +const mockLogActivity = vi.hoisted(() => vi.fn(async () => undefined)); + +vi.mock("../services/index.js", () => ({ + accessService: () => mockAccessService, + agentService: () => mockAgentService, + documentService: () => mockDocumentsService, + executionWorkspaceService: () => ({}), + feedbackService: () => ({}), + goalService: () => ({}), + heartbeatService: () => ({ + wakeup: vi.fn(async () => undefined), + reportRunActivity: vi.fn(async () => undefined), + }), + instanceSettingsService: () => ({ + getExperimental: vi.fn(async () => ({})), + getGeneral: vi.fn(async () => ({ feedbackDataSharingPreference: "prompt" })), + }), + issueApprovalService: () => ({}), + issueService: () => mockIssueService, + logActivity: mockLogActivity, + projectService: () => ({}), + routineService: () => ({ + syncRunStatusForIssue: vi.fn(async () => undefined), + }), + workProductService: () => ({}), +})); + +async function createApp() { + const [{ issueRoutes }, { errorHandler }] = await Promise.all([ + import("../routes/issues.js"), + import("../middleware/index.js"), + ]); + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { + (req as any).actor = { + type: "board", + userId: "board-user", + companyIds: [companyId], + source: "local_implicit", + isInstanceAdmin: false, + }; + next(); + }); + app.use("/api", issueRoutes({} as any, {} as any)); + app.use(errorHandler); + return app; +} + +describe("issue document revision routes", () => { + beforeEach(() => { + vi.resetModules(); + vi.resetAllMocks(); + mockIssueService.getById.mockResolvedValue({ + id: issueId, + companyId, + identifier: "PAP-881", + title: "Document revisions", + status: "in_progress", + }); + mockDocumentsService.listIssueDocumentRevisions.mockResolvedValue([ + { + id: "revision-2", + companyId, + documentId: "document-1", + issueId, + key: "plan", + revisionNumber: 2, + title: "Plan v2", + format: "markdown", + body: "# Two", + changeSummary: null, + createdByAgentId: null, + createdByUserId: "board-user", + createdAt: new Date("2026-03-26T12:00:00.000Z"), + }, + ]); + mockDocumentsService.restoreIssueDocumentRevision.mockResolvedValue({ + restoredFromRevisionId: "revision-1", + restoredFromRevisionNumber: 1, + document: { + id: "document-1", + companyId, + issueId, + key: "plan", + title: "Plan v1", + format: "markdown", + body: "# One", + latestRevisionId: "revision-3", + latestRevisionNumber: 3, + createdByAgentId: null, + createdByUserId: "board-user", + updatedByAgentId: null, + updatedByUserId: "board-user", + createdAt: new Date("2026-03-26T12:00:00.000Z"), + updatedAt: new Date("2026-03-26T12:10:00.000Z"), + }, + }); + mockLogActivity.mockResolvedValue(undefined); + }); + + it("returns revision snapshots including title and format", async () => { + const res = await request(await createApp()).get(`/api/issues/${issueId}/documents/plan/revisions`); + + expect(res.status).toBe(200); + expect(mockDocumentsService.listIssueDocumentRevisions).toHaveBeenCalledWith(issueId, "plan"); + expect(res.body).toEqual([ + expect.objectContaining({ + revisionNumber: 2, + title: "Plan v2", + format: "markdown", + body: "# Two", + }), + ]); + }); + + it("restores a revision through the append-only route and logs the action", async () => { + const res = await request(await createApp()) + .post(`/api/issues/${issueId}/documents/plan/revisions/revision-1/restore`) + .send({}); + + expect(res.status).toBe(200); + expect(mockDocumentsService.restoreIssueDocumentRevision).toHaveBeenCalledWith({ + issueId, + key: "plan", + revisionId: "revision-1", + createdByAgentId: null, + createdByUserId: "board-user", + }); + expect(mockLogActivity).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + action: "issue.document_restored", + details: expect.objectContaining({ + key: "plan", + restoredFromRevisionId: "revision-1", + restoredFromRevisionNumber: 1, + revisionNumber: 3, + }), + }), + ); + expect(res.body).toEqual(expect.objectContaining({ + key: "plan", + title: "Plan v1", + latestRevisionNumber: 3, + })); + }); + + it("rejects invalid document keys before attempting restore", async () => { + const res = await request(await createApp()) + .post(`/api/issues/${issueId}/documents/INVALID KEY/revisions/revision-1/restore`) + .send({}); + + expect(res.status).toBe(400); + expect(mockDocumentsService.restoreIssueDocumentRevision).not.toHaveBeenCalled(); + }); +}); diff --git a/server/src/__tests__/issue-execution-policy-routes.test.ts b/server/src/__tests__/issue-execution-policy-routes.test.ts new file mode 100644 index 00000000000..190cb077af9 --- /dev/null +++ b/server/src/__tests__/issue-execution-policy-routes.test.ts @@ -0,0 +1,201 @@ +import express from "express"; +import request from "supertest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { errorHandler } from "../middleware/index.js"; +import { issueRoutes } from "../routes/issues.js"; +import { normalizeIssueExecutionPolicy } from "../services/issue-execution-policy.ts"; + +const mockIssueService = vi.hoisted(() => ({ + getById: vi.fn(), + assertCheckoutOwner: vi.fn(), + update: vi.fn(), + addComment: vi.fn(), + findMentionedAgents: vi.fn(), + getRelationSummaries: vi.fn(), + listWakeableBlockedDependents: vi.fn(), + getWakeableParentAfterChildCompletion: vi.fn(), +})); + +const mockHeartbeatService = vi.hoisted(() => ({ + wakeup: vi.fn(async () => undefined), + reportRunActivity: vi.fn(async () => undefined), + getRun: vi.fn(async () => null), + getActiveRunForAgent: vi.fn(async () => null), + cancelRun: vi.fn(async () => null), +})); + +vi.mock("../services/index.js", () => ({ + accessService: () => ({ + canUser: vi.fn(async () => false), + hasPermission: vi.fn(async () => false), + }), + agentService: () => ({ + getById: vi.fn(async () => null), + }), + documentService: () => ({}), + executionWorkspaceService: () => ({}), + feedbackService: () => ({ + listIssueVotesForUser: vi.fn(async () => []), + saveIssueVote: vi.fn(async () => ({ vote: null, consentEnabledNow: false, sharingEnabled: false })), + }), + goalService: () => ({}), + heartbeatService: () => mockHeartbeatService, + instanceSettingsService: () => ({ + get: vi.fn(async () => ({ + id: "instance-settings-1", + general: { + censorUsernameInLogs: false, + feedbackDataSharingPreference: "prompt", + }, + })), + listCompanyIds: vi.fn(async () => ["company-1"]), + }), + issueApprovalService: () => ({}), + issueService: () => mockIssueService, + logActivity: vi.fn(async () => undefined), + projectService: () => ({}), + routineService: () => ({ + syncRunStatusForIssue: vi.fn(async () => undefined), + }), + workProductService: () => ({}), +})); + +function createApp( + actor: Record = { + type: "board", + userId: "local-board", + companyIds: ["company-1"], + source: "local_implicit", + isInstanceAdmin: false, + }, +) { + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { + (req as any).actor = actor; + next(); + }); + app.use("/api", issueRoutes({} as any, {} as any)); + app.use(errorHandler); + return app; +} + +describe("issue execution policy routes", () => { + beforeEach(() => { + vi.clearAllMocks(); + mockIssueService.assertCheckoutOwner.mockResolvedValue({ adoptedFromRunId: null }); + mockIssueService.findMentionedAgents.mockResolvedValue([]); + mockIssueService.getRelationSummaries.mockResolvedValue({ blockedBy: [], blocks: [] }); + mockIssueService.listWakeableBlockedDependents.mockResolvedValue([]); + mockIssueService.getWakeableParentAfterChildCompletion.mockResolvedValue(null); + }); + + it("does not auto-start execution review when reviewers are added to an already in_review issue", async () => { + const policy = normalizeIssueExecutionPolicy({ + stages: [ + { + id: "11111111-1111-4111-8111-111111111111", + type: "review", + participants: [{ type: "agent", agentId: "33333333-3333-4333-8333-333333333333" }], + }, + ], + })!; + const issue = { + id: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + companyId: "company-1", + status: "in_review", + assigneeAgentId: null, + assigneeUserId: "local-board", + createdByUserId: "local-board", + identifier: "PAP-999", + title: "Execution policy edit", + executionPolicy: null, + executionState: null, + }; + mockIssueService.getById.mockResolvedValue(issue); + mockIssueService.update.mockImplementation(async (_id: string, patch: Record) => ({ + ...issue, + ...patch, + updatedAt: new Date(), + })); + + const res = await request(createApp()) + .patch("/api/issues/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa") + .send({ executionPolicy: policy }); + + expect(res.status).toBe(200); + expect(mockIssueService.update).toHaveBeenCalledWith( + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + expect.objectContaining({ + executionPolicy: policy, + actorAgentId: null, + actorUserId: "local-board", + }), + ); + const updatePatch = mockIssueService.update.mock.calls[0]?.[1] as Record; + expect(updatePatch.status).toBeUndefined(); + expect(updatePatch.assigneeAgentId).toBeUndefined(); + expect(updatePatch.assigneeUserId).toBeUndefined(); + expect(updatePatch.executionState).toBeUndefined(); + expect(mockHeartbeatService.wakeup).not.toHaveBeenCalled(); + }); + + it("rejects agent stage advances from non-participants", async () => { + const reviewerAgentId = "33333333-3333-4333-8333-333333333333"; + const approverAgentId = "44444444-4444-4444-8444-444444444444"; + const executorAgentId = "22222222-2222-4222-8222-222222222222"; + const policy = normalizeIssueExecutionPolicy({ + stages: [ + { + id: "11111111-1111-4111-8111-111111111111", + type: "review", + participants: [{ type: "agent", agentId: reviewerAgentId }], + }, + { + id: "55555555-5555-4555-8555-555555555555", + type: "approval", + participants: [{ type: "agent", agentId: approverAgentId }], + }, + ], + })!; + const issue = { + id: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + companyId: "company-1", + status: "in_review", + assigneeAgentId: reviewerAgentId, + assigneeUserId: null, + createdByUserId: "local-board", + identifier: "PAP-1000", + title: "Execution policy guard", + executionPolicy: policy, + executionState: { + status: "pending", + currentStageId: "11111111-1111-4111-8111-111111111111", + currentStageIndex: 0, + currentStageType: "review", + currentParticipant: { type: "agent", agentId: reviewerAgentId }, + returnAssignee: { type: "agent", agentId: executorAgentId }, + completedStageIds: [], + lastDecisionId: null, + lastDecisionOutcome: null, + }, + }; + mockIssueService.getById.mockResolvedValue(issue); + + const res = await request( + createApp({ + type: "agent", + agentId: approverAgentId, + companyId: "company-1", + source: "api_key", + runId: "run-1", + }), + ) + .patch("/api/issues/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa") + .send({ status: "done", comment: "Skipping review." }); + + expect(res.status).toBe(403); + expect(res.body.error).toContain("active review participant"); + expect(mockIssueService.update).not.toHaveBeenCalled(); + }); +}); diff --git a/server/src/__tests__/issue-execution-policy.test.ts b/server/src/__tests__/issue-execution-policy.test.ts new file mode 100644 index 00000000000..7271b49935c --- /dev/null +++ b/server/src/__tests__/issue-execution-policy.test.ts @@ -0,0 +1,1092 @@ +import { describe, expect, it } from "vitest"; +import { applyIssueExecutionPolicyTransition, normalizeIssueExecutionPolicy, parseIssueExecutionState } from "../services/issue-execution-policy.ts"; +import type { IssueExecutionPolicy, IssueExecutionState } from "@paperclipai/shared"; + +const coderAgentId = "11111111-1111-4111-8111-111111111111"; +const qaAgentId = "22222222-2222-4222-8222-222222222222"; +const ctoAgentId = "33333333-3333-4333-8333-333333333333"; +const ctoUserId = "cto-user"; +const boardUserId = "board-user"; + +function makePolicy( + stages: Array<{ type: "review" | "approval"; participants: Array<{ type: "agent" | "user"; agentId?: string; userId?: string }> }>, +) { + return normalizeIssueExecutionPolicy({ stages })!; +} + +function twoStagePolicy() { + return makePolicy([ + { type: "review", participants: [{ type: "agent", agentId: qaAgentId }] }, + { type: "approval", participants: [{ type: "user", userId: ctoUserId }] }, + ]); +} + +function reviewOnlyPolicy() { + return makePolicy([ + { type: "review", participants: [{ type: "agent", agentId: qaAgentId }] }, + ]); +} + +function approvalOnlyPolicy() { + return makePolicy([ + { type: "approval", participants: [{ type: "user", userId: ctoUserId }] }, + ]); +} + +describe("normalizeIssueExecutionPolicy", () => { + it("returns null for null/undefined input", () => { + expect(normalizeIssueExecutionPolicy(null)).toBeNull(); + expect(normalizeIssueExecutionPolicy(undefined)).toBeNull(); + }); + + it("returns null when stages are empty", () => { + expect(normalizeIssueExecutionPolicy({ stages: [] })).toBeNull(); + }); + + it("throws when all participants are invalid (missing agentId)", () => { + expect(() => + normalizeIssueExecutionPolicy({ + stages: [{ type: "review", participants: [{ type: "agent" }] }], + }), + ).toThrow("Invalid execution policy"); + }); + + it("deduplicates participants within a stage", () => { + const result = normalizeIssueExecutionPolicy({ + stages: [ + { + type: "review", + participants: [ + { type: "agent", agentId: qaAgentId }, + { type: "agent", agentId: qaAgentId }, + ], + }, + ], + }); + expect(result!.stages[0].participants).toHaveLength(1); + }); + + it("assigns UUIDs to stages and participants", () => { + const result = normalizeIssueExecutionPolicy({ + stages: [ + { type: "review", participants: [{ type: "agent", agentId: qaAgentId }] }, + ], + }); + expect(result!.stages[0].id).toBeDefined(); + expect(result!.stages[0].participants[0].id).toBeDefined(); + }); + + it("always sets commentRequired to true", () => { + const result = normalizeIssueExecutionPolicy({ + commentRequired: false, + stages: [ + { type: "review", participants: [{ type: "agent", agentId: qaAgentId }] }, + ], + }); + expect(result!.commentRequired).toBe(true); + }); + + it("defaults mode to normal", () => { + const result = normalizeIssueExecutionPolicy({ + stages: [ + { type: "review", participants: [{ type: "agent", agentId: qaAgentId }] }, + ], + }); + expect(result!.mode).toBe("normal"); + }); + + it("rejects approvalsNeeded values above 1", () => { + expect(() => + normalizeIssueExecutionPolicy({ + stages: [ + { + type: "review", + approvalsNeeded: 2, + participants: [{ type: "agent", agentId: qaAgentId }], + }, + ], + }), + ).toThrow("Invalid execution policy"); + }); + + it("throws for invalid input", () => { + expect(() => normalizeIssueExecutionPolicy({ stages: [{ type: "invalid_type" }] })).toThrow(); + }); +}); + +describe("parseIssueExecutionState", () => { + it("returns null for null/undefined", () => { + expect(parseIssueExecutionState(null)).toBeNull(); + expect(parseIssueExecutionState(undefined)).toBeNull(); + }); + + it("returns null for invalid shape", () => { + expect(parseIssueExecutionState({ status: "bogus" })).toBeNull(); + }); + + it("parses a valid state", () => { + const state = parseIssueExecutionState({ + status: "pending", + currentStageId: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + currentStageIndex: 0, + currentStageType: "review", + currentParticipant: { type: "agent", agentId: qaAgentId }, + returnAssignee: { type: "agent", agentId: coderAgentId }, + completedStageIds: [], + lastDecisionId: null, + lastDecisionOutcome: null, + }); + expect(state).not.toBeNull(); + expect(state!.status).toBe("pending"); + }); +}); + +describe("issue execution policy transitions", () => { + describe("happy path: executor → review → approval → done", () => { + const policy = twoStagePolicy(); + + it("routes executor completion into review", () => { + const result = applyIssueExecutionPolicyTransition({ + issue: { + status: "in_progress", + assigneeAgentId: coderAgentId, + assigneeUserId: null, + executionPolicy: policy, + executionState: null, + }, + policy, + requestedStatus: "done", + requestedAssigneePatch: {}, + actor: { agentId: coderAgentId }, + commentBody: "Implemented the feature", + }); + + expect(result.patch.status).toBe("in_review"); + expect(result.patch.assigneeAgentId).toBe(qaAgentId); + expect(result.patch.executionState).toMatchObject({ + status: "pending", + currentStageType: "review", + returnAssignee: { type: "agent", agentId: coderAgentId }, + }); + expect(result.decision).toBeUndefined(); + }); + + it("reviewer approves → advances to approval stage", () => { + const reviewStageId = policy.stages[0].id; + const result = applyIssueExecutionPolicyTransition({ + issue: { + status: "in_review", + assigneeAgentId: qaAgentId, + assigneeUserId: null, + executionPolicy: policy, + executionState: { + status: "pending", + currentStageId: reviewStageId, + currentStageIndex: 0, + currentStageType: "review", + currentParticipant: { type: "agent", agentId: qaAgentId }, + returnAssignee: { type: "agent", agentId: coderAgentId }, + completedStageIds: [], + lastDecisionId: null, + lastDecisionOutcome: null, + }, + }, + policy, + requestedStatus: "done", + requestedAssigneePatch: {}, + actor: { agentId: qaAgentId }, + commentBody: "QA signoff complete", + }); + + expect(result.patch.status).toBe("in_review"); + expect(result.patch.assigneeAgentId).toBeNull(); + expect(result.patch.assigneeUserId).toBe(ctoUserId); + expect(result.patch.executionState).toMatchObject({ + status: "pending", + currentStageType: "approval", + completedStageIds: [reviewStageId], + currentParticipant: { type: "user", userId: ctoUserId }, + }); + expect(result.decision).toMatchObject({ + stageId: reviewStageId, + stageType: "review", + outcome: "approved", + }); + }); + + it("approver approves → marks completed (allows done)", () => { + const reviewStageId = policy.stages[0].id; + const approvalStageId = policy.stages[1].id; + const result = applyIssueExecutionPolicyTransition({ + issue: { + status: "in_review", + assigneeAgentId: null, + assigneeUserId: ctoUserId, + executionPolicy: policy, + executionState: { + status: "pending", + currentStageId: approvalStageId, + currentStageIndex: 1, + currentStageType: "approval", + currentParticipant: { type: "user", userId: ctoUserId }, + returnAssignee: { type: "agent", agentId: coderAgentId }, + completedStageIds: [reviewStageId], + lastDecisionId: null, + lastDecisionOutcome: null, + }, + }, + policy, + requestedStatus: "done", + requestedAssigneePatch: {}, + actor: { userId: ctoUserId }, + commentBody: "Approved, ship it", + }); + + expect(result.patch.executionState).toMatchObject({ + status: "completed", + completedStageIds: expect.arrayContaining([reviewStageId, approvalStageId]), + lastDecisionOutcome: "approved", + }); + expect(result.decision).toMatchObject({ + stageId: approvalStageId, + stageType: "approval", + outcome: "approved", + }); + // status should NOT be overridden — caller can set done + expect(result.patch.status).toBeUndefined(); + }); + }); + + describe("changes requested flow", () => { + const policy = twoStagePolicy(); + const reviewStageId = policy.stages[0].id; + + it("reviewer requests changes → returns to executor", () => { + const result = applyIssueExecutionPolicyTransition({ + issue: { + status: "in_review", + assigneeAgentId: qaAgentId, + assigneeUserId: null, + executionPolicy: policy, + executionState: { + status: "pending", + currentStageId: reviewStageId, + currentStageIndex: 0, + currentStageType: "review", + currentParticipant: { type: "agent", agentId: qaAgentId }, + returnAssignee: { type: "agent", agentId: coderAgentId }, + completedStageIds: [], + lastDecisionId: null, + lastDecisionOutcome: null, + }, + }, + policy, + requestedStatus: "in_progress", + requestedAssigneePatch: {}, + actor: { agentId: qaAgentId }, + commentBody: "Needs another pass on edge cases", + }); + + expect(result.patch.status).toBe("in_progress"); + expect(result.patch.assigneeAgentId).toBe(coderAgentId); + expect(result.patch.executionState).toMatchObject({ + status: "changes_requested", + currentStageType: "review", + returnAssignee: { type: "agent", agentId: coderAgentId }, + lastDecisionOutcome: "changes_requested", + }); + expect(result.decision).toMatchObject({ + stageId: reviewStageId, + stageType: "review", + outcome: "changes_requested", + }); + }); + + it("executor re-submits after changes → returns to same review stage", () => { + const result = applyIssueExecutionPolicyTransition({ + issue: { + status: "in_progress", + assigneeAgentId: coderAgentId, + assigneeUserId: null, + executionPolicy: policy, + executionState: { + status: "changes_requested", + currentStageId: reviewStageId, + currentStageIndex: 0, + currentStageType: "review", + currentParticipant: { type: "agent", agentId: qaAgentId }, + returnAssignee: { type: "agent", agentId: coderAgentId }, + completedStageIds: [], + lastDecisionId: null, + lastDecisionOutcome: "changes_requested", + }, + }, + policy, + requestedStatus: "done", + requestedAssigneePatch: {}, + actor: { agentId: coderAgentId }, + commentBody: "Fixed edge cases", + }); + + expect(result.patch.status).toBe("in_review"); + expect(result.patch.assigneeAgentId).toBe(qaAgentId); + expect(result.patch.executionState).toMatchObject({ + status: "pending", + currentStageId: reviewStageId, + currentStageType: "review", + currentParticipant: { type: "agent", agentId: qaAgentId }, + }); + }); + }); + + describe("review-only policy (no approval stage)", () => { + const policy = reviewOnlyPolicy(); + const reviewStageId = policy.stages[0].id; + + it("reviewer approval completes the policy", () => { + const result = applyIssueExecutionPolicyTransition({ + issue: { + status: "in_review", + assigneeAgentId: qaAgentId, + assigneeUserId: null, + executionPolicy: policy, + executionState: { + status: "pending", + currentStageId: reviewStageId, + currentStageIndex: 0, + currentStageType: "review", + currentParticipant: { type: "agent", agentId: qaAgentId }, + returnAssignee: { type: "agent", agentId: coderAgentId }, + completedStageIds: [], + lastDecisionId: null, + lastDecisionOutcome: null, + }, + }, + policy, + requestedStatus: "done", + requestedAssigneePatch: {}, + actor: { agentId: qaAgentId }, + commentBody: "LGTM", + }); + + expect(result.patch.executionState).toMatchObject({ + status: "completed", + completedStageIds: [reviewStageId], + lastDecisionOutcome: "approved", + }); + expect(result.decision).toMatchObject({ + stageType: "review", + outcome: "approved", + }); + }); + }); + + describe("approval-only policy (no review stage)", () => { + const policy = approvalOnlyPolicy(); + + it("executor completion routes directly to approval", () => { + const result = applyIssueExecutionPolicyTransition({ + issue: { + status: "in_progress", + assigneeAgentId: coderAgentId, + assigneeUserId: null, + executionPolicy: policy, + executionState: null, + }, + policy, + requestedStatus: "done", + requestedAssigneePatch: {}, + actor: { agentId: coderAgentId }, + commentBody: "Done", + }); + + expect(result.patch.status).toBe("in_review"); + expect(result.patch.assigneeUserId).toBe(ctoUserId); + expect(result.patch.executionState).toMatchObject({ + status: "pending", + currentStageType: "approval", + }); + }); + }); + + describe("access control", () => { + const policy = twoStagePolicy(); + const reviewStageId = policy.stages[0].id; + + it("non-participant stage updates are coerced back to the active stage", () => { + const result = applyIssueExecutionPolicyTransition({ + issue: { + status: "in_review", + assigneeAgentId: qaAgentId, + assigneeUserId: null, + executionPolicy: policy, + executionState: { + status: "pending", + currentStageId: reviewStageId, + currentStageIndex: 0, + currentStageType: "review", + currentParticipant: { type: "agent", agentId: qaAgentId }, + returnAssignee: { type: "agent", agentId: coderAgentId }, + completedStageIds: [], + lastDecisionId: null, + lastDecisionOutcome: null, + }, + }, + policy, + requestedStatus: "done", + requestedAssigneePatch: { assigneeUserId: boardUserId }, + actor: { agentId: coderAgentId }, + commentBody: "Trying to bypass review", + }); + + expect(result.patch).toMatchObject({ + status: "in_review", + assigneeAgentId: qaAgentId, + assigneeUserId: null, + executionState: { + status: "pending", + currentStageId: reviewStageId, + currentStageType: "review", + currentParticipant: { type: "agent", agentId: qaAgentId }, + returnAssignee: { type: "agent", agentId: coderAgentId }, + }, + }); + expect(result.decision).toBeUndefined(); + }); + + it("non-participant can still post non-advancing updates", () => { + const result = applyIssueExecutionPolicyTransition({ + issue: { + status: "in_review", + assigneeAgentId: qaAgentId, + assigneeUserId: null, + executionPolicy: policy, + executionState: { + status: "pending", + currentStageId: reviewStageId, + currentStageIndex: 0, + currentStageType: "review", + currentParticipant: { type: "agent", agentId: qaAgentId }, + returnAssignee: { type: "agent", agentId: coderAgentId }, + completedStageIds: [], + lastDecisionId: null, + lastDecisionOutcome: null, + }, + }, + policy, + requestedStatus: undefined, + requestedAssigneePatch: {}, + actor: { agentId: coderAgentId }, + commentBody: "Just a note", + }); + + // No error — just no patch modifications + expect(result.patch).toEqual({}); + }); + }); + + describe("comment requirements", () => { + const policy = twoStagePolicy(); + const reviewStageId = policy.stages[0].id; + + it("approval without comment throws", () => { + expect(() => + applyIssueExecutionPolicyTransition({ + issue: { + status: "in_review", + assigneeAgentId: qaAgentId, + assigneeUserId: null, + executionPolicy: policy, + executionState: { + status: "pending", + currentStageId: reviewStageId, + currentStageIndex: 0, + currentStageType: "review", + currentParticipant: { type: "agent", agentId: qaAgentId }, + returnAssignee: { type: "agent", agentId: coderAgentId }, + completedStageIds: [], + lastDecisionId: null, + lastDecisionOutcome: null, + }, + }, + policy, + requestedStatus: "done", + requestedAssigneePatch: {}, + actor: { agentId: qaAgentId }, + commentBody: "", + }), + ).toThrow("requires a comment"); + }); + + it("changes requested without comment throws", () => { + expect(() => + applyIssueExecutionPolicyTransition({ + issue: { + status: "in_review", + assigneeAgentId: qaAgentId, + assigneeUserId: null, + executionPolicy: policy, + executionState: { + status: "pending", + currentStageId: reviewStageId, + currentStageIndex: 0, + currentStageType: "review", + currentParticipant: { type: "agent", agentId: qaAgentId }, + returnAssignee: { type: "agent", agentId: coderAgentId }, + completedStageIds: [], + lastDecisionId: null, + lastDecisionOutcome: null, + }, + }, + policy, + requestedStatus: "in_progress", + requestedAssigneePatch: {}, + actor: { agentId: qaAgentId }, + commentBody: null, + }), + ).toThrow("requires a comment"); + }); + + it("whitespace-only comment is treated as empty", () => { + expect(() => + applyIssueExecutionPolicyTransition({ + issue: { + status: "in_review", + assigneeAgentId: qaAgentId, + assigneeUserId: null, + executionPolicy: policy, + executionState: { + status: "pending", + currentStageId: reviewStageId, + currentStageIndex: 0, + currentStageType: "review", + currentParticipant: { type: "agent", agentId: qaAgentId }, + returnAssignee: { type: "agent", agentId: coderAgentId }, + completedStageIds: [], + lastDecisionId: null, + lastDecisionOutcome: null, + }, + }, + policy, + requestedStatus: "done", + requestedAssigneePatch: {}, + actor: { agentId: qaAgentId }, + commentBody: " ", + }), + ).toThrow("requires a comment"); + }); + }); + + describe("policy removal mid-flow", () => { + it("clears execution state when policy removed and returns to executor", () => { + // Use a real UUID for currentStageId so parseIssueExecutionState succeeds + const stageId = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"; + const result = applyIssueExecutionPolicyTransition({ + issue: { + status: "in_review", + assigneeAgentId: qaAgentId, + assigneeUserId: null, + executionPolicy: null, + executionState: { + status: "pending", + currentStageId: stageId, + currentStageIndex: 0, + currentStageType: "review", + currentParticipant: { type: "agent", agentId: qaAgentId }, + returnAssignee: { type: "agent", agentId: coderAgentId }, + completedStageIds: [], + lastDecisionId: null, + lastDecisionOutcome: null, + }, + }, + policy: null, + requestedStatus: undefined, + requestedAssigneePatch: {}, + actor: { agentId: qaAgentId }, + }); + + expect(result.patch.executionState).toBeNull(); + expect(result.patch.status).toBe("in_progress"); + expect(result.patch.assigneeAgentId).toBe(coderAgentId); + }); + + it("clears execution state without assignee change when not in_review", () => { + const stageId = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"; + const result = applyIssueExecutionPolicyTransition({ + issue: { + status: "in_progress", + assigneeAgentId: coderAgentId, + assigneeUserId: null, + executionPolicy: null, + executionState: { + status: "changes_requested", + currentStageId: stageId, + currentStageIndex: 0, + currentStageType: "review", + currentParticipant: { type: "agent", agentId: qaAgentId }, + returnAssignee: { type: "agent", agentId: coderAgentId }, + completedStageIds: [], + lastDecisionId: null, + lastDecisionOutcome: "changes_requested", + }, + }, + policy: null, + requestedStatus: undefined, + requestedAssigneePatch: {}, + actor: { agentId: coderAgentId }, + }); + + expect(result.patch.executionState).toBeNull(); + // Not in_review, so no status/assignee change + expect(result.patch.status).toBeUndefined(); + }); + }); + + describe("reopening from done/cancelled clears state", () => { + it("reopening a done issue clears execution state", () => { + const policy = twoStagePolicy(); + const result = applyIssueExecutionPolicyTransition({ + issue: { + status: "done", + assigneeAgentId: coderAgentId, + assigneeUserId: null, + executionPolicy: policy, + executionState: { + status: "completed", + currentStageId: null, + currentStageIndex: null, + currentStageType: null, + currentParticipant: null, + returnAssignee: { type: "agent", agentId: coderAgentId }, + completedStageIds: [policy.stages[0].id, policy.stages[1].id], + lastDecisionId: null, + lastDecisionOutcome: "approved", + }, + }, + policy, + requestedStatus: "todo", + requestedAssigneePatch: {}, + actor: { userId: boardUserId }, + }); + + expect(result.patch.executionState).toBeNull(); + }); + }); + + describe("no-op transitions", () => { + const policy = twoStagePolicy(); + const reviewStageId = policy.stages[0].id; + + it("non-done status change without review context is a no-op", () => { + const result = applyIssueExecutionPolicyTransition({ + issue: { + status: "in_progress", + assigneeAgentId: coderAgentId, + assigneeUserId: null, + executionPolicy: policy, + executionState: null, + }, + policy, + requestedStatus: "blocked", + requestedAssigneePatch: {}, + actor: { agentId: coderAgentId }, + }); + + expect(result.patch).toEqual({}); + }); + + it("coerces a malformed executor in_review patch into the first policy stage", () => { + const result = applyIssueExecutionPolicyTransition({ + issue: { + status: "in_progress", + assigneeAgentId: coderAgentId, + assigneeUserId: null, + executionPolicy: policy, + executionState: null, + }, + policy, + requestedStatus: "in_review", + requestedAssigneePatch: { assigneeUserId: boardUserId }, + actor: { agentId: coderAgentId }, + }); + + expect(result.patch).toMatchObject({ + status: "in_review", + assigneeAgentId: qaAgentId, + assigneeUserId: null, + executionState: { + status: "pending", + currentStageType: "review", + currentParticipant: { type: "agent", agentId: qaAgentId }, + returnAssignee: { type: "agent", agentId: coderAgentId }, + }, + }); + }); + + it("reasserts the active stage when issue status drifted out of in_review", () => { + const result = applyIssueExecutionPolicyTransition({ + issue: { + status: "in_progress", + assigneeAgentId: coderAgentId, + assigneeUserId: null, + executionPolicy: policy, + executionState: { + status: "pending", + currentStageId: reviewStageId, + currentStageIndex: 0, + currentStageType: "review", + currentParticipant: { type: "agent", agentId: qaAgentId }, + returnAssignee: { type: "agent", agentId: coderAgentId }, + completedStageIds: [], + lastDecisionId: null, + lastDecisionOutcome: null, + }, + }, + policy, + requestedStatus: "in_progress", + requestedAssigneePatch: { assigneeAgentId: coderAgentId }, + actor: { agentId: coderAgentId }, + }); + + expect(result.patch).toMatchObject({ + status: "in_review", + assigneeAgentId: qaAgentId, + assigneeUserId: null, + executionState: { + status: "pending", + currentStageId: reviewStageId, + currentStageType: "review", + currentParticipant: { type: "agent", agentId: qaAgentId }, + }, + }); + }); + + it("no policy and no state is a no-op", () => { + const result = applyIssueExecutionPolicyTransition({ + issue: { + status: "in_progress", + assigneeAgentId: coderAgentId, + assigneeUserId: null, + executionPolicy: null, + executionState: null, + }, + policy: null, + requestedStatus: "done", + requestedAssigneePatch: {}, + actor: { agentId: coderAgentId }, + }); + + expect(result.patch).toEqual({}); + }); + + it("does not auto-start workflow when policy is added to an already in_review issue", () => { + const reviewOnly = reviewOnlyPolicy(); + const result = applyIssueExecutionPolicyTransition({ + issue: { + status: "in_review", + assigneeAgentId: null, + assigneeUserId: boardUserId, + executionPolicy: null, + executionState: null, + }, + policy: reviewOnly, + requestedStatus: undefined, + requestedAssigneePatch: {}, + actor: { userId: boardUserId }, + }); + + expect(result.patch).toEqual({}); + }); + }); + + describe("multi-participant stages", () => { + it("selects the preferred participant when explicitly requested", () => { + const policy = makePolicy([ + { + type: "review", + participants: [ + { type: "agent", agentId: qaAgentId }, + { type: "agent", agentId: ctoAgentId }, + ], + }, + ]); + + const result = applyIssueExecutionPolicyTransition({ + issue: { + status: "in_progress", + assigneeAgentId: coderAgentId, + assigneeUserId: null, + executionPolicy: policy, + executionState: null, + }, + policy, + requestedStatus: "done", + requestedAssigneePatch: { assigneeAgentId: ctoAgentId }, + actor: { agentId: coderAgentId }, + commentBody: "Ready for review", + }); + + expect(result.patch.assigneeAgentId).toBe(ctoAgentId); + }); + + it("falls back to first participant when no preference given", () => { + const policy = makePolicy([ + { + type: "review", + participants: [ + { type: "agent", agentId: qaAgentId }, + { type: "agent", agentId: ctoAgentId }, + ], + }, + ]); + + const result = applyIssueExecutionPolicyTransition({ + issue: { + status: "in_progress", + assigneeAgentId: coderAgentId, + assigneeUserId: null, + executionPolicy: policy, + executionState: null, + }, + policy, + requestedStatus: "done", + requestedAssigneePatch: {}, + actor: { agentId: coderAgentId }, + commentBody: "Ready for review", + }); + + expect(result.patch.assigneeAgentId).toBe(qaAgentId); + }); + + it("excludes the return assignee from participant selection", () => { + const policy = makePolicy([ + { + type: "review", + participants: [ + { type: "agent", agentId: coderAgentId }, + { type: "agent", agentId: qaAgentId }, + ], + }, + ]); + + const result = applyIssueExecutionPolicyTransition({ + issue: { + status: "in_progress", + assigneeAgentId: coderAgentId, + assigneeUserId: null, + executionPolicy: policy, + executionState: null, + }, + policy, + requestedStatus: "done", + requestedAssigneePatch: {}, + actor: { agentId: coderAgentId }, + commentBody: "Done", + }); + + // coderAgentId is the returnAssignee, so QA should be selected + expect(result.patch.assigneeAgentId).toBe(qaAgentId); + }); + }); + + describe("changes requested with no return assignee", () => { + it("throws when requesting changes with no return assignee", () => { + const policy = twoStagePolicy(); + const reviewStageId = policy.stages[0].id; + expect(() => + applyIssueExecutionPolicyTransition({ + issue: { + status: "in_review", + assigneeAgentId: qaAgentId, + assigneeUserId: null, + executionPolicy: policy, + executionState: { + status: "pending", + currentStageId: reviewStageId, + currentStageIndex: 0, + currentStageType: "review", + currentParticipant: { type: "agent", agentId: qaAgentId }, + returnAssignee: null, + completedStageIds: [], + lastDecisionId: null, + lastDecisionOutcome: null, + }, + }, + policy, + requestedStatus: "in_progress", + requestedAssigneePatch: {}, + actor: { agentId: qaAgentId }, + commentBody: "Changes needed", + }), + ).toThrow("no return assignee"); + }); + }); + + describe("approval stage changes requested → bounces back to executor", () => { + it("approver requests changes during approval stage", () => { + const policy = twoStagePolicy(); + const reviewStageId = policy.stages[0].id; + const approvalStageId = policy.stages[1].id; + const result = applyIssueExecutionPolicyTransition({ + issue: { + status: "in_review", + assigneeAgentId: null, + assigneeUserId: ctoUserId, + executionPolicy: policy, + executionState: { + status: "pending", + currentStageId: approvalStageId, + currentStageIndex: 1, + currentStageType: "approval", + currentParticipant: { type: "user", userId: ctoUserId }, + returnAssignee: { type: "agent", agentId: coderAgentId }, + completedStageIds: [reviewStageId], + lastDecisionId: null, + lastDecisionOutcome: null, + }, + }, + policy, + requestedStatus: "in_progress", + requestedAssigneePatch: {}, + actor: { userId: ctoUserId }, + commentBody: "Not happy with the approach, needs rework", + }); + + expect(result.patch.status).toBe("in_progress"); + expect(result.patch.assigneeAgentId).toBe(coderAgentId); + expect(result.patch.executionState).toMatchObject({ + status: "changes_requested", + currentStageType: "approval", + lastDecisionOutcome: "changes_requested", + }); + expect(result.decision).toMatchObject({ + stageId: approvalStageId, + stageType: "approval", + outcome: "changes_requested", + }); + }); + }); + + describe("user participants", () => { + it("handles user-type reviewer participant correctly", () => { + const policy = makePolicy([ + { type: "review", participants: [{ type: "user", userId: boardUserId }] }, + ]); + + const result = applyIssueExecutionPolicyTransition({ + issue: { + status: "in_progress", + assigneeAgentId: coderAgentId, + assigneeUserId: null, + executionPolicy: policy, + executionState: null, + }, + policy, + requestedStatus: "done", + requestedAssigneePatch: {}, + actor: { agentId: coderAgentId }, + commentBody: "Done", + }); + + expect(result.patch.status).toBe("in_review"); + expect(result.patch.assigneeAgentId).toBeNull(); + expect(result.patch.assigneeUserId).toBe(boardUserId); + }); + }); + + describe("policy edits while a stage is active", () => { + it("clears the active execution state when its stage is removed from the policy", () => { + const reviewAndApproval = twoStagePolicy(); + const approvalOnly = approvalOnlyPolicy(); + + const result = applyIssueExecutionPolicyTransition({ + issue: { + status: "in_review", + assigneeAgentId: qaAgentId, + assigneeUserId: null, + executionPolicy: reviewAndApproval, + executionState: { + status: "pending", + currentStageId: reviewAndApproval.stages[0].id, + currentStageIndex: 0, + currentStageType: "review", + currentParticipant: { type: "agent", agentId: qaAgentId }, + returnAssignee: { type: "agent", agentId: coderAgentId }, + completedStageIds: [], + lastDecisionId: null, + lastDecisionOutcome: null, + }, + }, + policy: approvalOnly, + requestedStatus: undefined, + requestedAssigneePatch: {}, + actor: { userId: boardUserId }, + }); + + expect(result.patch).toMatchObject({ + status: "in_progress", + assigneeAgentId: coderAgentId, + assigneeUserId: null, + executionState: null, + }); + }); + + it("reassigns the active stage when the current participant is removed", () => { + const policy = makePolicy([ + { + type: "review", + participants: [ + { type: "agent", agentId: qaAgentId }, + { type: "agent", agentId: ctoAgentId }, + ], + }, + ]); + const updatedPolicy = makePolicy([ + { + type: "review", + participants: [{ type: "agent", agentId: ctoAgentId }], + }, + ]); + + const result = applyIssueExecutionPolicyTransition({ + issue: { + status: "in_review", + assigneeAgentId: qaAgentId, + assigneeUserId: null, + executionPolicy: policy, + executionState: { + status: "pending", + currentStageId: policy.stages[0].id, + currentStageIndex: 0, + currentStageType: "review", + currentParticipant: { type: "agent", agentId: qaAgentId }, + returnAssignee: { type: "agent", agentId: coderAgentId }, + completedStageIds: [], + lastDecisionId: null, + lastDecisionOutcome: null, + }, + }, + policy: { + ...updatedPolicy, + stages: [{ ...updatedPolicy.stages[0], id: policy.stages[0].id }], + }, + requestedStatus: undefined, + requestedAssigneePatch: {}, + actor: { userId: boardUserId }, + }); + + expect(result.patch).toMatchObject({ + status: "in_review", + assigneeAgentId: ctoAgentId, + assigneeUserId: null, + executionState: { + status: "pending", + currentStageId: policy.stages[0].id, + currentStageType: "review", + currentParticipant: { type: "agent", agentId: ctoAgentId }, + returnAssignee: { type: "agent", agentId: coderAgentId }, + }, + }); + }); + }); +}); diff --git a/server/src/__tests__/issue-feedback-routes.test.ts b/server/src/__tests__/issue-feedback-routes.test.ts new file mode 100644 index 00000000000..6a9578278d9 --- /dev/null +++ b/server/src/__tests__/issue-feedback-routes.test.ts @@ -0,0 +1,199 @@ +import express from "express"; +import request from "supertest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const mockFeedbackService = vi.hoisted(() => ({ + getFeedbackTraceById: vi.fn(), + getFeedbackTraceBundle: vi.fn(), + listIssueVotesForUser: vi.fn(), + listFeedbackTraces: vi.fn(), + saveIssueVote: vi.fn(), +})); + +const mockIssueService = vi.hoisted(() => ({ + getById: vi.fn(), + getByIdentifier: vi.fn(), + update: vi.fn(), + addComment: vi.fn(), + findMentionedAgents: vi.fn(), +})); + +const mockFeedbackExportService = vi.hoisted(() => ({ + flushPendingFeedbackTraces: vi.fn(async () => ({ attempted: 1, sent: 1, failed: 0 })), +})); + +function registerServiceMocks() { + vi.doMock("@paperclipai/shared/telemetry", () => ({ + trackAgentTaskCompleted: vi.fn(), + trackErrorHandlerCrash: vi.fn(), + })); + + vi.doMock("../telemetry.js", () => ({ + getTelemetryClient: vi.fn(() => ({ track: vi.fn() })), + })); + + vi.doMock("../services/index.js", () => ({ + accessService: () => ({ + canUser: vi.fn(), + hasPermission: vi.fn(), + }), + agentService: () => ({ + getById: vi.fn(), + }), + documentService: () => ({}), + executionWorkspaceService: () => ({}), + feedbackService: () => mockFeedbackService, + goalService: () => ({}), + heartbeatService: () => ({ + wakeup: vi.fn(async () => undefined), + reportRunActivity: vi.fn(async () => undefined), + getRun: vi.fn(async () => null), + getActiveRunForAgent: vi.fn(async () => null), + cancelRun: vi.fn(async () => null), + }), + instanceSettingsService: () => ({ + get: vi.fn(async () => ({ + id: "instance-settings-1", + general: { + censorUsernameInLogs: false, + feedbackDataSharingPreference: "prompt", + }, + })), + listCompanyIds: vi.fn(async () => ["company-1"]), + }), + issueApprovalService: () => ({}), + issueService: () => mockIssueService, + logActivity: vi.fn(async () => undefined), + projectService: () => ({}), + routineService: () => ({ + syncRunStatusForIssue: vi.fn(async () => undefined), + }), + workProductService: () => ({}), + })); +} + +async function createApp(actor: Record) { + const [{ issueRoutes }, { errorHandler }] = await Promise.all([ + import("../routes/issues.js"), + import("../middleware/index.js"), + ]); + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { + (req as any).actor = actor; + next(); + }); + app.use("/api", issueRoutes({} as any, {} as any, { feedbackExportService: mockFeedbackExportService })); + app.use(errorHandler); + return app; +} + +describe("issue feedback trace routes", () => { + beforeEach(() => { + vi.resetModules(); + registerServiceMocks(); + vi.resetAllMocks(); + mockFeedbackExportService.flushPendingFeedbackTraces.mockResolvedValue({ + attempted: 1, + sent: 1, + failed: 0, + }); + }); + + it("flushes a newly shared feedback trace immediately after saving the vote", async () => { + const targetId = "11111111-1111-4111-8111-111111111111"; + mockIssueService.getById.mockResolvedValue({ + id: "issue-1", + companyId: "company-1", + identifier: "PAP-1", + }); + mockFeedbackService.saveIssueVote.mockResolvedValue({ + vote: { + targetType: "issue_comment", + targetId, + vote: "up", + reason: null, + }, + traceId: "trace-1", + consentEnabledNow: false, + persistedSharingPreference: null, + sharingEnabled: true, + }); + const app = await createApp({ + type: "board", + userId: "user-1", + source: "session", + isInstanceAdmin: true, + companyIds: ["company-1"], + }); + + const res = await request(app) + .post("/api/issues/issue-1/feedback-votes") + .send({ + targetType: "issue_comment", + targetId, + vote: "up", + allowSharing: true, + }); + + expect([200, 201]).toContain(res.status); + expect(mockFeedbackExportService.flushPendingFeedbackTraces).toHaveBeenCalledWith({ + companyId: "company-1", + traceId: "trace-1", + limit: 1, + }); + }); + + it("rejects non-board callers before fetching a feedback trace", async () => { + const app = await createApp({ + type: "agent", + agentId: "agent-1", + companyId: "company-1", + source: "agent_key", + runId: "run-1", + }); + + const res = await request(app).get("/api/feedback-traces/trace-1"); + + expect(res.status).toBe(403); + expect(mockFeedbackService.getFeedbackTraceById).not.toHaveBeenCalled(); + }); + + it("returns 404 when a board user lacks access to the trace company", async () => { + mockFeedbackService.getFeedbackTraceById.mockResolvedValue({ + id: "trace-1", + companyId: "company-2", + }); + const app = await createApp({ + type: "board", + userId: "user-1", + source: "session", + isInstanceAdmin: false, + companyIds: ["company-1"], + }); + + const res = await request(app).get("/api/feedback-traces/trace-1"); + + expect(res.status).toBe(404); + }); + + it("returns 404 for bundle fetches when a board user lacks access to the trace company", async () => { + mockFeedbackService.getFeedbackTraceBundle.mockResolvedValue({ + id: "trace-1", + companyId: "company-2", + issueId: "issue-1", + files: [], + }); + const app = await createApp({ + type: "board", + userId: "user-1", + source: "session", + isInstanceAdmin: false, + companyIds: ["company-1"], + }); + + const res = await request(app).get("/api/feedback-traces/trace-1/bundle"); + + expect(res.status).toBe(404); + }); +}); diff --git a/server/src/__tests__/issue-telemetry-routes.test.ts b/server/src/__tests__/issue-telemetry-routes.test.ts new file mode 100644 index 00000000000..80d8641b6ff --- /dev/null +++ b/server/src/__tests__/issue-telemetry-routes.test.ts @@ -0,0 +1,141 @@ +import express from "express"; +import request from "supertest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const mockIssueService = vi.hoisted(() => ({ + getById: vi.fn(), + getWakeableParentAfterChildCompletion: vi.fn(), + listWakeableBlockedDependents: vi.fn(), + update: vi.fn(), +})); + +const mockAgentService = vi.hoisted(() => ({ + getById: vi.fn(), +})); + +const mockTrackAgentTaskCompleted = vi.hoisted(() => vi.fn()); +const mockGetTelemetryClient = vi.hoisted(() => vi.fn()); + +function registerRouteMocks() { + vi.doMock("@paperclipai/shared/telemetry", () => ({ + trackAgentTaskCompleted: mockTrackAgentTaskCompleted, + trackErrorHandlerCrash: vi.fn(), + })); + + vi.doMock("../telemetry.js", () => ({ + getTelemetryClient: mockGetTelemetryClient, + })); + + vi.doMock("../services/index.js", () => ({ + accessService: () => ({ + canUser: vi.fn(), + hasPermission: vi.fn(), + }), + agentService: () => mockAgentService, + documentService: () => ({}), + executionWorkspaceService: () => ({}), + feedbackService: () => ({}), + goalService: () => ({}), + heartbeatService: () => ({ + wakeup: vi.fn(async () => undefined), + reportRunActivity: vi.fn(async () => undefined), + }), + instanceSettingsService: () => ({}), + issueApprovalService: () => ({}), + issueService: () => mockIssueService, + logActivity: vi.fn(async () => undefined), + projectService: () => ({}), + routineService: () => ({ + syncRunStatusForIssue: vi.fn(async () => undefined), + }), + workProductService: () => ({}), + })); +} + +function makeIssue(status: "todo" | "done") { + return { + id: "11111111-1111-4111-8111-111111111111", + companyId: "company-1", + status, + assigneeAgentId: "22222222-2222-4222-8222-222222222222", + assigneeUserId: null, + createdByUserId: "local-board", + identifier: "PAP-1018", + title: "Telemetry test", + }; +} + +async function createApp(actor: Record) { + const [{ issueRoutes }, { errorHandler }] = await Promise.all([ + import("../routes/issues.js"), + import("../middleware/index.js"), + ]); + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { + (req as any).actor = actor; + next(); + }); + app.use("/api", issueRoutes({} as any, {} as any)); + app.use(errorHandler); + return app; +} + +describe("issue telemetry routes", () => { + beforeEach(() => { + vi.resetModules(); + registerRouteMocks(); + vi.resetAllMocks(); + mockGetTelemetryClient.mockReturnValue({ track: vi.fn() }); + mockIssueService.getById.mockResolvedValue(makeIssue("todo")); + mockIssueService.getWakeableParentAfterChildCompletion.mockResolvedValue(null); + mockIssueService.listWakeableBlockedDependents.mockResolvedValue([]); + mockIssueService.update.mockImplementation(async (_id: string, patch: Record) => ({ + ...makeIssue("todo"), + ...patch, + })); + }); + + it("emits task-completed telemetry with the agent role", async () => { + mockAgentService.getById.mockResolvedValue({ + id: "agent-1", + companyId: "company-1", + role: "engineer", + adapterType: "codex_local", + }); + + const app = await createApp({ + type: "agent", + agentId: "agent-1", + companyId: "company-1", + runId: null, + }); + const res = await request(app) + .patch("/api/issues/11111111-1111-4111-8111-111111111111") + .send({ status: "done" }); + + expect(res.status).toBe(200); + await vi.waitFor(() => { + expect(mockTrackAgentTaskCompleted).toHaveBeenCalledWith(expect.anything(), { + agentRole: "engineer", + }); + }); + }, 10_000); + + it("does not emit agent task-completed telemetry for board-driven completions", async () => { + const app = await createApp({ + type: "board", + userId: "local-board", + companyIds: ["company-1"], + source: "local_implicit", + isInstanceAdmin: false, + }); + const res = await request(app) + .patch("/api/issues/11111111-1111-4111-8111-111111111111") + .send({ status: "done" }); + + expect(res.status).toBe(200); + expect(mockTrackAgentTaskCompleted).not.toHaveBeenCalled(); + expect(mockAgentService.getById).not.toHaveBeenCalled(); + }); +}); diff --git a/server/src/__tests__/issues-goal-context-routes.test.ts b/server/src/__tests__/issues-goal-context-routes.test.ts index b4611d39ca3..cd381c58a70 100644 --- a/server/src/__tests__/issues-goal-context-routes.test.ts +++ b/server/src/__tests__/issues-goal-context-routes.test.ts @@ -7,9 +7,11 @@ import { errorHandler } from "../middleware/index.js"; const mockIssueService = vi.hoisted(() => ({ getById: vi.fn(), getAncestors: vi.fn(), + getRelationSummaries: vi.fn(), findMentionedProjectIds: vi.fn(), getCommentCursor: vi.fn(), getComment: vi.fn(), + listAttachments: vi.fn(), })); const mockProjectService = vi.hoisted(() => ({ @@ -36,11 +38,25 @@ vi.mock("../services/index.js", () => ({ executionWorkspaceService: () => ({ getById: vi.fn(), }), + feedbackService: () => ({ + listIssueVotesForUser: vi.fn(async () => []), + saveIssueVote: vi.fn(async () => ({ vote: null, consentEnabledNow: false, sharingEnabled: false })), + }), goalService: () => mockGoalService, heartbeatService: () => ({ wakeup: vi.fn(async () => undefined), reportRunActivity: vi.fn(async () => undefined), }), + instanceSettingsService: () => ({ + get: vi.fn(async () => ({ + id: "instance-settings-1", + general: { + censorUsernameInLogs: false, + feedbackDataSharingPreference: "prompt", + }, + })), + listCompanyIds: vi.fn(async () => ["company-1"]), + }), issueApprovalService: () => ({}), issueService: () => mockIssueService, logActivity: vi.fn(async () => undefined), @@ -108,6 +124,7 @@ describe("issue goal context routes", () => { vi.clearAllMocks(); mockIssueService.getById.mockResolvedValue(legacyProjectLinkedIssue); mockIssueService.getAncestors.mockResolvedValue([]); + mockIssueService.getRelationSummaries.mockResolvedValue({ blockedBy: [], blocks: [] }); mockIssueService.findMentionedProjectIds.mockResolvedValue([]); mockIssueService.getCommentCursor.mockResolvedValue({ totalComments: 0, @@ -115,6 +132,7 @@ describe("issue goal context routes", () => { latestCommentAt: null, }); mockIssueService.getComment.mockResolvedValue(null); + mockIssueService.listAttachments.mockResolvedValue([]); mockProjectService.getById.mockResolvedValue({ id: legacyProjectLinkedIssue.projectId, companyId: "company-1", @@ -183,5 +201,35 @@ describe("issue goal context routes", () => { }), ); expect(mockGoalService.getDefaultCompanyGoal).not.toHaveBeenCalled(); + expect(res.body.attachments).toEqual([]); + }); + + it("surfaces blocker summaries on GET /issues/:id/heartbeat-context", async () => { + mockIssueService.getRelationSummaries.mockResolvedValue({ + blockedBy: [ + { + id: "55555555-5555-4555-8555-555555555555", + identifier: "PAP-580", + title: "Finish wakeup plumbing", + status: "done", + priority: "medium", + assigneeAgentId: null, + assigneeUserId: null, + }, + ], + blocks: [], + }); + + const res = await request(createApp()).get( + "/api/issues/11111111-1111-4111-8111-111111111111/heartbeat-context", + ); + + expect(res.status).toBe(200); + expect(res.body.issue.blockedBy).toEqual([ + expect.objectContaining({ + id: "55555555-5555-4555-8555-555555555555", + identifier: "PAP-580", + }), + ]); }); }); diff --git a/server/src/__tests__/issues-service.test.ts b/server/src/__tests__/issues-service.test.ts index 9543a6140c9..89f75a706bb 100644 --- a/server/src/__tests__/issues-service.test.ts +++ b/server/src/__tests__/issues-service.test.ts @@ -1,5 +1,7 @@ import { randomUUID } from "node:crypto"; +import { eq } from "drizzle-orm"; import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest"; +import { sql } from "drizzle-orm"; import { activityLog, agents, @@ -9,6 +11,7 @@ import { instanceSettings, issueComments, issueInboxArchives, + issueRelations, issues, projectWorkspaces, projects, @@ -23,6 +26,22 @@ import { issueService } from "../services/issues.ts"; const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport(); const describeEmbeddedPostgres = embeddedPostgresSupport.supported ? describe : describe.skip; +async function ensureIssueRelationsTable(db: ReturnType) { + await db.execute(sql.raw(` + CREATE TABLE IF NOT EXISTS "issue_relations" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(), + "company_id" uuid NOT NULL, + "issue_id" uuid NOT NULL, + "related_issue_id" uuid NOT NULL, + "type" text NOT NULL, + "created_by_agent_id" uuid, + "created_by_user_id" text, + "created_at" timestamptz NOT NULL DEFAULT now(), + "updated_at" timestamptz NOT NULL DEFAULT now() + ); + `)); +} + if (!embeddedPostgresSupport.supported) { console.warn( `Skipping embedded Postgres issue service tests on this host: ${embeddedPostgresSupport.reason ?? "unsupported environment"}`, @@ -38,10 +57,12 @@ describeEmbeddedPostgres("issueService.list participantAgentId", () => { tempDb = await startEmbeddedPostgresTestDatabase("paperclip-issues-service-"); db = createDb(tempDb.connectionString); svc = issueService(db); + await ensureIssueRelationsTable(db); }, 20_000); afterEach(async () => { await db.delete(issueComments); + await db.delete(issueRelations); await db.delete(issueInboxArchives); await db.delete(activityLog); await db.delete(issues); @@ -357,18 +378,8 @@ describeEmbeddedPostgres("issueService.list participantAgentId", () => { }, ]); - await svc.archiveInbox( - companyId, - archivedIssueId, - userId, - new Date("2026-03-26T12:30:00.000Z"), - ); - await svc.archiveInbox( - companyId, - resurfacedIssueId, - userId, - new Date("2026-03-26T13:00:00.000Z"), - ); + await svc.archiveInbox(companyId, archivedIssueId, userId, new Date("2026-03-26T12:30:00.000Z")); + await svc.archiveInbox(companyId, resurfacedIssueId, userId, new Date("2026-03-26T13:00:00.000Z")); await db.insert(issueComments).values({ companyId, @@ -402,6 +413,644 @@ describeEmbeddedPostgres("issueService.list participantAgentId", () => { resurfacedIssueId, ])); }); + + it("resurfaces archived issue when status/updatedAt changes after archiving", async () => { + const companyId = randomUUID(); + const userId = "user-1"; + const otherUserId = "user-2"; + + await db.insert(companies).values({ + id: companyId, + name: "Paperclip", + issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`, + requireBoardApprovalForNewAgents: false, + }); + + const issueId = randomUUID(); + + await db.insert(issues).values({ + id: issueId, + companyId, + title: "Issue with old comment then status change", + status: "todo", + priority: "medium", + createdByUserId: userId, + createdAt: new Date("2026-03-26T10:00:00.000Z"), + updatedAt: new Date("2026-03-26T10:00:00.000Z"), + }); + + // Old external comment before archiving + await db.insert(issueComments).values({ + companyId, + issueId, + authorUserId: otherUserId, + body: "Old comment before archive", + createdAt: new Date("2026-03-26T11:00:00.000Z"), + updatedAt: new Date("2026-03-26T11:00:00.000Z"), + }); + + // Archive after seeing the comment + await svc.archiveInbox( + companyId, + issueId, + userId, + new Date("2026-03-26T12:00:00.000Z"), + ); + + // Verify it's archived + const afterArchive = await svc.list(companyId, { + touchedByUserId: userId, + inboxArchivedByUserId: userId, + }); + expect(afterArchive.map((i) => i.id)).not.toContain(issueId); + + // Status/work update changes updatedAt (no new comment) + await db + .update(issues) + .set({ + status: "in_progress", + updatedAt: new Date("2026-03-26T13:00:00.000Z"), + }) + .where(eq(issues.id, issueId)); + + // Should resurface because updatedAt > archivedAt + const afterUpdate = await svc.list(companyId, { + touchedByUserId: userId, + inboxArchivedByUserId: userId, + }); + expect(afterUpdate.map((i) => i.id)).toContain(issueId); + }); + + it("sorts and exposes last activity from comments and non-local issue activity logs", async () => { + const companyId = randomUUID(); + const olderIssueId = randomUUID(); + const commentIssueId = randomUUID(); + const activityIssueId = randomUUID(); + + await db.insert(companies).values({ + id: companyId, + name: "Paperclip", + issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`, + requireBoardApprovalForNewAgents: false, + }); + + await db.insert(issues).values([ + { + id: olderIssueId, + companyId, + title: "Older issue", + status: "todo", + priority: "medium", + updatedAt: new Date("2026-03-26T10:00:00.000Z"), + }, + { + id: commentIssueId, + companyId, + title: "Comment activity issue", + status: "todo", + priority: "medium", + updatedAt: new Date("2026-03-26T10:00:00.000Z"), + }, + { + id: activityIssueId, + companyId, + title: "Logged activity issue", + status: "todo", + priority: "medium", + updatedAt: new Date("2026-03-26T10:00:00.000Z"), + }, + ]); + + await db.insert(issueComments).values({ + companyId, + issueId: commentIssueId, + body: "New comment without touching issue.updatedAt", + createdAt: new Date("2026-03-26T11:00:00.000Z"), + updatedAt: new Date("2026-03-26T11:00:00.000Z"), + }); + + await db.insert(activityLog).values([ + { + companyId, + actorType: "system", + actorId: "system", + action: "issue.document_updated", + entityType: "issue", + entityId: activityIssueId, + createdAt: new Date("2026-03-26T12:00:00.000Z"), + }, + { + companyId, + actorType: "user", + actorId: "user-1", + action: "issue.read_marked", + entityType: "issue", + entityId: olderIssueId, + createdAt: new Date("2026-03-26T13:00:00.000Z"), + }, + ]); + + const result = await svc.list(companyId, {}); + + expect(result.map((issue) => issue.id)).toEqual([ + activityIssueId, + commentIssueId, + olderIssueId, + ]); + expect(result.find((issue) => issue.id === activityIssueId)?.lastActivityAt?.toISOString()).toBe( + "2026-03-26T12:00:00.000Z", + ); + expect(result.find((issue) => issue.id === commentIssueId)?.lastActivityAt?.toISOString()).toBe( + "2026-03-26T11:00:00.000Z", + ); + expect(result.find((issue) => issue.id === olderIssueId)?.lastActivityAt?.toISOString()).toBe( + "2026-03-26T10:00:00.000Z", + ); + }); +}); + +describeEmbeddedPostgres("issueService.create workspace inheritance", () => { + let db!: ReturnType; + let svc!: ReturnType; + let tempDb: Awaited> | null = null; + + beforeAll(async () => { + tempDb = await startEmbeddedPostgresTestDatabase("paperclip-issues-create-"); + db = createDb(tempDb.connectionString); + svc = issueService(db); + await ensureIssueRelationsTable(db); + }, 20_000); + + afterEach(async () => { + await db.delete(issueComments); + await db.delete(issueRelations); + await db.delete(issueInboxArchives); + await db.delete(activityLog); + await db.delete(issues); + await db.delete(executionWorkspaces); + await db.delete(projectWorkspaces); + await db.delete(projects); + await db.delete(agents); + await db.delete(instanceSettings); + await db.delete(companies); + }); + + afterAll(async () => { + await tempDb?.cleanup(); + }); + + it("inherits the parent issue workspace linkage when child workspace fields are omitted", async () => { + const companyId = randomUUID(); + const projectId = randomUUID(); + const parentIssueId = randomUUID(); + const projectWorkspaceId = randomUUID(); + const executionWorkspaceId = randomUUID(); + + await db.insert(companies).values({ + id: companyId, + name: "Paperclip", + issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`, + requireBoardApprovalForNewAgents: false, + }); + await instanceSettingsService(db).updateExperimental({ enableIsolatedWorkspaces: true }); + + await db.insert(projects).values({ + id: projectId, + companyId, + name: "Workspace project", + status: "in_progress", + }); + + await db.insert(projectWorkspaces).values({ + id: projectWorkspaceId, + companyId, + projectId, + name: "Primary workspace", + isPrimary: true, + sharedWorkspaceKey: "workspace-key", + }); + + await db.insert(executionWorkspaces).values({ + id: executionWorkspaceId, + companyId, + projectId, + projectWorkspaceId, + mode: "isolated_workspace", + strategyType: "git_worktree", + name: "Issue worktree", + status: "active", + providerType: "git_worktree", + providerRef: `/tmp/${executionWorkspaceId}`, + }); + + await db.insert(issues).values({ + id: parentIssueId, + companyId, + projectId, + projectWorkspaceId, + title: "Parent issue", + status: "in_progress", + priority: "medium", + executionWorkspaceId, + executionWorkspacePreference: "reuse_existing", + executionWorkspaceSettings: { + mode: "isolated_workspace", + workspaceRuntime: { profile: "agent" }, + }, + }); + + const child = await svc.create(companyId, { + parentId: parentIssueId, + projectId, + title: "Child issue", + }); + + expect(child.parentId).toBe(parentIssueId); + expect(child.projectWorkspaceId).toBe(projectWorkspaceId); + expect(child.executionWorkspaceId).toBe(executionWorkspaceId); + expect(child.executionWorkspacePreference).toBe("reuse_existing"); + expect(child.executionWorkspaceSettings).toEqual({ + mode: "isolated_workspace", + workspaceRuntime: { profile: "agent" }, + }); + }); + + it("keeps explicit workspace fields instead of inheriting the parent linkage", async () => { + const companyId = randomUUID(); + const projectId = randomUUID(); + const parentIssueId = randomUUID(); + const parentProjectWorkspaceId = randomUUID(); + const parentExecutionWorkspaceId = randomUUID(); + const explicitProjectWorkspaceId = randomUUID(); + const explicitExecutionWorkspaceId = randomUUID(); + + await db.insert(companies).values({ + id: companyId, + name: "Paperclip", + issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`, + requireBoardApprovalForNewAgents: false, + }); + await instanceSettingsService(db).updateExperimental({ enableIsolatedWorkspaces: true }); + + await db.insert(projects).values({ + id: projectId, + companyId, + name: "Workspace project", + status: "in_progress", + }); + + await db.insert(projectWorkspaces).values([ + { + id: parentProjectWorkspaceId, + companyId, + projectId, + name: "Parent workspace", + }, + { + id: explicitProjectWorkspaceId, + companyId, + projectId, + name: "Explicit workspace", + }, + ]); + + await db.insert(executionWorkspaces).values([ + { + id: parentExecutionWorkspaceId, + companyId, + projectId, + projectWorkspaceId: parentProjectWorkspaceId, + mode: "isolated_workspace", + strategyType: "git_worktree", + name: "Parent worktree", + status: "active", + providerType: "git_worktree", + }, + { + id: explicitExecutionWorkspaceId, + companyId, + projectId, + projectWorkspaceId: explicitProjectWorkspaceId, + mode: "shared_workspace", + strategyType: "project_primary", + name: "Explicit shared workspace", + status: "active", + providerType: "local_fs", + }, + ]); + + await db.insert(issues).values({ + id: parentIssueId, + companyId, + projectId, + projectWorkspaceId: parentProjectWorkspaceId, + title: "Parent issue", + status: "in_progress", + priority: "medium", + executionWorkspaceId: parentExecutionWorkspaceId, + executionWorkspacePreference: "reuse_existing", + executionWorkspaceSettings: { + mode: "isolated_workspace", + }, + }); + + const child = await svc.create(companyId, { + parentId: parentIssueId, + projectId, + title: "Child issue", + projectWorkspaceId: explicitProjectWorkspaceId, + executionWorkspaceId: explicitExecutionWorkspaceId, + executionWorkspacePreference: "reuse_existing", + executionWorkspaceSettings: { + mode: "shared_workspace", + }, + }); + + expect(child.projectWorkspaceId).toBe(explicitProjectWorkspaceId); + expect(child.executionWorkspaceId).toBe(explicitExecutionWorkspaceId); + expect(child.executionWorkspacePreference).toBe("reuse_existing"); + expect(child.executionWorkspaceSettings).toEqual({ + mode: "shared_workspace", + }); + }); + + it("inherits workspace linkage from an explicit source issue without creating a parent-child relationship", async () => { + const companyId = randomUUID(); + const projectId = randomUUID(); + const sourceIssueId = randomUUID(); + const projectWorkspaceId = randomUUID(); + const executionWorkspaceId = randomUUID(); + + await db.insert(companies).values({ + id: companyId, + name: "Paperclip", + issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`, + requireBoardApprovalForNewAgents: false, + }); + await instanceSettingsService(db).updateExperimental({ enableIsolatedWorkspaces: true }); + + await db.insert(projects).values({ + id: projectId, + companyId, + name: "Workspace project", + status: "in_progress", + }); + + await db.insert(projectWorkspaces).values({ + id: projectWorkspaceId, + companyId, + projectId, + name: "Primary workspace", + }); + + await db.insert(executionWorkspaces).values({ + id: executionWorkspaceId, + companyId, + projectId, + projectWorkspaceId, + mode: "operator_branch", + strategyType: "git_worktree", + name: "Operator branch", + status: "active", + providerType: "git_worktree", + }); + + await db.insert(issues).values({ + id: sourceIssueId, + companyId, + projectId, + projectWorkspaceId, + title: "Source issue", + status: "todo", + priority: "medium", + executionWorkspaceId, + executionWorkspacePreference: "reuse_existing", + executionWorkspaceSettings: { + mode: "operator_branch", + }, + }); + + const followUp = await svc.create(companyId, { + projectId, + title: "Follow-up issue", + inheritExecutionWorkspaceFromIssueId: sourceIssueId, + }); + + expect(followUp.parentId).toBeNull(); + expect(followUp.projectWorkspaceId).toBe(projectWorkspaceId); + expect(followUp.executionWorkspaceId).toBe(executionWorkspaceId); + expect(followUp.executionWorkspacePreference).toBe("reuse_existing"); + expect(followUp.executionWorkspaceSettings).toEqual({ + mode: "operator_branch", + }); + }); +}); + +describeEmbeddedPostgres("issueService blockers and dependency wake readiness", () => { + let db!: ReturnType; + let svc!: ReturnType; + let tempDb: Awaited> | null = null; + + beforeAll(async () => { + tempDb = await startEmbeddedPostgresTestDatabase("paperclip-issues-blockers-"); + db = createDb(tempDb.connectionString); + svc = issueService(db); + await ensureIssueRelationsTable(db); + }, 20_000); + + afterEach(async () => { + await db.delete(issueComments); + await db.delete(issueRelations); + await db.delete(issueInboxArchives); + await db.delete(activityLog); + await db.delete(issues); + await db.delete(executionWorkspaces); + await db.delete(projectWorkspaces); + await db.delete(projects); + await db.delete(agents); + await db.delete(instanceSettings); + await db.delete(companies); + }); + + afterAll(async () => { + await tempDb?.cleanup(); + }); + + it("persists blocked-by relations and exposes both blockedBy and blocks summaries", async () => { + const companyId = randomUUID(); + await db.insert(companies).values({ + id: companyId, + name: "Paperclip", + issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`, + requireBoardApprovalForNewAgents: false, + }); + + const blockerId = randomUUID(); + const blockedId = randomUUID(); + await db.insert(issues).values([ + { + id: blockerId, + companyId, + title: "Blocker", + status: "todo", + priority: "high", + }, + { + id: blockedId, + companyId, + title: "Blocked issue", + status: "blocked", + priority: "medium", + }, + ]); + + await svc.update(blockedId, { + blockedByIssueIds: [blockerId], + }); + + const blockerRelations = await svc.getRelationSummaries(blockerId); + const blockedRelations = await svc.getRelationSummaries(blockedId); + + expect(blockerRelations.blocks.map((relation) => relation.id)).toEqual([blockedId]); + expect(blockedRelations.blockedBy.map((relation) => relation.id)).toEqual([blockerId]); + }); + + it("rejects blocking cycles", async () => { + const companyId = randomUUID(); + await db.insert(companies).values({ + id: companyId, + name: "Paperclip", + issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`, + requireBoardApprovalForNewAgents: false, + }); + + const issueA = randomUUID(); + const issueB = randomUUID(); + await db.insert(issues).values([ + { id: issueA, companyId, title: "Issue A", status: "todo", priority: "medium" }, + { id: issueB, companyId, title: "Issue B", status: "todo", priority: "medium" }, + ]); + + await svc.update(issueA, { blockedByIssueIds: [issueB] }); + + await expect( + svc.update(issueB, { blockedByIssueIds: [issueA] }), + ).rejects.toMatchObject({ status: 422 }); + }); + + it("only returns dependents once every blocker is done", async () => { + const companyId = randomUUID(); + const assigneeAgentId = randomUUID(); + await db.insert(companies).values({ + id: companyId, + name: "Paperclip", + issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`, + requireBoardApprovalForNewAgents: false, + }); + await db.insert(agents).values({ + id: assigneeAgentId, + companyId, + name: "CodexCoder", + role: "engineer", + status: "active", + adapterType: "codex_local", + adapterConfig: {}, + runtimeConfig: {}, + permissions: {}, + }); + + const blockerA = randomUUID(); + const blockerB = randomUUID(); + const blockedIssueId = randomUUID(); + await db.insert(issues).values([ + { id: blockerA, companyId, title: "Blocker A", status: "done", priority: "medium" }, + { id: blockerB, companyId, title: "Blocker B", status: "todo", priority: "medium" }, + { + id: blockedIssueId, + companyId, + title: "Blocked issue", + status: "blocked", + priority: "medium", + assigneeAgentId, + }, + ]); + + await svc.update(blockedIssueId, { blockedByIssueIds: [blockerA, blockerB] }); + + expect(await svc.listWakeableBlockedDependents(blockerA)).toEqual([]); + + await svc.update(blockerB, { status: "done" }); + + await expect(svc.listWakeableBlockedDependents(blockerA)).resolves.toEqual([ + expect.objectContaining({ + id: blockedIssueId, + assigneeAgentId, + blockerIssueIds: expect.arrayContaining([blockerA, blockerB]), + }), + ]); + }); + + it("wakes parents only when all direct children are terminal", async () => { + const companyId = randomUUID(); + const assigneeAgentId = randomUUID(); + await db.insert(companies).values({ + id: companyId, + name: "Paperclip", + issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`, + requireBoardApprovalForNewAgents: false, + }); + await db.insert(agents).values({ + id: assigneeAgentId, + companyId, + name: "CodexCoder", + role: "engineer", + status: "active", + adapterType: "codex_local", + adapterConfig: {}, + runtimeConfig: {}, + permissions: {}, + }); + + const parentId = randomUUID(); + const childA = randomUUID(); + const childB = randomUUID(); + await db.insert(issues).values([ + { + id: parentId, + companyId, + title: "Parent issue", + status: "todo", + priority: "medium", + assigneeAgentId, + }, + { + id: childA, + companyId, + parentId, + title: "Child A", + status: "done", + priority: "medium", + }, + { + id: childB, + companyId, + parentId, + title: "Child B", + status: "blocked", + priority: "medium", + }, + ]); + + expect(await svc.getWakeableParentAfterChildCompletion(parentId)).toBeNull(); + + await svc.update(childB, { status: "cancelled" }); + + expect(await svc.getWakeableParentAfterChildCompletion(parentId)).toEqual({ + id: parentId, + assigneeAgentId, + childIssueIds: [childA, childB], + }); + }); }); describeEmbeddedPostgres("issueService.create workspace inheritance", () => { diff --git a/server/src/__tests__/logger-tz.test.ts b/server/src/__tests__/logger-tz.test.ts new file mode 100644 index 00000000000..3b7b46fa431 --- /dev/null +++ b/server/src/__tests__/logger-tz.test.ts @@ -0,0 +1,99 @@ +import { describe, expect, it, vi, beforeEach } from "vitest"; + +/** + * Regression test for https://github.com/paperclipai/paperclip/issues/2879 + * + * pino-pretty's `translateTime: "HH:MM:ss"` formats all timestamps in UTC + * regardless of the process's TZ env var. The `SYS:` prefix instructs + * pino-pretty to use the local system timezone, so operators in non-UTC + * zones see correct wall-clock times in their logs. + * + * We verify that: + * 1. The logger module initialises pino-pretty with "SYS:HH:MM:ss". + * 2. The pino-pretty SYS: prefix resolves to a timezone-sensitive format + * string — confirmed via pino-pretty's own asynchronous formatter, which + * applies translateTime to a known epoch under different TZ values. + */ + +const mockTransport = vi.hoisted(() => vi.fn(() => ({ write: vi.fn() }))); +const mockPino = vi.hoisted(() => { + const fn = vi.fn(() => ({ + info: vi.fn(), + debug: vi.fn(), + error: vi.fn(), + warn: vi.fn(), + child: vi.fn(), + })); + (fn as any).transport = mockTransport; + return fn; +}); + +// Mock fs so the module-level mkdirSync call is a no-op in tests. +vi.mock("node:fs", async (importOriginal) => { + const actual = await importOriginal(); + return { ...actual, mkdirSync: vi.fn() }; +}); + +vi.mock("pino", () => ({ + default: mockPino, +})); +vi.mock("pino-http", () => ({ + pinoHttp: vi.fn(() => vi.fn()), +})); +vi.mock("../config-file.js", () => ({ + readConfigFile: vi.fn(() => null), +})); +vi.mock("../home-paths.js", () => ({ + resolveHomeAwarePath: vi.fn((p: string) => p), + resolveDefaultLogsDir: vi.fn(() => "/tmp/paperclip-test-logs"), +})); + +describe("logger translateTime respects TZ environment variable", () => { + beforeEach(() => { + vi.resetModules(); + mockTransport.mockClear(); + mockPino.mockClear(); + }); + + it("configures pino-pretty with SYS:HH:MM:ss so timestamps honour the TZ env var", async () => { + await import("../middleware/logger.js"); + + expect(mockTransport).toHaveBeenCalledOnce(); + const { targets } = mockTransport.mock.calls[0][0] as { + targets: Array<{ options: Record }>; + }; + for (const target of targets) { + expect(target.options.translateTime).toBe("SYS:HH:MM:ss"); + } + }); + + it("SYS: prefix produces timezone-sensitive output: UTC epoch formats differently under UTC vs UTC+8", () => { + // Verifies the contract that SYS: relies on: formatting the same epoch + // with different explicit timezones (mirroring what the process TZ env + // var does at the OS level) must yield different results. + const EPOCH_MS = 946_684_800_000; // 2000-01-01 00:00:00 UTC + + const fmtUtc = new Intl.DateTimeFormat("en-GB", { + timeZone: "UTC", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + hour12: false, + }).format(EPOCH_MS); + + const fmtSgt = new Intl.DateTimeFormat("en-GB", { + timeZone: "Asia/Singapore", // UTC+8 + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + hour12: false, + }).format(EPOCH_MS); + + // UTC midnight = 00:00:00; the same instant in SGT = 08:00:00. + // SYS: picks up whichever of these the process TZ is set to — which is + // exactly what the fix enables by switching from HH:MM:ss (UTC-only). + expect(fmtUtc).toBe("00:00:00"); + expect(fmtSgt).toBe("08:00:00"); + expect(fmtUtc).not.toBe(fmtSgt); + }); +}); diff --git a/server/src/__tests__/openclaw-gateway-adapter.test.ts b/server/src/__tests__/openclaw-gateway-adapter.test.ts index 07bab9daed6..9bb85b7cdd1 100644 --- a/server/src/__tests__/openclaw-gateway-adapter.test.ts +++ b/server/src/__tests__/openclaw-gateway-adapter.test.ts @@ -439,6 +439,43 @@ describe("openclaw gateway adapter execute", () => { lifecycle: "ephemeral", }, ], + paperclipWake: { + reason: "issue_commented", + issue: { + id: "issue-123", + identifier: "PAP-874", + title: "chat-speed issues", + status: "in_progress", + priority: "medium", + }, + commentIds: ["comment-1", "comment-2"], + latestCommentId: "comment-2", + comments: [ + { + id: "comment-1", + issueId: "issue-123", + body: "First comment", + bodyTruncated: false, + createdAt: "2026-03-28T14:35:00.000Z", + author: { type: "user", id: "user-1" }, + }, + { + id: "comment-2", + issueId: "issue-123", + body: "Second comment", + bodyTruncated: false, + createdAt: "2026-03-28T14:35:10.000Z", + author: { type: "user", id: "user-1" }, + }, + ], + commentWindow: { + requestedCount: 2, + includedCount: 2, + missingCount: 0, + }, + truncated: false, + fallbackFetchNeeded: false, + }, }, }, ), @@ -456,6 +493,21 @@ describe("openclaw gateway adapter execute", () => { expect(String(payload?.message ?? "")).toContain("wake now"); expect(String(payload?.message ?? "")).toContain("PAPERCLIP_RUN_ID=run-123"); expect(String(payload?.message ?? "")).toContain("PAPERCLIP_TASK_ID=task-123"); + expect(String(payload?.message ?? "")).toContain("## Paperclip Wake Payload"); + expect(String(payload?.message ?? "")).toContain( + "Treat this wake payload as the highest-priority change for the current heartbeat.", + ); + expect(String(payload?.message ?? "")).toContain( + "Do not switch to another issue until you have handled this wake.", + ); + expect(String(payload?.message ?? "")).toContain("First comment"); + expect(String(payload?.message ?? "")).toContain("\"commentIds\":[\"comment-1\",\"comment-2\"]"); + expect(payload?.paperclip).toMatchObject({ + wake: { + latestCommentId: "comment-2", + commentIds: ["comment-1", "comment-2"], + }, + }); expect(logs.some((entry) => entry.includes("[openclaw-gateway:event] run=run-123 stream=assistant"))).toBe(true); } finally { diff --git a/server/src/__tests__/openclaw-invite-prompt-route.test.ts b/server/src/__tests__/openclaw-invite-prompt-route.test.ts index 990587d3e06..a2cb205bd1c 100644 --- a/server/src/__tests__/openclaw-invite-prompt-route.test.ts +++ b/server/src/__tests__/openclaw-invite-prompt-route.test.ts @@ -36,14 +36,16 @@ const mockBoardAuthService = vi.hoisted(() => ({ const mockLogActivity = vi.hoisted(() => vi.fn()); -vi.mock("../services/index.js", () => ({ - accessService: () => mockAccessService, - agentService: () => mockAgentService, - boardAuthService: () => mockBoardAuthService, - deduplicateAgentName: vi.fn(), - logActivity: mockLogActivity, - notifyHireApproved: vi.fn(), -})); +function registerServiceMocks() { + vi.doMock("../services/index.js", () => ({ + accessService: () => mockAccessService, + agentService: () => mockAgentService, + boardAuthService: () => mockBoardAuthService, + deduplicateAgentName: vi.fn(), + logActivity: mockLogActivity, + notifyHireApproved: vi.fn(), + })); +} function createDbStub() { const createdInvite = { @@ -84,7 +86,11 @@ function createDbStub() { }; } -function createApp(actor: Record, db: Record) { +async function createApp(actor: Record, db: Record) { + const [{ accessRoutes }, { errorHandler }] = await Promise.all([ + import("../routes/access.js"), + import("../middleware/index.js"), + ]); const app = express(); app.use(express.json()); app.use((req, _res, next) => { @@ -106,6 +112,9 @@ function createApp(actor: Record, db: Record) describe("POST /companies/:companyId/openclaw/invite-prompt", () => { beforeEach(() => { + vi.resetModules(); + registerServiceMocks(); + vi.clearAllMocks(); mockAccessService.canUser.mockResolvedValue(false); mockAgentService.getById.mockReset(); mockLogActivity.mockResolvedValue(undefined); @@ -118,7 +127,7 @@ describe("POST /companies/:companyId/openclaw/invite-prompt", () => { companyId: "company-1", role: "engineer", }); - const app = createApp( + const app = await createApp( { type: "agent", agentId: "agent-1", @@ -143,7 +152,7 @@ describe("POST /companies/:companyId/openclaw/invite-prompt", () => { companyId: "company-1", role: "ceo", }); - const app = createApp( + const app = await createApp( { type: "agent", agentId: "agent-1", @@ -162,6 +171,34 @@ describe("POST /companies/:companyId/openclaw/invite-prompt", () => { expect(typeof res.body.token).toBe("string"); expect(res.body.companyName).toBe("Acme AI"); expect(res.body.onboardingTextPath).toContain("/api/invites/"); + expect((db as any).__insertValues).toHaveBeenCalledWith( + expect.objectContaining({ + companyId: "company-1", + inviteType: "company_join", + allowedJoinTypes: "agent", + }), + ); + }); + + it("includes companyName in invite summary responses", async () => { + const db = createDbStub(); + const app = await createApp( + { + type: "board", + userId: "user-1", + companyIds: ["company-1"], + source: "session", + isInstanceAdmin: false, + }, + db, + ); + + const res = await request(app).get("/api/invites/pcp_invite_test"); + + expect(res.status).toBe(200); + expect(res.body.companyName).toBe("Acme AI"); + expect(res.body.inviteType).toBe("company_join"); + expect(res.body.allowedJoinTypes).toBe("agent"); }); it("includes companyName in invite summary responses", async () => { @@ -187,7 +224,7 @@ describe("POST /companies/:companyId/openclaw/invite-prompt", () => { it("allows board callers with invite permission", async () => { const db = createDbStub(); mockAccessService.canUser.mockResolvedValue(true); - const app = createApp( + const app = await createApp( { type: "board", userId: "user-1", @@ -203,13 +240,19 @@ describe("POST /companies/:companyId/openclaw/invite-prompt", () => { .send({}); expect(res.status).toBe(201); - expect(res.body.allowedJoinTypes).toBe("agent"); + expect((db as any).__insertValues).toHaveBeenCalledWith( + expect.objectContaining({ + companyId: "company-1", + inviteType: "company_join", + allowedJoinTypes: "agent", + }), + ); }); it("rejects board callers without invite permission", async () => { const db = createDbStub(); mockAccessService.canUser.mockResolvedValue(false); - const app = createApp( + const app = await createApp( { type: "board", userId: "user-1", diff --git a/server/src/__tests__/pi-local-execute.test.ts b/server/src/__tests__/pi-local-execute.test.ts new file mode 100644 index 00000000000..e0f49a27a07 --- /dev/null +++ b/server/src/__tests__/pi-local-execute.test.ts @@ -0,0 +1,76 @@ +import { describe, expect, it } from "vitest"; +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { execute } from "@paperclipai/adapter-pi-local/server"; + +async function writeFakePiCommand(commandPath: string): Promise { + const script = `#!/usr/bin/env node +if (process.argv.includes("--list-models")) { + console.log("provider model"); + console.log("google gemini-3-flash-preview"); + process.exit(0); +} +console.log(JSON.stringify({ type: "agent_start" })); +console.log(JSON.stringify({ type: "turn_start" })); +console.log(JSON.stringify({ type: "turn_end", message: { role: "assistant", content: "" }, toolResults: [] })); +console.log(JSON.stringify({ type: "agent_end", messages: [] })); +console.log(JSON.stringify({ + type: "auto_retry_end", + success: false, + attempt: 3, + finalError: "Cloud Code Assist API error (429): RESOURCE_EXHAUSTED" +})); +process.exit(0); +`; + await fs.writeFile(commandPath, script, "utf8"); + await fs.chmod(commandPath, 0o755); +} + +describe("pi_local execute", () => { + it("fails the run when Pi exhausts automatic retries despite exiting 0", async () => { + const root = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-pi-execute-")); + const workspace = path.join(root, "workspace"); + const commandPath = path.join(root, "pi"); + await fs.mkdir(workspace, { recursive: true }); + await writeFakePiCommand(commandPath); + + const previousHome = process.env.HOME; + process.env.HOME = root; + + try { + const result = await execute({ + runId: "run-pi-quota-exhausted", + agent: { + id: "agent-1", + companyId: "company-1", + name: "Pi Agent", + adapterType: "pi_local", + adapterConfig: {}, + }, + runtime: { + sessionId: null, + sessionParams: null, + sessionDisplayId: null, + taskKey: null, + }, + config: { + command: commandPath, + cwd: workspace, + model: "google/gemini-3-flash-preview", + promptTemplate: "Keep working.", + }, + context: {}, + authToken: "run-jwt-token", + onLog: async () => {}, + }); + + expect(result.exitCode).toBe(1); + expect(result.errorMessage).toContain("RESOURCE_EXHAUSTED"); + } finally { + if (previousHome === undefined) delete process.env.HOME; + else process.env.HOME = previousHome; + await fs.rm(root, { recursive: true, force: true }); + } + }); +}); diff --git a/server/src/__tests__/plugin-telemetry-bridge.test.ts b/server/src/__tests__/plugin-telemetry-bridge.test.ts new file mode 100644 index 00000000000..367663d9bb3 --- /dev/null +++ b/server/src/__tests__/plugin-telemetry-bridge.test.ts @@ -0,0 +1,114 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { createHostClientHandlers } from "../../../packages/plugins/sdk/src/host-client-factory.js"; +import { PLUGIN_RPC_ERROR_CODES } from "../../../packages/plugins/sdk/src/protocol.js"; +import { buildHostServices } from "../services/plugin-host-services.js"; + +const mockGetTelemetryClient = vi.hoisted(() => vi.fn()); + +vi.mock("../telemetry.js", () => ({ + getTelemetryClient: mockGetTelemetryClient, +})); + +function createEventBusStub() { + return { + forPlugin() { + return { + emit: vi.fn(), + subscribe: vi.fn(), + }; + }, + } as any; +} + +describe("plugin telemetry bridge", () => { + beforeEach(() => { + mockGetTelemetryClient.mockReset(); + }); + + it("prefixes plugin telemetry events before forwarding them to the telemetry client", async () => { + const track = vi.fn(); + mockGetTelemetryClient.mockReturnValue({ track }); + + const services = buildHostServices( + {} as never, + "plugin-record-id", + "linear", + createEventBusStub(), + ); + const handlers = createHostClientHandlers({ + pluginId: "linear", + capabilities: ["telemetry.track"], + services, + }); + + await handlers["telemetry.track"]({ + eventName: "sync_completed", + dimensions: { attempts: 2, success: true }, + }); + + expect(track).toHaveBeenCalledWith("plugin.linear.sync_completed", { + attempts: 2, + success: true, + }); + }); + + it("rejects invalid bare telemetry event names before prefixing", async () => { + mockGetTelemetryClient.mockReturnValue({ track: vi.fn() }); + + const services = buildHostServices( + {} as never, + "plugin-record-id", + "linear", + createEventBusStub(), + ); + + await expect( + services.telemetry.track({ eventName: "sync.completed" }), + ).rejects.toThrow( + 'Plugin telemetry event names must be lowercase slugs using letters, numbers, "_" or "-".', + ); + }); + + it("rejects telemetry tracking when the plugin lacks the capability", async () => { + const services = buildHostServices( + {} as never, + "plugin-record-id", + "linear", + createEventBusStub(), + ); + const handlers = createHostClientHandlers({ + pluginId: "linear", + capabilities: [], + services, + }); + + await expect( + handlers["telemetry.track"]({ eventName: "sync_completed" }), + ).rejects.toMatchObject({ + code: PLUGIN_RPC_ERROR_CODES.CAPABILITY_DENIED, + }); + + expect(mockGetTelemetryClient).not.toHaveBeenCalled(); + }); + + it("passes telemetry requests through when the plugin declares the capability", async () => { + const services = buildHostServices( + {} as never, + "plugin-record-id", + "linear", + createEventBusStub(), + ); + const handlers = createHostClientHandlers({ + pluginId: "linear", + capabilities: ["telemetry.track"], + services, + }); + + await handlers["telemetry.track"]({ + eventName: "sync_completed", + dimensions: { source: "manual" }, + }); + + expect(mockGetTelemetryClient).toHaveBeenCalledTimes(1); + }); +}); diff --git a/server/src/__tests__/private-hostname-guard.test.ts b/server/src/__tests__/private-hostname-guard.test.ts index 4879e279e89..13cdebbcae5 100644 --- a/server/src/__tests__/private-hostname-guard.test.ts +++ b/server/src/__tests__/private-hostname-guard.test.ts @@ -3,6 +3,8 @@ import express from "express"; import request from "supertest"; import { privateHostnameGuard } from "../middleware/private-hostname-guard.js"; +const unknownHostname = "blocked-host.invalid"; + function createApp(opts: { enabled: boolean; allowedHostnames?: string[]; bindHost?: string }) { const app = express(); app.use( @@ -42,15 +44,15 @@ describe("privateHostnameGuard", () => { it("blocks unknown hostnames with remediation command", async () => { const app = createApp({ enabled: true, allowedHostnames: ["some-other-host"] }); - const res = await request(app).get("/api/health").set("Host", "dotta-macbook-pro:3100"); + const res = await request(app).get("/api/health").set("Host", `${unknownHostname}:3100`); expect(res.status).toBe(403); - expect(res.body?.error).toContain("please run pnpm paperclipai allowed-hostname dotta-macbook-pro"); + expect(res.body?.error).toContain(`please run pnpm paperclipai allowed-hostname ${unknownHostname}`); }); it("blocks unknown hostnames on page routes with plain-text remediation command", async () => { const app = createApp({ enabled: true, allowedHostnames: ["some-other-host"] }); - const res = await request(app).get("/dashboard").set("Host", "dotta-macbook-pro:3100"); + const res = await request(app).get("/dashboard").set("Host", `${unknownHostname}:3100`); expect(res.status).toBe(403); - expect(res.text).toContain("please run pnpm paperclipai allowed-hostname dotta-macbook-pro"); + expect(res.text).toContain(`please run pnpm paperclipai allowed-hostname ${unknownHostname}`); }, 20_000); }); diff --git a/server/src/__tests__/project-goal-telemetry-routes.test.ts b/server/src/__tests__/project-goal-telemetry-routes.test.ts new file mode 100644 index 00000000000..4653ca65314 --- /dev/null +++ b/server/src/__tests__/project-goal-telemetry-routes.test.ts @@ -0,0 +1,120 @@ +import express from "express"; +import request from "supertest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { projectRoutes } from "../routes/projects.js"; +import { goalRoutes } from "../routes/goals.js"; +import { errorHandler } from "../middleware/index.js"; + +const mockProjectService = vi.hoisted(() => ({ + list: vi.fn(), + getById: vi.fn(), + create: vi.fn(), + createWorkspace: vi.fn(), + resolveByReference: vi.fn(), +})); + +const mockGoalService = vi.hoisted(() => ({ + list: vi.fn(), + getById: vi.fn(), + create: vi.fn(), + update: vi.fn(), + remove: vi.fn(), +})); + +const mockWorkspaceOperationService = vi.hoisted(() => ({})); +const mockSecretService = vi.hoisted(() => ({ + normalizeEnvBindingsForPersistence: vi.fn(), +})); +const mockLogActivity = vi.hoisted(() => vi.fn()); +const mockTrackProjectCreated = vi.hoisted(() => vi.fn()); +const mockTrackGoalCreated = vi.hoisted(() => vi.fn()); +const mockGetTelemetryClient = vi.hoisted(() => vi.fn()); + +vi.mock("@paperclipai/shared/telemetry", async () => { + const actual = await vi.importActual( + "@paperclipai/shared/telemetry", + ); + return { + ...actual, + trackProjectCreated: mockTrackProjectCreated, + trackGoalCreated: mockTrackGoalCreated, + }; +}); + +vi.mock("../telemetry.js", () => ({ + getTelemetryClient: mockGetTelemetryClient, +})); + +vi.mock("../services/index.js", () => ({ + goalService: () => mockGoalService, + logActivity: mockLogActivity, + projectService: () => mockProjectService, + secretService: () => mockSecretService, + workspaceOperationService: () => mockWorkspaceOperationService, +})); + +vi.mock("../services/workspace-runtime.js", () => ({ + startRuntimeServicesForWorkspaceControl: vi.fn(), + stopRuntimeServicesForProjectWorkspace: vi.fn(), +})); + +function createApp(route: ReturnType | ReturnType) { + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { + (req as any).actor = { + type: "board", + userId: "board-user", + companyIds: ["company-1"], + source: "local_implicit", + isInstanceAdmin: false, + }; + next(); + }); + app.use("/api", route); + app.use(errorHandler); + return app; +} + +describe("project and goal telemetry routes", () => { + beforeEach(() => { + vi.clearAllMocks(); + mockGetTelemetryClient.mockReturnValue({ track: vi.fn() }); + mockProjectService.resolveByReference.mockResolvedValue({ ambiguous: false, project: null }); + mockSecretService.normalizeEnvBindingsForPersistence.mockImplementation(async (_companyId, env) => env); + mockProjectService.create.mockResolvedValue({ + id: "project-1", + companyId: "company-1", + name: "Telemetry project", + description: null, + status: "backlog", + }); + mockGoalService.create.mockResolvedValue({ + id: "goal-1", + companyId: "company-1", + title: "Telemetry goal", + description: null, + level: "team", + status: "planned", + }); + mockLogActivity.mockResolvedValue(undefined); + }); + + it("emits telemetry when a project is created", async () => { + const res = await request(createApp(projectRoutes({} as any))) + .post("/api/companies/company-1/projects") + .send({ name: "Telemetry project" }); + + expect([200, 201]).toContain(res.status); + expect(mockTrackProjectCreated).toHaveBeenCalledWith(expect.anything()); + }); + + it("emits telemetry when a goal is created", async () => { + const res = await request(createApp(goalRoutes({} as any))) + .post("/api/companies/company-1/goals") + .send({ title: "Telemetry goal", level: "team" }); + + expect(res.status, JSON.stringify(res.body)).toBe(201); + expect(mockTrackGoalCreated).toHaveBeenCalledWith(expect.anything(), { goalLevel: "team" }); + }); +}); diff --git a/server/src/__tests__/project-routes-env.test.ts b/server/src/__tests__/project-routes-env.test.ts new file mode 100644 index 00000000000..895cc02e5d4 --- /dev/null +++ b/server/src/__tests__/project-routes-env.test.ts @@ -0,0 +1,177 @@ +import express from "express"; +import request from "supertest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const mockProjectService = vi.hoisted(() => ({ + list: vi.fn(), + getById: vi.fn(), + create: vi.fn(), + update: vi.fn(), + createWorkspace: vi.fn(), + listWorkspaces: vi.fn(), + updateWorkspace: vi.fn(), + removeWorkspace: vi.fn(), + remove: vi.fn(), + resolveByReference: vi.fn(), +})); +const mockSecretService = vi.hoisted(() => ({ + normalizeEnvBindingsForPersistence: vi.fn(), +})); +const mockWorkspaceOperationService = vi.hoisted(() => ({})); +const mockLogActivity = vi.hoisted(() => vi.fn()); +const mockGetTelemetryClient = vi.hoisted(() => vi.fn()); + +vi.mock("../telemetry.js", () => ({ + getTelemetryClient: mockGetTelemetryClient, +})); + +vi.mock("../services/index.js", () => ({ + logActivity: mockLogActivity, + projectService: () => mockProjectService, + secretService: () => mockSecretService, + workspaceOperationService: () => mockWorkspaceOperationService, +})); + +vi.mock("../services/workspace-runtime.js", () => ({ + startRuntimeServicesForWorkspaceControl: vi.fn(), + stopRuntimeServicesForProjectWorkspace: vi.fn(), +})); + +async function createApp() { + const { projectRoutes } = await import("../routes/projects.js"); + const { errorHandler } = await import("../middleware/index.js"); + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { + (req as any).actor = { + type: "board", + userId: "board-user", + companyIds: ["company-1"], + source: "local_implicit", + isInstanceAdmin: false, + }; + next(); + }); + app.use("/api", projectRoutes({} as any)); + app.use(errorHandler); + return app; +} + +function buildProject(overrides: Record = {}) { + return { + id: "project-1", + companyId: "company-1", + urlKey: "project-1", + goalId: null, + goalIds: [], + goals: [], + name: "Project", + description: null, + status: "backlog", + leadAgentId: null, + targetDate: null, + color: null, + env: null, + pauseReason: null, + pausedAt: null, + executionWorkspacePolicy: null, + codebase: { + workspaceId: null, + repoUrl: null, + repoRef: null, + defaultRef: null, + repoName: null, + localFolder: null, + managedFolder: "/tmp/project", + effectiveLocalFolder: "/tmp/project", + origin: "managed_checkout", + }, + workspaces: [], + primaryWorkspace: null, + archivedAt: null, + createdAt: new Date(), + updatedAt: new Date(), + ...overrides, + }; +} + +describe("project env routes", () => { + beforeEach(() => { + vi.clearAllMocks(); + mockGetTelemetryClient.mockReturnValue({ track: vi.fn() }); + mockProjectService.resolveByReference.mockResolvedValue({ ambiguous: false, project: null }); + mockProjectService.createWorkspace.mockResolvedValue(null); + mockProjectService.listWorkspaces.mockResolvedValue([]); + mockSecretService.normalizeEnvBindingsForPersistence.mockImplementation(async (_companyId, env) => env); + }); + + it("normalizes env bindings on create and logs only env keys", async () => { + const normalizedEnv = { + API_KEY: { + type: "secret_ref", + secretId: "11111111-1111-4111-8111-111111111111", + version: "latest", + }, + }; + mockSecretService.normalizeEnvBindingsForPersistence.mockResolvedValue(normalizedEnv); + mockProjectService.create.mockResolvedValue(buildProject({ env: normalizedEnv })); + + const app = await createApp(); + const res = await request(app) + .post("/api/companies/company-1/projects") + .send({ + name: "Project", + env: normalizedEnv, + }); + + expect(res.status, JSON.stringify(res.body)).toBe(201); + expect(mockSecretService.normalizeEnvBindingsForPersistence).toHaveBeenCalledWith( + "company-1", + normalizedEnv, + expect.objectContaining({ fieldPath: "env" }), + ); + expect(mockProjectService.create).toHaveBeenCalledWith( + "company-1", + expect.objectContaining({ env: normalizedEnv }), + ); + expect(mockLogActivity).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + details: expect.objectContaining({ + envKeys: ["API_KEY"], + }), + }), + ); + }); + + it("normalizes env bindings on update and avoids logging raw values", async () => { + const normalizedEnv = { + PLAIN_KEY: { type: "plain", value: "top-secret" }, + }; + mockSecretService.normalizeEnvBindingsForPersistence.mockResolvedValue(normalizedEnv); + mockProjectService.getById.mockResolvedValue(buildProject()); + mockProjectService.update.mockResolvedValue(buildProject({ env: normalizedEnv })); + + const app = await createApp(); + const res = await request(app) + .patch("/api/projects/project-1") + .send({ + env: normalizedEnv, + }); + + expect(res.status, JSON.stringify(res.body)).toBe(200); + expect(mockProjectService.update).toHaveBeenCalledWith( + "project-1", + expect.objectContaining({ env: normalizedEnv }), + ); + expect(mockLogActivity).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + details: { + changedKeys: ["env"], + envKeys: ["PLAIN_KEY"], + }, + }), + ); + }); +}); diff --git a/server/src/__tests__/routine-run-telemetry.test.ts b/server/src/__tests__/routine-run-telemetry.test.ts new file mode 100644 index 00000000000..ded455971a7 --- /dev/null +++ b/server/src/__tests__/routine-run-telemetry.test.ts @@ -0,0 +1,163 @@ +import { randomUUID } from "node:crypto"; +import { eq } from "drizzle-orm"; +import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; +import { + agents, + companies, + createDb, + executionWorkspaces, + heartbeatRuns, + issues, + projectWorkspaces, + projects, + routineRuns, + routines, + routineTriggers, +} from "@paperclipai/db"; +import { + getEmbeddedPostgresTestSupport, + startEmbeddedPostgresTestDatabase, +} from "./helpers/embedded-postgres.js"; + +const mockTelemetryClient = vi.hoisted(() => ({ track: vi.fn() })); +const mockTrackRoutineRun = vi.hoisted(() => vi.fn()); + +vi.mock("../telemetry.js", () => ({ + getTelemetryClient: () => mockTelemetryClient, +})); + +vi.mock("@paperclipai/shared/telemetry", async () => { + const actual = await vi.importActual( + "@paperclipai/shared/telemetry", + ); + return { + ...actual, + trackRoutineRun: mockTrackRoutineRun, + }; +}); + +import { routineService } from "../services/routines.ts"; + +const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport(); +const describeEmbeddedPostgres = embeddedPostgresSupport.supported ? describe : describe.skip; + +describeEmbeddedPostgres("routine run telemetry", () => { + let db!: ReturnType; + let tempDb: Awaited> | null = null; + + beforeAll(async () => { + tempDb = await startEmbeddedPostgresTestDatabase("paperclip-routine-telemetry-"); + db = createDb(tempDb.connectionString); + }, 20_000); + + afterEach(async () => { + vi.clearAllMocks(); + await db.delete(routineRuns); + await db.delete(routineTriggers); + await db.delete(routines); + await db.delete(heartbeatRuns); + await db.delete(issues); + await db.delete(executionWorkspaces); + await db.delete(projectWorkspaces); + await db.delete(projects); + await db.delete(agents); + await db.delete(companies); + }); + + afterAll(async () => { + await tempDb?.cleanup(); + }); + + async function seedFixture() { + const companyId = randomUUID(); + const agentId = randomUUID(); + const projectId = randomUUID(); + + await db.insert(companies).values({ + id: companyId, + name: "Paperclip", + issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`, + requireBoardApprovalForNewAgents: false, + }); + + await db.insert(agents).values({ + id: agentId, + companyId, + name: "CodexCoder", + role: "engineer", + status: "active", + adapterType: "codex_local", + adapterConfig: {}, + runtimeConfig: {}, + permissions: {}, + }); + + await db.insert(projects).values({ + id: projectId, + companyId, + name: "Routines", + status: "in_progress", + }); + + const svc = routineService(db, { + heartbeat: { + wakeup: async (wakeupAgentId, wakeupOpts) => { + const issueId = + (typeof wakeupOpts.payload?.issueId === "string" && wakeupOpts.payload.issueId) + || (typeof wakeupOpts.contextSnapshot?.issueId === "string" && wakeupOpts.contextSnapshot.issueId) + || null; + if (!issueId) return null; + const queuedRunId = randomUUID(); + await db.insert(heartbeatRuns).values({ + id: queuedRunId, + companyId, + agentId: wakeupAgentId, + invocationSource: wakeupOpts.source ?? "assignment", + triggerDetail: wakeupOpts.triggerDetail ?? null, + status: "queued", + contextSnapshot: { ...(wakeupOpts.contextSnapshot ?? {}), issueId }, + }); + await db + .update(issues) + .set({ + executionRunId: queuedRunId, + executionLockedAt: new Date(), + }) + .where(eq(issues.id, issueId)); + return { id: queuedRunId }; + }, + }, + }); + + const routine = await svc.create( + companyId, + { + projectId, + goalId: null, + parentIssueId: null, + title: "Run telemetry test", + description: "Routine body", + assigneeAgentId: agentId, + priority: "medium", + status: "active", + concurrencyPolicy: "coalesce_if_active", + catchUpPolicy: "skip_missed", + }, + {}, + ); + + return { routine, svc }; + } + + it("emits telemetry for routine runs from the service layer", async () => { + const { routine, svc } = await seedFixture(); + + const run = await svc.runRoutine(routine.id, { source: "manual" }); + + expect(run.status).toBe("issue_created"); + expect(mockTrackRoutineRun).toHaveBeenCalledWith(mockTelemetryClient, { + source: "manual", + status: "issue_created", + }); + }); +}); diff --git a/server/src/__tests__/routines-e2e.test.ts b/server/src/__tests__/routines-e2e.test.ts index ab5fd778eac..12a3d837202 100644 --- a/server/src/__tests__/routines-e2e.test.ts +++ b/server/src/__tests__/routines-e2e.test.ts @@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto"; import { eq } from "drizzle-orm"; import express from "express"; import request from "supertest"; -import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { activityLog, agentWakeupRequests, @@ -10,11 +10,13 @@ import { companies, companyMemberships, createDb, + executionWorkspaces, heartbeatRunEvents, heartbeatRuns, instanceSettings, issues, principalPermissionGrants, + projectWorkspaces, projects, routineRuns, routines, @@ -27,54 +29,53 @@ import { import { errorHandler } from "../middleware/index.js"; import { accessService } from "../services/access.js"; -vi.mock("../services/index.js", async () => { - const actual = await vi.importActual("../services/index.js"); - const { randomUUID } = await import("node:crypto"); - const { eq } = await import("drizzle-orm"); - const { heartbeatRuns, issues } = await import("@paperclipai/db"); +function registerServiceMocks() { + vi.doMock("../services/index.js", async () => { + const actual = await vi.importActual("../services/index.js"); - return { - ...actual, - routineService: (db: any) => - actual.routineService(db, { - heartbeat: { - wakeup: async (agentId: string, wakeupOpts: any) => { - const issueId = - (typeof wakeupOpts?.payload?.issueId === "string" && wakeupOpts.payload.issueId) || - (typeof wakeupOpts?.contextSnapshot?.issueId === "string" && wakeupOpts.contextSnapshot.issueId) || - null; - if (!issueId) return null; + return { + ...actual, + routineService: (db: any) => + actual.routineService(db, { + heartbeat: { + wakeup: async (agentId: string, wakeupOpts: any) => { + const issueId = + (typeof wakeupOpts?.payload?.issueId === "string" && wakeupOpts.payload.issueId) || + (typeof wakeupOpts?.contextSnapshot?.issueId === "string" && wakeupOpts.contextSnapshot.issueId) || + null; + if (!issueId) return null; - const issue = await db - .select({ companyId: issues.companyId }) - .from(issues) - .where(eq(issues.id, issueId)) - .then((rows: Array<{ companyId: string }>) => rows[0] ?? null); - if (!issue) return null; + const issue = await db + .select({ companyId: issues.companyId }) + .from(issues) + .where(eq(issues.id, issueId)) + .then((rows: Array<{ companyId: string }>) => rows[0] ?? null); + if (!issue) return null; - const queuedRunId = randomUUID(); - await db.insert(heartbeatRuns).values({ - id: queuedRunId, - companyId: issue.companyId, - agentId, - invocationSource: wakeupOpts?.source ?? "assignment", - triggerDetail: wakeupOpts?.triggerDetail ?? null, - status: "queued", - contextSnapshot: { ...(wakeupOpts?.contextSnapshot ?? {}), issueId }, - }); - await db - .update(issues) - .set({ - executionRunId: queuedRunId, - executionLockedAt: new Date(), - }) - .where(eq(issues.id, issueId)); - return { id: queuedRunId }; + const queuedRunId = randomUUID(); + await db.insert(heartbeatRuns).values({ + id: queuedRunId, + companyId: issue.companyId, + agentId, + invocationSource: wakeupOpts?.source ?? "assignment", + triggerDetail: wakeupOpts?.triggerDetail ?? null, + status: "queued", + contextSnapshot: { ...(wakeupOpts?.contextSnapshot ?? {}), issueId }, + }); + await db + .update(issues) + .set({ + executionRunId: queuedRunId, + executionLockedAt: new Date(), + }) + .where(eq(issues.id, issueId)); + return { id: queuedRunId }; + }, }, - }, - }), - }; -}); + }), + }; + }); +} const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport(); const describeEmbeddedPostgres = embeddedPostgresSupport.supported ? describe : describe.skip; @@ -94,6 +95,11 @@ describeEmbeddedPostgres("routine routes end-to-end", () => { db = createDb(tempDb.connectionString); }, 20_000); + beforeEach(() => { + vi.resetModules(); + registerServiceMocks(); + }); + afterEach(async () => { await db.delete(activityLog); await db.delete(routineRuns); @@ -102,6 +108,8 @@ describeEmbeddedPostgres("routine routes end-to-end", () => { await db.delete(heartbeatRuns); await db.delete(agentWakeupRequests); await db.delete(issues); + await db.delete(executionWorkspaces); + await db.delete(projectWorkspaces); await db.delete(principalPermissionGrants); await db.delete(companyMemberships); await db.delete(routines); @@ -271,5 +279,184 @@ describeEmbeddedPostgres("routine routes end-to-end", () => { "routine.run_triggered", ]), ); + }, 15_000); + + it("runs routines with variable inputs and interpolates the execution issue description", async () => { + const { companyId, agentId, projectId, userId } = await seedFixture(); + const app = await createApp({ + type: "board", + userId, + source: "session", + isInstanceAdmin: false, + companyIds: [companyId], + }); + + const createRes = await request(app) + .post(`/api/companies/${companyId}/routines`) + .send({ + projectId, + title: "Repository triage", + description: "Review {{repo}} for {{priority}} bugs", + assigneeAgentId: agentId, + variables: [ + { name: "repo", type: "text", required: true }, + { name: "priority", type: "select", required: true, defaultValue: "high", options: ["high", "low"] }, + ], + }); + + expect(createRes.status).toBe(201); + + const runRes = await request(app) + .post(`/api/routines/${createRes.body.id}/run`) + .send({ + source: "manual", + variables: { repo: "paperclip" }, + }); + + expect(runRes.status).toBe(202); + expect(runRes.body.triggerPayload).toEqual({ + variables: { + repo: "paperclip", + priority: "high", + }, + }); + + const [issue] = await db + .select({ description: issues.description }) + .from(issues) + .where(eq(issues.id, runRes.body.linkedIssueId)); + + expect(issue?.description).toBe("Review paperclip for high bugs"); + }); + + it("allows drafting a routine without defaults and running it with one-off overrides", async () => { + const { companyId, agentId, projectId, userId } = await seedFixture(); + const app = await createApp({ + type: "board", + userId, + source: "session", + isInstanceAdmin: false, + companyIds: [companyId], + }); + + const createRes = await request(app) + .post(`/api/companies/${companyId}/routines`) + .send({ + title: "Draft routine", + description: "No saved defaults", + }); + + expect(createRes.status).toBe(201); + expect(createRes.body.projectId).toBeNull(); + expect(createRes.body.assigneeAgentId).toBeNull(); + expect(createRes.body.status).toBe("paused"); + + const runRes = await request(app) + .post(`/api/routines/${createRes.body.id}/run`) + .send({ + source: "manual", + projectId, + assigneeAgentId: agentId, + }); + + expect(runRes.status).toBe(202); + expect(runRes.body.status).toBe("issue_created"); + + const [issue] = await db + .select({ + projectId: issues.projectId, + assigneeAgentId: issues.assigneeAgentId, + }) + .from(issues) + .where(eq(issues.id, runRes.body.linkedIssueId)); + + expect(issue).toEqual({ + projectId, + assigneeAgentId: agentId, + }); + }); + + it("persists execution workspace selections from manual routine runs", async () => { + const { companyId, agentId, projectId, userId } = await seedFixture(); + const projectWorkspaceId = randomUUID(); + const executionWorkspaceId = randomUUID(); + const app = await createApp({ + type: "board", + userId, + source: "session", + isInstanceAdmin: false, + companyIds: [companyId], + }); + + await db.insert(projectWorkspaces).values({ + id: projectWorkspaceId, + companyId, + projectId, + name: "Primary workspace", + isPrimary: true, + sharedWorkspaceKey: "routine-primary", + }); + await db.insert(executionWorkspaces).values({ + id: executionWorkspaceId, + companyId, + projectId, + projectWorkspaceId, + mode: "isolated_workspace", + strategyType: "git_worktree", + name: "Routine worktree", + status: "active", + providerType: "git_worktree", + }); + await db + .update(projects) + .set({ + executionWorkspacePolicy: { + enabled: true, + defaultMode: "shared_workspace", + defaultProjectWorkspaceId: projectWorkspaceId, + }, + }) + .where(eq(projects.id, projectId)); + await db.insert(instanceSettings).values({ + experimental: { enableIsolatedWorkspaces: true }, + }); + + const createRes = await request(app) + .post(`/api/companies/${companyId}/routines`) + .send({ + projectId, + title: "Workspace-aware routine", + assigneeAgentId: agentId, + }); + + expect(createRes.status).toBe(201); + + const runRes = await request(app) + .post(`/api/routines/${createRes.body.id}/run`) + .send({ + source: "manual", + executionWorkspaceId, + executionWorkspacePreference: "reuse_existing", + executionWorkspaceSettings: { mode: "isolated_workspace" }, + }); + + expect(runRes.status).toBe(202); + + const [issue] = await db + .select({ + projectWorkspaceId: issues.projectWorkspaceId, + executionWorkspaceId: issues.executionWorkspaceId, + executionWorkspacePreference: issues.executionWorkspacePreference, + executionWorkspaceSettings: issues.executionWorkspaceSettings, + }) + .from(issues) + .where(eq(issues.id, runRes.body.linkedIssueId)); + + expect(issue).toEqual({ + projectWorkspaceId, + executionWorkspaceId, + executionWorkspacePreference: "reuse_existing", + executionWorkspaceSettings: { mode: "isolated_workspace" }, + }); }); }); diff --git a/server/src/__tests__/routines-routes.test.ts b/server/src/__tests__/routines-routes.test.ts index 0c3c0b2b7cb..ce1db54950f 100644 --- a/server/src/__tests__/routines-routes.test.ts +++ b/server/src/__tests__/routines-routes.test.ts @@ -1,8 +1,6 @@ import express from "express"; import request from "supertest"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { routineRoutes } from "../routes/routines.js"; -import { errorHandler } from "../middleware/index.js"; const companyId = "22222222-2222-4222-8222-222222222222"; const agentId = "11111111-1111-4111-8111-111111111111"; @@ -82,14 +80,31 @@ const mockAccessService = vi.hoisted(() => ({ })); const mockLogActivity = vi.hoisted(() => vi.fn()); +const mockTrackRoutineCreated = vi.hoisted(() => vi.fn()); +const mockGetTelemetryClient = vi.hoisted(() => vi.fn()); -vi.mock("../services/index.js", () => ({ - accessService: () => mockAccessService, - logActivity: mockLogActivity, - routineService: () => mockRoutineService, -})); +function registerRouteMocks() { + vi.doMock("@paperclipai/shared/telemetry", () => ({ + trackRoutineCreated: mockTrackRoutineCreated, + trackErrorHandlerCrash: vi.fn(), + })); -function createApp(actor: Record) { + vi.doMock("../telemetry.js", () => ({ + getTelemetryClient: mockGetTelemetryClient, + })); + + vi.doMock("../services/index.js", () => ({ + accessService: () => mockAccessService, + logActivity: mockLogActivity, + routineService: () => mockRoutineService, + })); +} + +async function createApp(actor: Record) { + const [{ routineRoutes }, { errorHandler }] = await Promise.all([ + import("../routes/routines.js"), + import("../middleware/index.js"), + ]); const app = express(); app.use(express.json()); app.use((req, _res, next) => { @@ -103,7 +118,10 @@ function createApp(actor: Record) { describe("routine routes", () => { beforeEach(() => { + vi.resetModules(); + registerRouteMocks(); vi.clearAllMocks(); + mockGetTelemetryClient.mockReturnValue({ track: vi.fn() }); mockRoutineService.create.mockResolvedValue(routine); mockRoutineService.get.mockResolvedValue(routine); mockRoutineService.getTrigger.mockResolvedValue(trigger); @@ -118,7 +136,7 @@ describe("routine routes", () => { }); it("requires tasks:assign permission for non-admin board routine creation", async () => { - const app = createApp({ + const app = await createApp({ type: "board", userId: "board-user", source: "session", @@ -140,7 +158,7 @@ describe("routine routes", () => { }); it("requires tasks:assign permission to retarget a routine assignee", async () => { - const app = createApp({ + const app = await createApp({ type: "board", userId: "board-user", source: "session", @@ -161,7 +179,7 @@ describe("routine routes", () => { it("requires tasks:assign permission to reactivate a routine", async () => { mockRoutineService.get.mockResolvedValue(pausedRoutine); - const app = createApp({ + const app = await createApp({ type: "board", userId: "board-user", source: "session", @@ -181,7 +199,7 @@ describe("routine routes", () => { }); it("requires tasks:assign permission to create a trigger", async () => { - const app = createApp({ + const app = await createApp({ type: "board", userId: "board-user", source: "session", @@ -203,7 +221,7 @@ describe("routine routes", () => { }); it("requires tasks:assign permission to update a trigger", async () => { - const app = createApp({ + const app = await createApp({ type: "board", userId: "board-user", source: "session", @@ -223,7 +241,7 @@ describe("routine routes", () => { }); it("requires tasks:assign permission to manually run a routine", async () => { - const app = createApp({ + const app = await createApp({ type: "board", userId: "board-user", source: "session", @@ -242,7 +260,7 @@ describe("routine routes", () => { it("allows routine creation when the board user has tasks:assign", async () => { mockAccessService.canUser.mockResolvedValue(true); - const app = createApp({ + const app = await createApp({ type: "board", userId: "board-user", source: "session", @@ -267,5 +285,6 @@ describe("routine routes", () => { agentId: null, userId: "board-user", }); + expect(mockTrackRoutineCreated).toHaveBeenCalledWith(expect.anything()); }); }); diff --git a/server/src/__tests__/routines-service.test.ts b/server/src/__tests__/routines-service.test.ts index d6aad0f2fb4..abaac6ac1e0 100644 --- a/server/src/__tests__/routines-service.test.ts +++ b/server/src/__tests__/routines-service.test.ts @@ -8,8 +8,11 @@ import { companySecrets, companySecretVersions, createDb, + executionWorkspaces, heartbeatRuns, + instanceSettings, issues, + projectWorkspaces, projects, routineRuns, routines, @@ -20,6 +23,7 @@ import { startEmbeddedPostgresTestDatabase, } from "./helpers/embedded-postgres.js"; import { issueService } from "../services/issues.ts"; +import { instanceSettingsService } from "../services/instance-settings.ts"; import { routineService } from "../services/routines.ts"; const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport(); @@ -49,9 +53,12 @@ describeEmbeddedPostgres("routine service live-execution coalescing", () => { await db.delete(companySecrets); await db.delete(heartbeatRuns); await db.delete(issues); + await db.delete(executionWorkspaces); + await db.delete(projectWorkspaces); await db.delete(projects); await db.delete(agents); await db.delete(companies); + await db.delete(instanceSettings); }); afterAll(async () => { @@ -214,6 +221,31 @@ describeEmbeddedPostgres("routine service live-execution coalescing", () => { expect(routineIssues.map((issue) => issue.id)).toContain(run.linkedIssueId); }); + it("creates draft routines without a project or default assignee", async () => { + const { companyId, svc } = await seedFixture(); + + const routine = await svc.create( + companyId, + { + projectId: null, + goalId: null, + parentIssueId: null, + title: "draft routine", + description: "No defaults yet", + assigneeAgentId: null, + priority: "medium", + status: "active", + concurrencyPolicy: "coalesce_if_active", + catchUpPolicy: "skip_missed", + }, + {}, + ); + + expect(routine.projectId).toBeNull(); + expect(routine.assigneeAgentId).toBeNull(); + expect(routine.status).toBe("paused"); + }); + it("wakes the assignee when a routine creates a fresh execution issue", async () => { const { agentId, routine, svc, wakeups } = await seedFixture(); @@ -317,6 +349,265 @@ describeEmbeddedPostgres("routine service live-execution coalescing", () => { expect(routineIssues[0]?.id).toBe(previousIssue.id); }); + it("interpolates routine variables into the execution issue and stores resolved values", async () => { + const { companyId, agentId, projectId, svc } = await seedFixture(); + const variableRoutine = await svc.create( + companyId, + { + projectId, + goalId: null, + parentIssueId: null, + title: "repo triage for {{repo}}", + description: "Review {{repo}} for {{priority}} bugs", + assigneeAgentId: agentId, + priority: "medium", + status: "active", + concurrencyPolicy: "coalesce_if_active", + catchUpPolicy: "skip_missed", + variables: [ + { name: "repo", label: null, type: "text", defaultValue: null, required: true, options: [] }, + { name: "priority", label: null, type: "select", defaultValue: "high", required: true, options: ["high", "low"] }, + ], + }, + {}, + ); + expect(variableRoutine.variables.map((variable) => variable.name)).toEqual(["repo", "priority"]); + + const run = await svc.runRoutine(variableRoutine.id, { + source: "manual", + variables: { repo: "paperclip" }, + }); + + const storedIssue = await db + .select({ title: issues.title, description: issues.description }) + .from(issues) + .where(eq(issues.id, run.linkedIssueId!)) + .then((rows) => rows[0] ?? null); + const storedRun = await db + .select({ triggerPayload: routineRuns.triggerPayload }) + .from(routineRuns) + .where(eq(routineRuns.id, run.id)) + .then((rows) => rows[0] ?? null); + + expect(storedIssue?.title).toBe("repo triage for paperclip"); + expect(storedIssue?.description).toBe("Review paperclip for high bugs"); + expect(storedRun?.triggerPayload).toEqual({ + variables: { + repo: "paperclip", + priority: "high", + }, + }); + }); + + it("attaches the selected execution workspace to manually triggered routine issues", async () => { + const { companyId, projectId, routine, svc } = await seedFixture(); + const projectWorkspaceId = randomUUID(); + const executionWorkspaceId = randomUUID(); + + await instanceSettingsService(db).updateExperimental({ enableIsolatedWorkspaces: true }); + await db + .update(projects) + .set({ + executionWorkspacePolicy: { + enabled: true, + defaultMode: "shared_workspace", + defaultProjectWorkspaceId: projectWorkspaceId, + }, + }) + .where(eq(projects.id, projectId)); + await db.insert(projectWorkspaces).values({ + id: projectWorkspaceId, + companyId, + projectId, + name: "Primary workspace", + isPrimary: true, + sharedWorkspaceKey: "routine-primary", + }); + await db.insert(executionWorkspaces).values({ + id: executionWorkspaceId, + companyId, + projectId, + projectWorkspaceId, + mode: "isolated_workspace", + strategyType: "git_worktree", + name: "Routine worktree", + status: "active", + providerType: "git_worktree", + }); + + const run = await svc.runRoutine(routine.id, { + source: "manual", + executionWorkspaceId, + executionWorkspacePreference: "reuse_existing", + executionWorkspaceSettings: { mode: "isolated_workspace" }, + }); + + const storedIssue = await db + .select({ + projectWorkspaceId: issues.projectWorkspaceId, + executionWorkspaceId: issues.executionWorkspaceId, + executionWorkspacePreference: issues.executionWorkspacePreference, + executionWorkspaceSettings: issues.executionWorkspaceSettings, + }) + .from(issues) + .where(eq(issues.id, run.linkedIssueId!)) + .then((rows) => rows[0] ?? null); + + expect(storedIssue).toEqual({ + projectWorkspaceId, + executionWorkspaceId, + executionWorkspacePreference: "reuse_existing", + executionWorkspaceSettings: { mode: "isolated_workspace" }, + }); + }); + + it("runs draft routines with one-off agent and project overrides", async () => { + const { companyId, agentId, projectId, svc } = await seedFixture(); + const draftRoutine = await svc.create( + companyId, + { + projectId: null, + goalId: null, + parentIssueId: null, + title: "draft dispatch", + description: "Pick defaults at run time", + assigneeAgentId: null, + priority: "medium", + status: "paused", + concurrencyPolicy: "coalesce_if_active", + catchUpPolicy: "skip_missed", + }, + {}, + ); + + const run = await svc.runRoutine(draftRoutine.id, { + source: "manual", + projectId, + assigneeAgentId: agentId, + }); + + expect(run.status).toBe("issue_created"); + expect(run.linkedIssueId).toBeTruthy(); + + const storedIssue = await db + .select({ + projectId: issues.projectId, + assigneeAgentId: issues.assigneeAgentId, + }) + .from(issues) + .where(eq(issues.id, run.linkedIssueId!)) + .then((rows) => rows[0] ?? null); + + expect(storedIssue).toEqual({ + projectId, + assigneeAgentId: agentId, + }); + }); + + it("rejects enabling automation for routines without a default agent", async () => { + const { companyId, svc } = await seedFixture(); + const draftRoutine = await svc.create( + companyId, + { + projectId: null, + goalId: null, + parentIssueId: null, + title: "draft routine", + description: null, + assigneeAgentId: null, + priority: "medium", + status: "paused", + concurrencyPolicy: "coalesce_if_active", + catchUpPolicy: "skip_missed", + }, + {}, + ); + + await expect( + svc.update(draftRoutine.id, { status: "active" }, {}), + ).rejects.toThrow(/default agent required/i); + }); + + it("blocks schedule triggers when required variables do not have defaults", async () => { + const { companyId, agentId, projectId, svc } = await seedFixture(); + const variableRoutine = await svc.create( + companyId, + { + projectId, + goalId: null, + parentIssueId: null, + title: "repo triage", + description: "Review {{repo}}", + assigneeAgentId: agentId, + priority: "medium", + status: "active", + concurrencyPolicy: "coalesce_if_active", + catchUpPolicy: "skip_missed", + variables: [ + { name: "repo", label: null, type: "text", defaultValue: null, required: true, options: [] }, + ], + }, + {}, + ); + + await expect( + svc.createTrigger(variableRoutine.id, { + kind: "schedule", + label: "daily", + cronExpression: "0 10 * * *", + timezone: "UTC", + }, {}), + ).rejects.toThrow(/require defaults for required variables/i); + }); + + it("treats malformed stored defaults as missing when validating schedule triggers", async () => { + const { companyId, agentId, projectId, svc } = await seedFixture(); + const variableRoutine = await svc.create( + companyId, + { + projectId, + goalId: null, + parentIssueId: null, + title: "ship check", + description: "Review {{approved}}", + assigneeAgentId: agentId, + priority: "medium", + status: "active", + concurrencyPolicy: "coalesce_if_active", + catchUpPolicy: "skip_missed", + variables: [ + { name: "approved", label: null, type: "boolean", defaultValue: true, required: true, options: [] }, + ], + }, + {}, + ); + + await db + .update(routines) + .set({ + variables: [ + { + name: "approved", + label: null, + type: "boolean", + defaultValue: "definitely", + required: true, + options: [], + }, + ], + }) + .where(eq(routines.id, variableRoutine.id)); + + await expect( + svc.createTrigger(variableRoutine.id, { + kind: "schedule", + label: "daily", + cronExpression: "0 10 * * *", + timezone: "UTC", + }, {}), + ).rejects.toThrow(/require defaults for required variables/i); + }); + it("serializes concurrent dispatches until the first execution issue is linked to a queued run", async () => { const { routine, svc } = await seedFixture({ wakeup: async (wakeupAgentId, wakeupOpts) => { @@ -420,4 +711,72 @@ describeEmbeddedPostgres("routine service live-execution coalescing", () => { expect(run.status).toBe("issue_created"); expect(run.linkedIssueId).toBeTruthy(); }); + + it("accepts GitHub-style X-Hub-Signature-256 with github_hmac signing mode", async () => { + const { routine, svc } = await seedFixture(); + const { trigger, secretMaterial } = await svc.createTrigger( + routine.id, + { + kind: "webhook", + signingMode: "github_hmac", + }, + {}, + ); + + const payload = { action: "opened", pull_request: { number: 1 } }; + const rawBody = Buffer.from(JSON.stringify(payload)); + const signature = `sha256=${createHmac("sha256", secretMaterial!.webhookSecret) + .update(rawBody) + .digest("hex")}`; + + const run = await svc.firePublicTrigger(trigger.publicId!, { + hubSignatureHeader: signature, + rawBody, + payload, + }); + + expect(run.source).toBe("webhook"); + expect(run.status).toBe("issue_created"); + }); + + it("rejects invalid signature for github_hmac signing mode", async () => { + const { routine, svc } = await seedFixture(); + const { trigger } = await svc.createTrigger( + routine.id, + { + kind: "webhook", + signingMode: "github_hmac", + }, + {}, + ); + + const rawBody = Buffer.from(JSON.stringify({ ok: true })); + + await expect( + svc.firePublicTrigger(trigger.publicId!, { + hubSignatureHeader: "sha256=0000000000000000000000000000000000000000000000000000000000000000", + rawBody, + payload: { ok: true }, + }), + ).rejects.toThrow(); + }); + + it("accepts any request with none signing mode", async () => { + const { routine, svc } = await seedFixture(); + const { trigger } = await svc.createTrigger( + routine.id, + { + kind: "webhook", + signingMode: "none", + }, + {}, + ); + + const run = await svc.firePublicTrigger(trigger.publicId!, { + payload: { event: "error.created" }, + }); + + expect(run.source).toBe("webhook"); + expect(run.status).toBe("issue_created"); + }); }); diff --git a/server/src/__tests__/server-startup-feedback-export.test.ts b/server/src/__tests__/server-startup-feedback-export.test.ts new file mode 100644 index 00000000000..785656458ed --- /dev/null +++ b/server/src/__tests__/server-startup-feedback-export.test.ts @@ -0,0 +1,173 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const { + createAppMock, + createDbMock, + detectPortMock, + feedbackExportServiceMock, + feedbackServiceFactoryMock, + fakeServer, +} = vi.hoisted(() => { + const createAppMock = vi.fn(async () => ((_: unknown, __: unknown) => {}) as never); + const createDbMock = vi.fn(() => ({}) as never); + const detectPortMock = vi.fn(async (port: number) => port); + const feedbackExportServiceMock = { + flushPendingFeedbackTraces: vi.fn(async () => ({ attempted: 0, sent: 0, failed: 0 })), + }; + const feedbackServiceFactoryMock = vi.fn(() => feedbackExportServiceMock); + const fakeServer = { + once: vi.fn().mockReturnThis(), + off: vi.fn().mockReturnThis(), + listen: vi.fn((_port: number, _host: string, callback?: () => void) => { + callback?.(); + return fakeServer; + }), + close: vi.fn(), + }; + + return { + createAppMock, + createDbMock, + detectPortMock, + feedbackExportServiceMock, + feedbackServiceFactoryMock, + fakeServer, + }; +}); + +vi.mock("node:http", () => ({ + createServer: vi.fn(() => fakeServer), +})); + +vi.mock("detect-port", () => ({ + default: detectPortMock, +})); + +vi.mock("@paperclipai/db", () => ({ + createDb: createDbMock, + ensurePostgresDatabase: vi.fn(), + getPostgresDataDirectory: vi.fn(), + inspectMigrations: vi.fn(async () => ({ status: "upToDate" })), + applyPendingMigrations: vi.fn(), + reconcilePendingMigrationHistory: vi.fn(async () => ({ repairedMigrations: [] })), + formatDatabaseBackupResult: vi.fn(() => "ok"), + runDatabaseBackup: vi.fn(), + authUsers: {}, + companies: {}, + companyMemberships: {}, + instanceUserRoles: {}, +})); + +vi.mock("../app.js", () => ({ + createApp: createAppMock, +})); + +vi.mock("../config.js", () => ({ + loadConfig: vi.fn(() => ({ + deploymentMode: "authenticated", + deploymentExposure: "private", + host: "127.0.0.1", + port: 3210, + allowedHostnames: [], + authBaseUrlMode: "auto", + authPublicBaseUrl: undefined, + authDisableSignUp: false, + databaseMode: "postgres", + databaseUrl: "postgres://paperclip:paperclip@127.0.0.1:5432/paperclip", + embeddedPostgresDataDir: "/tmp/paperclip-test-db", + embeddedPostgresPort: 54329, + databaseBackupEnabled: false, + databaseBackupIntervalMinutes: 60, + databaseBackupRetentionDays: 30, + databaseBackupDir: "/tmp/paperclip-test-backups", + serveUi: false, + uiDevMiddleware: false, + secretsProvider: "local_encrypted", + secretsStrictMode: false, + secretsMasterKeyFilePath: "/tmp/paperclip-master.key", + storageProvider: "local_disk", + storageLocalDiskBaseDir: "/tmp/paperclip-storage", + storageS3Bucket: "paperclip-test", + storageS3Region: "us-east-1", + storageS3Endpoint: undefined, + storageS3Prefix: "", + storageS3ForcePathStyle: false, + feedbackExportBackendUrl: "https://telemetry.example.com", + feedbackExportBackendToken: "telemetry-token", + heartbeatSchedulerEnabled: false, + heartbeatSchedulerIntervalMs: 30000, + companyDeletionEnabled: false, + })), +})); + +vi.mock("../middleware/logger.js", () => ({ + logger: { + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + }, +})); + +vi.mock("../realtime/live-events-ws.js", () => ({ + setupLiveEventsWebSocketServer: vi.fn(), +})); + +vi.mock("../services/index.js", () => ({ + feedbackService: feedbackServiceFactoryMock, + heartbeatService: vi.fn(() => ({ + reapOrphanedRuns: vi.fn(async () => undefined), + resumeQueuedRuns: vi.fn(async () => undefined), + tickTimers: vi.fn(async () => ({ enqueued: 0 })), + })), + reconcilePersistedRuntimeServicesOnStartup: vi.fn(async () => ({ reconciled: 0 })), + routineService: vi.fn(() => ({ + tickScheduledTriggers: vi.fn(async () => ({ triggered: 0 })), + })), +})); + +vi.mock("../storage/index.js", () => ({ + createStorageServiceFromConfig: vi.fn(() => ({ id: "storage-service" })), +})); + +vi.mock("../services/feedback-share-client.js", () => ({ + createFeedbackTraceShareClientFromConfig: vi.fn(() => ({ id: "feedback-share-client" })), +})); + +vi.mock("../startup-banner.js", () => ({ + printStartupBanner: vi.fn(), +})); + +vi.mock("../board-claim.js", () => ({ + getBoardClaimWarningUrl: vi.fn(() => null), + initializeBoardClaimChallenge: vi.fn(async () => undefined), +})); + +vi.mock("../auth/better-auth.js", () => ({ + createBetterAuthHandler: vi.fn(() => undefined), + createBetterAuthInstance: vi.fn(() => ({})), + deriveAuthTrustedOrigins: vi.fn(() => []), + resolveBetterAuthSession: vi.fn(async () => null), + resolveBetterAuthSessionFromHeaders: vi.fn(async () => null), +})); + +import { startServer } from "../index.ts"; + +describe("startServer feedback export wiring", () => { + beforeEach(() => { + vi.clearAllMocks(); + process.env.BETTER_AUTH_SECRET = "test-secret"; + }); + + it("passes the feedback export service into createApp so pending traces flush in runtime", async () => { + const started = await startServer(); + + expect(started.server).toBe(fakeServer); + expect(feedbackServiceFactoryMock).toHaveBeenCalledTimes(1); + expect(createAppMock).toHaveBeenCalledTimes(1); + expect(createAppMock.mock.calls[0]?.[1]).toMatchObject({ + feedbackExportService: feedbackExportServiceMock, + storageService: { id: "storage-service" }, + serverPort: 3210, + }); + }); +}); diff --git a/server/src/__tests__/telemetry-client-flush.test.ts b/server/src/__tests__/telemetry-client-flush.test.ts new file mode 100644 index 00000000000..2264638c2fc --- /dev/null +++ b/server/src/__tests__/telemetry-client-flush.test.ts @@ -0,0 +1,85 @@ +import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"; +import { TelemetryClient } from "../../../packages/shared/src/telemetry/client.js"; +import type { TelemetryConfig, TelemetryState } from "../../../packages/shared/src/telemetry/types.js"; + +function makeClient(config?: Partial) { + const merged: TelemetryConfig = { enabled: true, endpoint: "http://localhost:9999/ingest", ...config }; + const state: TelemetryState = { + installId: "test-install", + salt: "test-salt", + createdAt: "2026-01-01T00:00:00Z", + firstSeenVersion: "0.0.0", + }; + return new TelemetryClient(merged, () => state, "0.0.0-test"); +} + +describe("TelemetryClient periodic flush", () => { + beforeEach(() => { + vi.useFakeTimers(); + vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ ok: true })); + }); + + afterEach(() => { + vi.useRealTimers(); + vi.restoreAllMocks(); + }); + + it("flushes queued events on interval", async () => { + const client = makeClient(); + client.startPeriodicFlush(1000); + + client.track("install.started"); + expect(fetch).not.toHaveBeenCalled(); + + await vi.advanceTimersByTimeAsync(1000); + expect(fetch).toHaveBeenCalledTimes(1); + const lastCall = vi.mocked(fetch).mock.calls.at(-1); + expect(lastCall?.[0]).toBe("http://localhost:9999/ingest"); + const requestInit = lastCall?.[1] as RequestInit | undefined; + expect(requestInit?.method).toBe("POST"); + expect(requestInit?.headers).toEqual({ "Content-Type": "application/json" }); + const body = JSON.parse(String(requestInit?.body ?? "{}")); + expect(body).toMatchObject({ + app: "paperclip", + schemaVersion: "1", + installId: "test-install", + version: "0.0.0-test", + events: [ + { + name: "install.started", + dimensions: {}, + }, + ], + }); + expect(body.events[0]?.occurredAt).toEqual(expect.any(String)); + + // Second tick with no new events — no additional call + await vi.advanceTimersByTimeAsync(1000); + expect(fetch).toHaveBeenCalledTimes(1); + + // New event gets flushed on next tick + client.track("install.started"); + await vi.advanceTimersByTimeAsync(1000); + expect(fetch).toHaveBeenCalledTimes(2); + + client.stop(); + }); + + it("stop() prevents further flushes", async () => { + const client = makeClient(); + client.startPeriodicFlush(1000); + + client.track("install.started"); + client.stop(); + + await vi.advanceTimersByTimeAsync(2000); + expect(fetch).not.toHaveBeenCalled(); + }); + + it("startPeriodicFlush is idempotent", () => { + const client = makeClient(); + client.startPeriodicFlush(1000); + client.startPeriodicFlush(1000); // should not throw or double-fire + client.stop(); + }); +}); diff --git a/server/src/__tests__/workspace-runtime.test.ts b/server/src/__tests__/workspace-runtime.test.ts index 573013bea66..c05fa1ce8e0 100644 --- a/server/src/__tests__/workspace-runtime.test.ts +++ b/server/src/__tests__/workspace-runtime.test.ts @@ -18,6 +18,7 @@ import { import { eq } from "drizzle-orm"; import { cleanupExecutionWorkspaceArtifacts, + ensureServerWorkspaceLinksCurrent, ensureRuntimeServicesForRun, normalizeAdapterManagedRuntimeServices, reconcilePersistedRuntimeServicesOnStartup, @@ -28,6 +29,7 @@ import { stopRuntimeServicesForExecutionWorkspace, type RealizedExecutionWorkspace, } from "../services/workspace-runtime.ts"; +import { writeLocalServiceRegistryRecord } from "../services/local-service-supervisor.ts"; import { resolvePaperclipConfigPath } from "../paths.ts"; import type { WorkspaceOperation } from "@paperclipai/shared"; import type { WorkspaceOperationRecorder } from "../services/workspace-operations.ts"; @@ -51,7 +53,11 @@ async function runGit(cwd: string, args: string[]) { await execFileAsync("git", args, { cwd }); } -async function createTempRepo() { +async function runPnpm(cwd: string, args: string[]) { + await execFileAsync("pnpm", args, { cwd }); +} + +async function createTempRepo(defaultBranch = "main") { const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-worktree-repo-")); await runGit(repoRoot, ["init"]); await runGit(repoRoot, ["config", "user.email", "paperclip@example.com"]); @@ -59,7 +65,7 @@ async function createTempRepo() { await fs.writeFile(path.join(repoRoot, "README.md"), "hello\n", "utf8"); await runGit(repoRoot, ["add", "README.md"]); await runGit(repoRoot, ["commit", "-m", "Initial commit"]); - await runGit(repoRoot, ["checkout", "-B", "main"]); + await runGit(repoRoot, ["checkout", "-B", defaultBranch]); return repoRoot; } @@ -370,6 +376,96 @@ describe("realizeExecutionWorkspace", () => { await expect(fs.readFile(path.join(reused.cwd, ".paperclip-provision-created"), "utf8")).resolves.toBe("false\n"); }); + it("uses the latest repo-managed provision script when reusing an existing worktree", async () => { + const repoRoot = await createTempRepo(); + await fs.mkdir(path.join(repoRoot, "scripts"), { recursive: true }); + await fs.writeFile( + path.join(repoRoot, "scripts", "provision.sh"), + [ + "#!/usr/bin/env bash", + "set -euo pipefail", + "printf 'v1\\n' > .paperclip-provision-version", + ].join("\n"), + "utf8", + ); + await runGit(repoRoot, ["add", "scripts/provision.sh"]); + await runGit(repoRoot, ["commit", "-m", "Add initial provision script"]); + + const initial = await realizeExecutionWorkspace({ + base: { + baseCwd: repoRoot, + source: "project_primary", + projectId: "project-1", + workspaceId: "workspace-1", + repoUrl: null, + repoRef: "HEAD", + }, + config: { + workspaceStrategy: { + type: "git_worktree", + branchTemplate: "{{issue.identifier}}-{{slug}}", + provisionCommand: "bash ./scripts/provision.sh", + }, + }, + issue: { + id: "issue-1", + identifier: "PAP-449", + title: "Reuse latest provision script", + }, + agent: { + id: "agent-1", + name: "Codex Coder", + companyId: "company-1", + }, + }); + + await expect(fs.readFile(path.join(initial.cwd, ".paperclip-provision-version"), "utf8")).resolves.toBe("v1\n"); + + await fs.writeFile( + path.join(repoRoot, "scripts", "provision.sh"), + [ + "#!/usr/bin/env bash", + "set -euo pipefail", + "printf 'v2\\n' > .paperclip-provision-version", + ].join("\n"), + "utf8", + ); + await runGit(repoRoot, ["add", "scripts/provision.sh"]); + await runGit(repoRoot, ["commit", "-m", "Update provision script"]); + + await expect(fs.readFile(path.join(initial.cwd, "scripts", "provision.sh"), "utf8")).resolves.toContain("v1"); + + const reused = await realizeExecutionWorkspace({ + base: { + baseCwd: repoRoot, + source: "project_primary", + projectId: "project-1", + workspaceId: "workspace-1", + repoUrl: null, + repoRef: "HEAD", + }, + config: { + workspaceStrategy: { + type: "git_worktree", + branchTemplate: "{{issue.identifier}}-{{slug}}", + provisionCommand: "bash ./scripts/provision.sh", + }, + }, + issue: { + id: "issue-1", + identifier: "PAP-449", + title: "Reuse latest provision script", + }, + agent: { + id: "agent-1", + name: "Codex Coder", + companyId: "company-1", + }, + }); + + await expect(fs.readFile(path.join(reused.cwd, ".paperclip-provision-version"), "utf8")).resolves.toBe("v2\n"); + }); + it("writes an isolated repo-local Paperclip config and worktree branding when provisioning", async () => { const repoRoot = await createTempRepo(); const previousCwd = process.cwd(); @@ -505,20 +601,300 @@ describe("realizeExecutionWorkspace", () => { path.join(expectedInstanceRoot, "secrets", "master.key"), ); expect(envContents).not.toContain("DATABASE_URL="); - expect(envContents).toContain(`PAPERCLIP_HOME=${JSON.stringify(isolatedWorktreeHome)}`); - expect(envContents).toContain(`PAPERCLIP_INSTANCE_ID=${JSON.stringify(expectedInstanceId)}`); - expect(envContents).toContain(`PAPERCLIP_CONFIG=${JSON.stringify(configPath)}`); - expect(envContents).toContain("PAPERCLIP_IN_WORKTREE=true"); - expect(envContents).toContain( - `PAPERCLIP_WORKTREE_NAME=${JSON.stringify("PAP-885-show-worktree-banner")}`, - ); + const envVars = parseEnvContents(envContents); + expect(envVars.PAPERCLIP_HOME).toBe(isolatedWorktreeHome); + expect(envVars.PAPERCLIP_INSTANCE_ID).toBe(expectedInstanceId); + expect(await fs.realpath(envVars.PAPERCLIP_CONFIG!)).toBe(await fs.realpath(configPath)); + expect(envVars.PAPERCLIP_IN_WORKTREE).toBe("true"); + expect(envVars.PAPERCLIP_WORKTREE_NAME).toBe("PAP-885-show-worktree-banner"); process.chdir(workspace.cwd); expect(resolvePaperclipConfigPath()).toBe(configPath); } finally { process.chdir(previousCwd); } - }); + }, 15_000); + + it( + "provisions worktree-local pnpm node_modules instead of reusing base-repo links", + async () => { + const repoRoot = await createTempRepo(); + await fs.mkdir(path.join(repoRoot, "scripts"), { recursive: true }); + await fs.mkdir(path.join(repoRoot, "packages", "shared"), { recursive: true }); + await fs.mkdir(path.join(repoRoot, "server"), { recursive: true }); + await fs.writeFile( + path.join(repoRoot, "package.json"), + JSON.stringify( + { + name: "workspace-root", + private: true, + packageManager: "pnpm@9.15.4", + }, + null, + 2, + ), + "utf8", + ); + await fs.writeFile( + path.join(repoRoot, "pnpm-workspace.yaml"), + ["packages:", " - packages/*", " - server", ""].join("\n"), + "utf8", + ); + await fs.writeFile( + path.join(repoRoot, "packages", "shared", "package.json"), + JSON.stringify( + { + name: "@repo/shared", + version: "1.0.0", + private: true, + type: "module", + exports: "./index.js", + }, + null, + 2, + ), + "utf8", + ); + await fs.writeFile(path.join(repoRoot, "packages", "shared", "index.js"), "export const value = 'shared';\n", "utf8"); + await fs.writeFile( + path.join(repoRoot, "server", "package.json"), + JSON.stringify( + { + name: "server", + private: true, + type: "module", + dependencies: { + "@repo/shared": "workspace:*", + }, + }, + null, + 2, + ), + "utf8", + ); + await fs.writeFile(path.join(repoRoot, "server", "index.js"), "export {};\n", "utf8"); + await fs.copyFile(provisionWorktreeScriptPath, path.join(repoRoot, "scripts", "provision-worktree.sh")); + await fs.chmod(path.join(repoRoot, "scripts", "provision-worktree.sh"), 0o755); + await runPnpm(repoRoot, ["install"]); + await runGit(repoRoot, ["add", "."]); + await runGit(repoRoot, ["commit", "-m", "Add pnpm workspace fixture"]); + + const workspace = await realizeExecutionWorkspace({ + base: { + baseCwd: repoRoot, + source: "project_primary", + projectId: "project-1", + workspaceId: "workspace-1", + repoUrl: null, + repoRef: "HEAD", + }, + config: { + workspaceStrategy: { + type: "git_worktree", + branchTemplate: "{{issue.identifier}}-{{slug}}", + provisionCommand: "bash ./scripts/provision-worktree.sh", + }, + }, + issue: { + id: "issue-1", + identifier: "PAP-551", + title: "Provision local workspace dependencies", + }, + agent: { + id: "agent-1", + name: "Codex Coder", + companyId: "company-1", + }, + }); + + expect((await fs.lstat(path.join(workspace.cwd, "node_modules"))).isSymbolicLink()).toBe(false); + expect((await fs.lstat(path.join(workspace.cwd, "server", "node_modules"))).isSymbolicLink()).toBe(false); + await expect(fs.realpath(path.join(workspace.cwd, "server", "node_modules", "@repo", "shared"))).resolves.toBe( + await fs.realpath(path.join(workspace.cwd, "packages", "shared")), + ); + await expect(fs.realpath(path.join(repoRoot, "server", "node_modules", "@repo", "shared"))).resolves.toBe( + await fs.realpath(path.join(repoRoot, "packages", "shared")), + ); + }, + 30_000, + ); + + it("provisions successfully when install is needed but there are no symlinked node_modules to move", async () => { + const repoRoot = await createTempRepo(); + await fs.mkdir(path.join(repoRoot, "scripts"), { recursive: true }); + await fs.writeFile( + path.join(repoRoot, "package.json"), + JSON.stringify( + { + name: "workspace-root", + private: true, + packageManager: "pnpm@9.15.4", + }, + null, + 2, + ), + "utf8", + ); + await fs.writeFile( + path.join(repoRoot, "pnpm-lock.yaml"), + [ + "lockfileVersion: '9.0'", + "", + "settings:", + " autoInstallPeers: true", + " excludeLinksFromLockfile: false", + "", + "importers:", + " .: {}", + "", + ].join("\n"), + "utf8", + ); + await fs.copyFile(provisionWorktreeScriptPath, path.join(repoRoot, "scripts", "provision-worktree.sh")); + await fs.chmod(path.join(repoRoot, "scripts", "provision-worktree.sh"), 0o755); + + await fs.mkdir(path.join(repoRoot, "node_modules"), { recursive: true }); + await fs.writeFile(path.join(repoRoot, "node_modules", ".keep"), "", "utf8"); + + await runGit(repoRoot, ["add", "package.json", "pnpm-lock.yaml", "scripts/provision-worktree.sh"]); + await runGit(repoRoot, ["commit", "-m", "Add minimal provision fixture"]); + + const workspace = await realizeExecutionWorkspace({ + base: { + baseCwd: repoRoot, + source: "project_primary", + projectId: "project-1", + workspaceId: "workspace-1", + repoUrl: null, + repoRef: "HEAD", + }, + config: { + workspaceStrategy: { + type: "git_worktree", + branchTemplate: "{{issue.identifier}}-{{slug}}", + provisionCommand: "bash ./scripts/provision-worktree.sh", + }, + }, + issue: { + id: "issue-1", + identifier: "PAP-552", + title: "Install without moved symlinks", + }, + agent: { + id: "agent-1", + name: "Codex Coder", + companyId: "company-1", + }, + }); + + await expect(fs.readFile(path.join(workspace.cwd, ".paperclip", "config.json"), "utf8")).resolves.toContain( + "\"database\"", + ); + }, 30_000); + + it( + "provisions worktree-local pnpm node_modules instead of reusing base-repo links", + async () => { + const repoRoot = await createTempRepo(); + await fs.mkdir(path.join(repoRoot, "scripts"), { recursive: true }); + await fs.mkdir(path.join(repoRoot, "packages", "shared"), { recursive: true }); + await fs.mkdir(path.join(repoRoot, "server"), { recursive: true }); + await fs.writeFile( + path.join(repoRoot, "package.json"), + JSON.stringify( + { + name: "workspace-root", + private: true, + packageManager: "pnpm@9.15.4", + }, + null, + 2, + ), + "utf8", + ); + await fs.writeFile( + path.join(repoRoot, "pnpm-workspace.yaml"), + ["packages:", " - packages/*", " - server", ""].join("\n"), + "utf8", + ); + await fs.writeFile( + path.join(repoRoot, "packages", "shared", "package.json"), + JSON.stringify( + { + name: "@repo/shared", + version: "1.0.0", + private: true, + type: "module", + exports: "./index.js", + }, + null, + 2, + ), + "utf8", + ); + await fs.writeFile(path.join(repoRoot, "packages", "shared", "index.js"), "export const value = 'shared';\n", "utf8"); + await fs.writeFile( + path.join(repoRoot, "server", "package.json"), + JSON.stringify( + { + name: "server", + private: true, + type: "module", + dependencies: { + "@repo/shared": "workspace:*", + }, + }, + null, + 2, + ), + "utf8", + ); + await fs.writeFile(path.join(repoRoot, "server", "index.js"), "export {};\n", "utf8"); + await fs.copyFile(provisionWorktreeScriptPath, path.join(repoRoot, "scripts", "provision-worktree.sh")); + await fs.chmod(path.join(repoRoot, "scripts", "provision-worktree.sh"), 0o755); + await runPnpm(repoRoot, ["install"]); + await runGit(repoRoot, ["add", "."]); + await runGit(repoRoot, ["commit", "-m", "Add pnpm workspace fixture"]); + + const workspace = await realizeExecutionWorkspace({ + base: { + baseCwd: repoRoot, + source: "project_primary", + projectId: "project-1", + workspaceId: "workspace-1", + repoUrl: null, + repoRef: "HEAD", + }, + config: { + workspaceStrategy: { + type: "git_worktree", + branchTemplate: "{{issue.identifier}}-{{slug}}", + provisionCommand: "bash ./scripts/provision-worktree.sh", + }, + }, + issue: { + id: "issue-1", + identifier: "PAP-551", + title: "Provision local workspace dependencies", + }, + agent: { + id: "agent-1", + name: "Codex Coder", + companyId: "company-1", + }, + }); + + expect((await fs.lstat(path.join(workspace.cwd, "node_modules"))).isSymbolicLink()).toBe(false); + expect((await fs.lstat(path.join(workspace.cwd, "server", "node_modules"))).isSymbolicLink()).toBe(false); + await expect(fs.realpath(path.join(workspace.cwd, "server", "node_modules", "@repo", "shared"))).resolves.toBe( + await fs.realpath(path.join(workspace.cwd, "packages", "shared")), + ); + await expect(fs.realpath(path.join(repoRoot, "server", "node_modules", "@repo", "shared"))).resolves.toBe( + await fs.realpath(path.join(repoRoot, "packages", "shared")), + ); + }, + 15_000, + ); it("records worktree setup and provision operations when a recorder is provided", async () => { const repoRoot = await createTempRepo(); @@ -578,6 +954,57 @@ describe("realizeExecutionWorkspace", () => { expect(operations[1]?.command).toBe("bash ./scripts/provision.sh"); }); + it("truncates oversized provision command output before storing it in memory", async () => { + const repoRoot = await createTempRepo(); + const { recorder, operations } = createWorkspaceOperationRecorderDouble(); + + await fs.mkdir(path.join(repoRoot, "scripts"), { recursive: true }); + await fs.writeFile( + path.join(repoRoot, "scripts", "noisy.js"), + 'process.stdout.write("x".repeat(400000));\n', + "utf8", + ); + await runGit(repoRoot, ["add", "scripts/noisy.js"]); + await runGit(repoRoot, ["commit", "-m", "Add noisy provision script"]); + + await realizeExecutionWorkspace({ + base: { + baseCwd: repoRoot, + source: "project_primary", + projectId: "project-1", + workspaceId: "workspace-1", + repoUrl: null, + repoRef: "HEAD", + }, + config: { + workspaceStrategy: { + type: "git_worktree", + branchTemplate: "{{issue.identifier}}-{{slug}}", + provisionCommand: "node ./scripts/noisy.js", + }, + }, + issue: { + id: "issue-1", + identifier: "PAP-1142", + title: "Limit noisy provision output", + }, + agent: { + id: "agent-1", + name: "Codex Coder", + companyId: "company-1", + }, + recorder, + }); + + const provisionOperation = operations.find((operation) => operation.phase === "workspace_provision"); + expect(provisionOperation?.result.metadata).toMatchObject({ + stdoutTruncated: true, + stderrTruncated: false, + }); + expect(provisionOperation?.result.stdout).toContain("[output truncated to last"); + expect(provisionOperation?.result.stdout?.length ?? 0).toBeLessThan(300000); + }); + it("reuses an existing branch without resetting it when recreating a missing worktree", async () => { const repoRoot = await createTempRepo(); const branchName = "PAP-450-recreate-missing-worktree"; @@ -622,6 +1049,109 @@ describe("realizeExecutionWorkspace", () => { expect(actualHead).toBe(expectedHead); }); + it("auto-detects the default branch when baseRef is not configured", async () => { + // Create a repo with "master" as default branch (not "main") + const repoRoot = await createTempRepo("master"); + + // Set up a bare remote and push master so refs/remotes/origin/master + // exists locally. Note: refs/remotes/origin/HEAD is NOT set by a manual + // fetch — that requires git clone or git remote set-head. This test + // exercises the heuristic fallback path in detectDefaultBranch. + const bareRemote = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-worktree-bare-")); + await runGit(bareRemote, ["init", "--bare"]); + await runGit(repoRoot, ["remote", "add", "origin", bareRemote]); + await runGit(repoRoot, ["push", "-u", "origin", "master"]); + await runGit(repoRoot, ["fetch", "origin"]); + + const { recorder, operations } = createWorkspaceOperationRecorderDouble(); + + const workspace = await realizeExecutionWorkspace({ + base: { + baseCwd: repoRoot, + source: "project_primary", + projectId: "project-1", + workspaceId: "workspace-1", + repoUrl: null, + repoRef: null, + }, + config: { + workspaceStrategy: { + type: "git_worktree", + // No baseRef configured — should auto-detect "master" + }, + }, + issue: { + id: "issue-1", + identifier: "PAP-460", + title: "Auto detect default branch", + }, + agent: { + id: "agent-1", + name: "Codex Coder", + companyId: "company-1", + }, + recorder, + }); + + expect(workspace.strategy).toBe("git_worktree"); + expect(workspace.created).toBe(true); + // The worktree should have been created successfully (baseRef resolved to "master") + const worktreeOp = operations.find(op => op.phase === "worktree_prepare" && op.metadata?.created); + expect(worktreeOp).toBeDefined(); + expect(worktreeOp!.metadata!.baseRef).toBe("master"); + }); + + it("auto-detects the default branch via symbolic-ref when origin/HEAD is set", async () => { + // Create a repo with "master" as default branch + const repoRoot = await createTempRepo("master"); + + // Set up a bare remote and push + const bareRemote = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-worktree-bare-symref-")); + await runGit(bareRemote, ["init", "--bare"]); + await runGit(repoRoot, ["remote", "add", "origin", bareRemote]); + await runGit(repoRoot, ["push", "-u", "origin", "master"]); + await runGit(repoRoot, ["fetch", "origin"]); + // Explicitly set refs/remotes/origin/HEAD to exercise the symbolic-ref path + // (git remote set-head -a requires the remote to advertise HEAD, so we set it manually) + await runGit(repoRoot, ["remote", "set-head", "origin", "master"]); + + const { recorder, operations } = createWorkspaceOperationRecorderDouble(); + + const workspace = await realizeExecutionWorkspace({ + base: { + baseCwd: repoRoot, + source: "project_primary", + projectId: "project-1", + workspaceId: "workspace-1", + repoUrl: null, + repoRef: null, + }, + config: { + workspaceStrategy: { + type: "git_worktree", + // No baseRef configured — should auto-detect "master" via symbolic-ref + }, + }, + issue: { + id: "issue-1", + identifier: "PAP-461", + title: "Auto detect default branch via symref", + }, + agent: { + id: "agent-1", + name: "Codex Coder", + companyId: "company-1", + }, + recorder, + }); + + expect(workspace.strategy).toBe("git_worktree"); + expect(workspace.created).toBe(true); + const worktreeOp = operations.find(op => op.phase === "worktree_prepare" && op.metadata?.created); + expect(worktreeOp).toBeDefined(); + expect(worktreeOp!.metadata!.baseRef).toBe("master"); + }); + it("removes a created git worktree and branch during cleanup", async () => { const repoRoot = await createTempRepo(); diff --git a/server/src/adapters/builtin-adapter-types.ts b/server/src/adapters/builtin-adapter-types.ts new file mode 100644 index 00000000000..463a56945ff --- /dev/null +++ b/server/src/adapters/builtin-adapter-types.ts @@ -0,0 +1,15 @@ +/** + * Adapter types shipped with Paperclip. External plugins must not replace these. + */ +export const BUILTIN_ADAPTER_TYPES = new Set([ + "claude_local", + "codex_local", + "cursor", + "gemini_local", + "openclaw_gateway", + "opencode_local", + "pi_local", + "hermes_local", + "process", + "http", +]); diff --git a/server/src/adapters/index.ts b/server/src/adapters/index.ts index 8be40a51e00..49530dc7721 100644 --- a/server/src/adapters/index.ts +++ b/server/src/adapters/index.ts @@ -1,4 +1,14 @@ -export { getServerAdapter, listAdapterModels, listServerAdapters, findServerAdapter, detectAdapterModel } from "./registry.js"; +export { + getServerAdapter, + listAdapterModels, + listServerAdapters, + findServerAdapter, + findActiveServerAdapter, + detectAdapterModel, + registerServerAdapter, + unregisterServerAdapter, + requireServerAdapter, +} from "./registry.js"; export type { ServerAdapterModule, AdapterExecutionContext, diff --git a/server/src/adapters/plugin-loader.ts b/server/src/adapters/plugin-loader.ts new file mode 100644 index 00000000000..a9f704635da --- /dev/null +++ b/server/src/adapters/plugin-loader.ts @@ -0,0 +1,277 @@ +/** + * External adapter plugin loader. + * + * Loads external adapter packages from the adapter-plugin-store and returns + * their ServerAdapterModule instances. The caller (registry.ts) is + * responsible for registering them. + * + * This avoids circular initialization: plugin-loader imports only + * adapter-utils, never registry.ts. + */ + +import fs from "node:fs"; +import path from "node:path"; +import type { ServerAdapterModule } from "./types.js"; +import { logger } from "../middleware/logger.js"; + +import { + listAdapterPlugins, + getAdapterPluginsDir, + getAdapterPluginByType, +} from "../services/adapter-plugin-store.js"; +import type { AdapterPluginRecord } from "../services/adapter-plugin-store.js"; + +// --------------------------------------------------------------------------- +// In-memory UI parser cache +// --------------------------------------------------------------------------- + +const uiParserCache = new Map(); + +export function getUiParserSource(adapterType: string): string | undefined { + return uiParserCache.get(adapterType); +} + +/** + * On cache miss, attempt on-demand extraction from the plugin store. + * Makes the ui-parser.js endpoint self-healing. + */ +export function getOrExtractUiParserSource(adapterType: string): string | undefined { + const cached = uiParserCache.get(adapterType); + if (cached) return cached; + + const record = getAdapterPluginByType(adapterType); + if (!record) return undefined; + + const packageDir = resolvePackageDir(record); + const source = extractUiParserSource(packageDir, record.packageName); + if (source) { + uiParserCache.set(adapterType, source); + logger.info( + { type: adapterType, packageName: record.packageName, origin: "lazy" }, + "UI parser extracted on-demand (cache miss)", + ); + } + return source; +} + +// --------------------------------------------------------------------------- +// Shared helpers +// --------------------------------------------------------------------------- + +function resolvePackageDir(record: Pick): string { + return record.localPath + ? path.resolve(record.localPath) + : path.resolve(getAdapterPluginsDir(), "node_modules", record.packageName); +} + +function resolvePackageEntryPoint(packageDir: string): string { + const pkgJsonPath = path.join(packageDir, "package.json"); + const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8")); + + if (pkg.exports && typeof pkg.exports === "object" && pkg.exports["."]) { + const exp = pkg.exports["."]; + return typeof exp === "string" ? exp : (exp.import ?? exp.default ?? "index.js"); + } + return pkg.main ?? "index.js"; +} + +// --------------------------------------------------------------------------- +// UI parser extraction +// --------------------------------------------------------------------------- + +const SUPPORTED_PARSER_CONTRACT = "1"; + +function extractUiParserSource( + packageDir: string, + packageName: string, +): string | undefined { + const pkgJsonPath = path.join(packageDir, "package.json"); + const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8")); + + if (!pkg.exports || typeof pkg.exports !== "object" || !pkg.exports["./ui-parser"]) { + return undefined; + } + + const contractVersion = pkg.paperclip?.adapterUiParser; + if (contractVersion) { + const major = contractVersion.split(".")[0]; + if (major !== SUPPORTED_PARSER_CONTRACT) { + logger.warn( + { packageName, contractVersion, supported: `${SUPPORTED_PARSER_CONTRACT}.x` }, + "Adapter declares unsupported UI parser contract version — skipping UI parser", + ); + return undefined; + } + } else { + logger.info( + { packageName }, + "Adapter has ./ui-parser export but no paperclip.adapterUiParser version — loading anyway (future versions may require it)", + ); + } + + const uiParserExp = pkg.exports["./ui-parser"]; + const uiParserFile = typeof uiParserExp === "string" + ? uiParserExp + : (uiParserExp.import ?? uiParserExp.default); + const uiParserPath = path.resolve(packageDir, uiParserFile); + + if (!uiParserPath.startsWith(packageDir + path.sep) && uiParserPath !== packageDir) { + logger.warn( + { packageName, uiParserFile }, + "UI parser path escapes package directory — skipping", + ); + return undefined; + } + + if (!fs.existsSync(uiParserPath)) { + return undefined; + } + + try { + const source = fs.readFileSync(uiParserPath, "utf-8"); + logger.info( + { packageName, uiParserFile, size: source.length }, + `Loaded UI parser from adapter package${contractVersion ? "" : " (no version declared)"}`, + ); + return source; + } catch (err) { + logger.warn({ err, packageName, uiParserFile }, "Failed to read UI parser from adapter package"); + return undefined; + } +} + +// --------------------------------------------------------------------------- +// Load / reload +// --------------------------------------------------------------------------- + +function validateAdapterModule(mod: unknown, packageName: string): ServerAdapterModule { + const m = mod as Record; + const createServerAdapter = m.createServerAdapter; + if (typeof createServerAdapter !== "function") { + throw new Error( + `Package "${packageName}" does not export createServerAdapter(). ` + + `Ensure the package's main entry exports a createServerAdapter function.`, + ); + } + + const adapterModule = createServerAdapter() as ServerAdapterModule; + if (!adapterModule || !adapterModule.type) { + throw new Error( + `createServerAdapter() from "${packageName}" returned an invalid module (missing "type").`, + ); + } + return adapterModule; +} + +export async function loadExternalAdapterPackage( + packageName: string, + localPath?: string, +): Promise { + const packageDir = localPath + ? path.resolve(localPath) + : path.resolve(getAdapterPluginsDir(), "node_modules", packageName); + + const entryPoint = resolvePackageEntryPoint(packageDir); + const modulePath = path.resolve(packageDir, entryPoint); + const uiParserSource = extractUiParserSource(packageDir, packageName); + + logger.info({ packageName, packageDir, entryPoint, modulePath, hasUiParser: !!uiParserSource }, "Loading external adapter package"); + + const mod = await import(modulePath); + const adapterModule = validateAdapterModule(mod, packageName); + + if (uiParserSource) { + uiParserCache.set(adapterModule.type, uiParserSource); + } + + return adapterModule; +} + +async function loadFromRecord(record: AdapterPluginRecord): Promise { + try { + return await loadExternalAdapterPackage(record.packageName, record.localPath); + } catch (err) { + logger.warn( + { err, packageName: record.packageName, type: record.type }, + "Failed to dynamically load external adapter; skipping", + ); + return null; + } +} + +/** + * Reload an external adapter at runtime (dev iteration without server restart). + * Busts the ESM module cache via a cache-busting query string. + */ +export async function reloadExternalAdapter( + type: string, +): Promise { + const record = getAdapterPluginByType(type); + if (!record) return null; + + const packageDir = resolvePackageDir(record); + const entryPoint = resolvePackageEntryPoint(packageDir); + const modulePath = path.resolve(packageDir, entryPoint); + const fileUrl = `file://${modulePath}`; + + // Bust ESM module cache so re-import loads fresh code from disk. + // Query-string trick (?t=...) works in Node; Bun may need the file:// URL + // to be evicted from its internal registry first. + try { + // @ts-expect-error -- Bun internal module cache + const bunCache = globalThis.Bun?.__moduleCache as Map | undefined; + if (bunCache) { + bunCache.delete(fileUrl); + bunCache.delete(modulePath); + } + } catch { + // Ignore — query-string fallback still works in Node + } + + const cacheBustUrl = `${fileUrl}?t=${Date.now()}`; + + logger.info( + { type, packageName: record.packageName, modulePath, cacheBustUrl }, + "Reloading external adapter (cache bust)", + ); + + const mod = await import(cacheBustUrl); + const adapterModule = validateAdapterModule(mod, record.packageName); + + uiParserCache.delete(type); + const uiParserSource = extractUiParserSource(packageDir, record.packageName); + if (uiParserSource) { + uiParserCache.set(adapterModule.type, uiParserSource); + } + + logger.info( + { type, packageName: record.packageName, hasUiParser: !!uiParserSource }, + "Successfully reloaded external adapter", + ); + + return adapterModule; +} + +/** + * Build all external adapter modules from the plugin store. + */ +export async function buildExternalAdapters(): Promise { + const results: ServerAdapterModule[] = []; + + const storeRecords = listAdapterPlugins(); + for (const record of storeRecords) { + const adapter = await loadFromRecord(record); + if (adapter) { + results.push(adapter); + } + } + + if (results.length > 0) { + logger.info( + { count: results.length, adapters: results.map((a) => a.type) }, + "Loaded external adapters from plugin store", + ); + } + + return results; +} diff --git a/server/src/adapters/registry.ts b/server/src/adapters/registry.ts index 3db7cdf9ee3..f674622721b 100644 --- a/server/src/adapters/registry.ts +++ b/server/src/adapters/registry.ts @@ -4,6 +4,7 @@ import { execute as claudeExecute, listClaudeSkills, syncClaudeSkills, + listClaudeModels, testEnvironment as claudeTestEnvironment, sessionCodec as claudeSessionCodec, getQuotaWindows as claudeGetQuotaWindows, @@ -44,6 +45,7 @@ import { } from "@paperclipai/adapter-opencode-local/server"; import { agentConfigurationDoc as openCodeAgentConfigurationDoc, + models as openCodeModels, } from "@paperclipai/adapter-opencode-local"; import { execute as openclawGatewayExecute, @@ -78,6 +80,9 @@ import { agentConfigurationDoc as hermesAgentConfigurationDoc, models as hermesModels, } from "hermes-paperclip-adapter"; +import { BUILTIN_ADAPTER_TYPES } from "./builtin-adapter-types.js"; +import { buildExternalAdapters } from "./plugin-loader.js"; +import { getDisabledAdapterTypes } from "../services/adapter-plugin-store.js"; import { processAdapter } from "./process/index.js"; import { httpAdapter } from "./http/index.js"; @@ -90,6 +95,7 @@ const claudeLocalAdapter: ServerAdapterModule = { sessionCodec: claudeSessionCodec, sessionManagement: getAdapterSessionManagement("claude_local") ?? undefined, models: claudeModels, + listModels: listClaudeModels, supportsLocalAgentJwt: true, agentConfigurationDoc: claudeAgentConfigurationDoc, getQuotaWindows: claudeGetQuotaWindows, @@ -153,8 +159,8 @@ const openCodeLocalAdapter: ServerAdapterModule = { listSkills: listOpenCodeSkills, syncSkills: syncOpenCodeSkills, sessionCodec: openCodeSessionCodec, + models: openCodeModels, sessionManagement: getAdapterSessionManagement("opencode_local") ?? undefined, - models: [], listModels: listOpenCodeModels, supportsLocalAgentJwt: true, agentConfigurationDoc: openCodeAgentConfigurationDoc, @@ -187,8 +193,19 @@ const hermesLocalAdapter: ServerAdapterModule = { detectModel: () => detectModelFromHermes(), }; -const adaptersByType = new Map( - [ +const adaptersByType = new Map(); + +// For builtin types that are overridden by an external adapter, we keep the +// original builtin so it can be restored when the override is deactivated. +const builtinFallbacks = new Map(); + +// Tracks which override types are currently deactivated (paused). When +// paused, `getServerAdapter()` returns the builtin fallback instead of the +// external. Persisted across reloads via the same disabled-adapters store. +const pausedOverrides = new Set(); + +function registerBuiltInAdapters() { + for (const adapter of [ claudeLocalAdapter, codexLocalAdapter, openCodeLocalAdapter, @@ -199,20 +216,109 @@ const adaptersByType = new Map( hermesLocalAdapter, processAdapter, httpAdapter, - ].map((a) => [a.type, a]), -); + ]) { + adaptersByType.set(adapter.type, adapter); + } +} -export function getServerAdapter(type: string): ServerAdapterModule { - const adapter = adaptersByType.get(type); +registerBuiltInAdapters(); + +// --------------------------------------------------------------------------- +// Load external adapter plugins (e.g. droid_local) +// +// External adapter packages export createServerAdapter() which returns a +// ServerAdapterModule. The host fills in sessionManagement. +// --------------------------------------------------------------------------- + +/** Cached sync wrapper — the store is a simple JSON file read, safe to call frequently. */ +function getDisabledAdapterTypesFromStore(): string[] { + return getDisabledAdapterTypes(); +} + +/** + * Load external adapters from the plugin store and hardcoded sources. + * Called once at module initialization. The promise is exported so that + * callers (e.g. assertKnownAdapterType, app startup) can await completion + * and avoid racing against the loading window. + */ +const externalAdaptersReady: Promise = (async () => { + try { + const externalAdapters = await buildExternalAdapters(); + for (const externalAdapter of externalAdapters) { + const overriding = BUILTIN_ADAPTER_TYPES.has(externalAdapter.type); + if (overriding) { + console.log( + `[paperclip] External adapter "${externalAdapter.type}" overrides built-in adapter`, + ); + // Save the original builtin for later restoration. + const existing = adaptersByType.get(externalAdapter.type); + if (existing && !builtinFallbacks.has(externalAdapter.type)) { + builtinFallbacks.set(externalAdapter.type, existing); + } + } + adaptersByType.set( + externalAdapter.type, + { + ...externalAdapter, + sessionManagement: getAdapterSessionManagement(externalAdapter.type) ?? undefined, + }, + ); + } + } catch (err) { + console.error("[paperclip] Failed to load external adapters:", err); + } +})(); + +/** + * Await this before validating adapter types to avoid race conditions + * during server startup. External adapters are loaded asynchronously; + * calling assertKnownAdapterType before this resolves will reject + * valid external adapter types. + */ +export function waitForExternalAdapters(): Promise { + return externalAdaptersReady; +} + +export function registerServerAdapter(adapter: ServerAdapterModule): void { + if (BUILTIN_ADAPTER_TYPES.has(adapter.type) && !builtinFallbacks.has(adapter.type)) { + const existing = adaptersByType.get(adapter.type); + if (existing) { + builtinFallbacks.set(adapter.type, existing); + } + } + adaptersByType.set(adapter.type, adapter); +} + +export function unregisterServerAdapter(type: string): void { + if (type === processAdapter.type || type === httpAdapter.type) return; + if (builtinFallbacks.has(type)) { + pausedOverrides.delete(type); + const fallback = builtinFallbacks.get(type); + if (fallback) { + adaptersByType.set(type, fallback); + } + return; + } + if (BUILTIN_ADAPTER_TYPES.has(type)) { + return; + } + adaptersByType.delete(type); +} + +export function requireServerAdapter(type: string): ServerAdapterModule { + const adapter = findActiveServerAdapter(type); if (!adapter) { - // Fall back to process adapter for unknown types - return processAdapter; + throw new Error(`Unknown adapter type: ${type}`); } return adapter; } +export function getServerAdapter(type: string): ServerAdapterModule { + return findActiveServerAdapter(type) ?? processAdapter; +} + export async function listAdapterModels(type: string): Promise<{ id: string; label: string }[]> { - const adapter = adaptersByType.get(type); + const adapter = findActiveServerAdapter(type); if (!adapter) return []; if (adapter.listModels) { const discovered = await adapter.listModels(); @@ -225,15 +331,85 @@ export function listServerAdapters(): ServerAdapterModule[] { return Array.from(adaptersByType.values()); } +/** + * List adapters excluding those that are disabled in settings. + * Used for menus and agent creation flows — disabled adapters remain + * functional for existing agents but hidden from selection. + */ +export function listEnabledServerAdapters(): ServerAdapterModule[] { + const disabled = getDisabledAdapterTypesFromStore(); + const disabledSet = disabled.length > 0 ? new Set(disabled) : null; + return disabledSet + ? Array.from(adaptersByType.values()).filter((a) => !disabledSet.has(a.type)) + : Array.from(adaptersByType.values()); +} + export async function detectAdapterModel( type: string, -): Promise<{ model: string; provider: string; source: string } | null> { - const adapter = adaptersByType.get(type); +): Promise<{ model: string; provider: string; source: string; candidates?: string[] } | null> { + const adapter = findActiveServerAdapter(type); if (!adapter?.detectModel) return null; const detected = await adapter.detectModel(); - return detected ? { model: detected.model, provider: detected.provider, source: detected.source } : null; + if (!detected) return null; + return { + model: detected.model, + provider: detected.provider, + source: detected.source, + ...(detected.candidates?.length ? { candidates: detected.candidates } : {}), + }; +} + +// --------------------------------------------------------------------------- +// Override pause / resume +// --------------------------------------------------------------------------- + +/** + * Pause or resume an external override for a builtin adapter type. + * + * - `paused = true` → subsequent calls to `getServerAdapter(type)` return + * the builtin fallback instead of the external adapter. Already-running + * agent sessions are unaffected (they hold a reference to the module they + * started with). + * + * - `paused = false` → the external adapter is active again. + * + * Returns `true` if the state actually changed, `false` if the type is not + * an override or was already in the requested state. + */ +export function setOverridePaused(type: string, paused: boolean): boolean { + if (!builtinFallbacks.has(type)) return false; + const wasPaused = pausedOverrides.has(type); + if (paused && !wasPaused) { + pausedOverrides.add(type); + console.log(`[paperclip] Override paused for "${type}" — builtin adapter restored`); + return true; + } + if (!paused && wasPaused) { + pausedOverrides.delete(type); + console.log(`[paperclip] Override resumed for "${type}" — external adapter active`); + return true; + } + return false; +} + +/** Check whether the external override for a builtin type is currently paused. */ +export function isOverridePaused(type: string): boolean { + return pausedOverrides.has(type); +} + +/** Get the set of types whose overrides are currently paused. */ +export function getPausedOverrides(): Set { + return pausedOverrides; } export function findServerAdapter(type: string): ServerAdapterModule | null { return adaptersByType.get(type) ?? null; } + +export function findActiveServerAdapter(type: string): ServerAdapterModule | null { + if (pausedOverrides.has(type)) { + const fallback = builtinFallbacks.get(type); + if (fallback) return fallback; + } + return adaptersByType.get(type) ?? null; +} diff --git a/server/src/adapters/types.ts b/server/src/adapters/types.ts index 7df5474150b..b8f32568e5c 100644 --- a/server/src/adapters/types.ts +++ b/server/src/adapters/types.ts @@ -25,5 +25,8 @@ export type { NativeContextManagement, ResolvedSessionCompactionPolicy, SessionCompactionPolicy, + ConfigFieldOption, + ConfigFieldSchema, + AdapterConfigSchema, ServerAdapterModule, } from "@paperclipai/adapter-utils"; diff --git a/server/src/agent-auth-jwt.ts b/server/src/agent-auth-jwt.ts index 6ec696b9f4b..728461b090a 100644 --- a/server/src/agent-auth-jwt.ts +++ b/server/src/agent-auth-jwt.ts @@ -26,7 +26,7 @@ function parseNumber(value: string | undefined, fallback: number) { } function jwtConfig() { - const secret = process.env.PAPERCLIP_AGENT_JWT_SECRET; + const secret = process.env.PAPERCLIP_AGENT_JWT_SECRET?.trim() || process.env.BETTER_AUTH_SECRET?.trim(); if (!secret) return null; return { diff --git a/server/src/app.ts b/server/src/app.ts index 2ef02d175ac..618e6a122d2 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -24,11 +24,13 @@ import { costRoutes } from "./routes/costs.js"; import { activityRoutes } from "./routes/activity.js"; import { dashboardRoutes } from "./routes/dashboard.js"; import { sidebarBadgeRoutes } from "./routes/sidebar-badges.js"; +import { inboxDismissalRoutes } from "./routes/inbox-dismissals.js"; import { instanceSettingsRoutes } from "./routes/instance-settings.js"; import { llmRoutes } from "./routes/llms.js"; import { assetRoutes } from "./routes/assets.js"; import { accessRoutes } from "./routes/access.js"; import { pluginRoutes } from "./routes/plugins.js"; +import { adapterRoutes } from "./routes/adapters.js"; import { pluginUiStaticRoutes } from "./routes/plugin-ui-static.js"; import { applyUiBranding } from "./ui-branding.js"; import { logger } from "./middleware/logger.js"; @@ -49,6 +51,7 @@ import { createHostClientHandlers } from "@paperclipai/plugin-sdk"; import type { BetterAuthSessionResult } from "./auth/better-auth.js"; type UiMode = "none" | "static" | "vite-dev"; +const FEEDBACK_EXPORT_FLUSH_INTERVAL_MS = 5_000; export function resolveViteHmrPort(serverPort: number): number { if (serverPort <= 55_535) { @@ -63,6 +66,14 @@ export async function createApp( uiMode: UiMode; serverPort: number; storageService: StorageService; + feedbackExportService?: { + flushPendingFeedbackTraces(input?: { + companyId?: string; + traceId?: string; + limit?: number; + now?: Date; + }): Promise; + }; deploymentMode: DeploymentMode; deploymentExposure: DeploymentExposure; allowedHostnames: string[]; @@ -123,7 +134,7 @@ export async function createApp( }); }); if (opts.betterAuthHandler) { - app.all("/api/auth/*authPath", opts.betterAuthHandler); + app.all("/api/auth/{*authPath}", opts.betterAuthHandler); } app.use(llmRoutes(db)); @@ -144,7 +155,9 @@ export async function createApp( api.use(agentRoutes(db)); api.use(assetRoutes(db, opts.storageService)); api.use(projectRoutes(db)); - api.use(issueRoutes(db, opts.storageService)); + api.use(issueRoutes(db, opts.storageService, { + feedbackExportService: opts.feedbackExportService, + })); api.use(routineRoutes(db)); api.use(executionWorkspaceRoutes(db)); api.use(goalRoutes(db)); @@ -154,6 +167,7 @@ export async function createApp( api.use(activityRoutes(db)); api.use(dashboardRoutes(db)); api.use(sidebarBadgeRoutes(db)); + api.use(inboxDismissalRoutes(db)); api.use(instanceSettingsRoutes(db)); const hostServicesDisposers = new Map void>(); const workerManager = createPluginWorkerManager(); @@ -218,6 +232,7 @@ export async function createApp( { workerManager }, ), ); + api.use(adapterRoutes()); api.use( accessRoutes(db, { deploymentMode: opts.deploymentMode, @@ -288,6 +303,19 @@ export async function createApp( jobCoordinator.start(); scheduler.start(); + const feedbackExportTimer = opts.feedbackExportService + ? setInterval(() => { + void opts.feedbackExportService?.flushPendingFeedbackTraces().catch((err) => { + logger.error({ err }, "Failed to flush pending feedback exports"); + }); + }, FEEDBACK_EXPORT_FLUSH_INTERVAL_MS) + : null; + feedbackExportTimer?.unref?.(); + if (opts.feedbackExportService) { + void opts.feedbackExportService.flushPendingFeedbackTraces().catch((err) => { + logger.error({ err }, "Failed to flush pending feedback exports"); + }); + } void toolDispatcher.initialize().catch((err) => { logger.error({ err }, "Failed to initialize plugin tool dispatcher"); }); @@ -308,6 +336,7 @@ export async function createApp( logger.error({ err }, "Failed to load ready plugins on startup"); }); process.once("exit", () => { + if (feedbackExportTimer) clearInterval(feedbackExportTimer); devWatcher?.close(); hostServiceCleanup.disposeAll(); hostServiceCleanup.teardown(); diff --git a/server/src/attachment-types.ts b/server/src/attachment-types.ts index b9349179d26..9c02b86ddd6 100644 --- a/server/src/attachment-types.ts +++ b/server/src/attachment-types.ts @@ -1,10 +1,10 @@ /** * Shared attachment content-type configuration. * - * By default only image types are allowed. Set the + * By default a curated set of image/document/text types are allowed. Set the * `PAPERCLIP_ALLOWED_ATTACHMENT_TYPES` environment variable to a * comma-separated list of MIME types or wildcard patterns to expand the - * allowed set. + * allowed set for routes that use this allowlist. * * Examples: * PAPERCLIP_ALLOWED_ATTACHMENT_TYPES=image/*,application/pdf @@ -29,6 +29,17 @@ export const DEFAULT_ALLOWED_TYPES: readonly string[] = [ "text/html", ]; +export const DEFAULT_ATTACHMENT_CONTENT_TYPE = "application/octet-stream"; +export const SVG_CONTENT_TYPE = "image/svg+xml"; +export const INLINE_ATTACHMENT_TYPES: readonly string[] = [ + "image/*", + "application/pdf", + "text/plain", + "text/markdown", + "application/json", + "text/csv", +]; + /** * Parse a comma-separated list of MIME type patterns into a normalised array. * Returns the default image-only list when the input is empty or undefined. @@ -59,6 +70,15 @@ export function matchesContentType(contentType: string, allowedPatterns: string[ }); } +export function normalizeContentType(contentType: string | null | undefined): string { + const normalized = (contentType ?? "").trim().toLowerCase(); + return normalized || DEFAULT_ATTACHMENT_CONTENT_TYPE; +} + +export function isInlineAttachmentContentType(contentType: string): boolean { + return matchesContentType(contentType, [...INLINE_ATTACHMENT_TYPES]); +} + // ---------- Module-level singletons read once at startup ---------- const allowedPatterns: string[] = parseAllowedTypes( diff --git a/server/src/auth/better-auth.ts b/server/src/auth/better-auth.ts index d338eeb8187..881c3072ca5 100644 --- a/server/src/auth/better-auth.ts +++ b/server/src/auth/better-auth.ts @@ -67,7 +67,13 @@ export function deriveAuthTrustedOrigins(config: Config): string[] { export function createBetterAuthInstance(db: Db, config: Config, trustedOrigins?: string[]): BetterAuthInstance { const baseUrl = config.authBaseUrlMode === "explicit" ? config.authPublicBaseUrl : undefined; - const secret = process.env.BETTER_AUTH_SECRET ?? process.env.PAPERCLIP_AGENT_JWT_SECRET ?? "paperclip-dev-secret"; + const secret = process.env.BETTER_AUTH_SECRET ?? process.env.PAPERCLIP_AGENT_JWT_SECRET; + if (!secret) { + throw new Error( + "BETTER_AUTH_SECRET (or PAPERCLIP_AGENT_JWT_SECRET) must be set. " + + "For local development, set BETTER_AUTH_SECRET=paperclip-dev-secret in your .env file.", + ); + } const effectiveTrustedOrigins = trustedOrigins ?? deriveAuthTrustedOrigins(config); const publicUrl = process.env.PAPERCLIP_PUBLIC_URL ?? baseUrl; diff --git a/server/src/config.ts b/server/src/config.ts index 4a1cc17b2fa..71084cc097d 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -70,9 +70,12 @@ export interface Config { storageS3Endpoint: string | undefined; storageS3Prefix: string; storageS3ForcePathStyle: boolean; + feedbackExportBackendUrl: string | undefined; + feedbackExportBackendToken: string | undefined; heartbeatSchedulerEnabled: boolean; heartbeatSchedulerIntervalMs: number; companyDeletionEnabled: boolean; + telemetryEnabled: boolean; } export function loadConfig(): Config { @@ -120,6 +123,14 @@ export function loadConfig(): Config { process.env.PAPERCLIP_STORAGE_S3_FORCE_PATH_STYLE !== undefined ? process.env.PAPERCLIP_STORAGE_S3_FORCE_PATH_STYLE === "true" : (fileStorage?.s3?.forcePathStyle ?? false); + const feedbackExportBackendUrl = + process.env.PAPERCLIP_FEEDBACK_EXPORT_BACKEND_URL?.trim() || + process.env.PAPERCLIP_TELEMETRY_BACKEND_URL?.trim() || + undefined; + const feedbackExportBackendToken = + process.env.PAPERCLIP_FEEDBACK_EXPORT_BACKEND_TOKEN?.trim() || + process.env.PAPERCLIP_TELEMETRY_BACKEND_TOKEN?.trim() || + undefined; const deploymentModeFromEnvRaw = process.env.PAPERCLIP_DEPLOYMENT_MODE; const deploymentModeFromEnv = @@ -252,8 +263,11 @@ export function loadConfig(): Config { storageS3Endpoint, storageS3Prefix, storageS3ForcePathStyle, + feedbackExportBackendUrl, + feedbackExportBackendToken, heartbeatSchedulerEnabled: process.env.HEARTBEAT_SCHEDULER_ENABLED !== "false", heartbeatSchedulerIntervalMs: Math.max(10000, Number(process.env.HEARTBEAT_SCHEDULER_INTERVAL_MS) || 30000), companyDeletionEnabled, + telemetryEnabled: fileConfig?.telemetry?.enabled ?? true, }; } diff --git a/server/src/dev-server-status.ts b/server/src/dev-server-status.ts index aecb0fc985a..ec78bfe8c4e 100644 --- a/server/src/dev-server-status.ts +++ b/server/src/dev-server-status.ts @@ -1,4 +1,6 @@ -import { existsSync, readFileSync } from "node:fs"; +import { existsSync, readFileSync, statSync } from "node:fs"; + +const MAX_PERSISTED_DEV_SERVER_STATUS_BYTES = 64 * 1024; export type PersistedDevServerStatus = { dirty: boolean; @@ -44,6 +46,9 @@ export function readPersistedDevServerStatus( if (!filePath || !existsSync(filePath)) return null; try { + if (statSync(filePath).size > MAX_PERSISTED_DEV_SERVER_STATUS_BYTES) { + return null; + } const raw = JSON.parse(readFileSync(filePath, "utf8")) as Record; const changedPathsSample = normalizeStringArray(raw.changedPathsSample).slice(0, 5); const pendingMigrations = normalizeStringArray(raw.pendingMigrations); diff --git a/server/src/dev-watch-ignore.ts b/server/src/dev-watch-ignore.ts index cd618f73918..4fe3769d165 100644 --- a/server/src/dev-watch-ignore.ts +++ b/server/src/dev-watch-ignore.ts @@ -28,6 +28,9 @@ export function resolveServerDevWatchIgnorePaths(serverRoot: string): string[] { "../ui/node_modules/.vite-temp", "../ui/.vite", "../ui/dist", + // npm install during reinstall would trigger a restart mid-request + // if tsx watch sees the new files. Exclude the managed plugins dir. + process.env.HOME + "/.paperclip/adapter-plugins", ]) { addIgnorePath(ignorePaths, path.resolve(serverRoot, relativePath)); } diff --git a/server/src/index.ts b/server/src/index.ts index 7ebfa7d1c5e..955aaa16193 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -28,11 +28,18 @@ import { createApp } from "./app.js"; import { loadConfig } from "./config.js"; import { logger } from "./middleware/logger.js"; import { setupLiveEventsWebSocketServer } from "./realtime/live-events-ws.js"; -import { heartbeatService, reconcilePersistedRuntimeServicesOnStartup, routineService } from "./services/index.js"; +import { + feedbackService, + heartbeatService, + reconcilePersistedRuntimeServicesOnStartup, + routineService, +} from "./services/index.js"; +import { createFeedbackTraceShareClientFromConfig } from "./services/feedback-share-client.js"; import { createStorageServiceFromConfig } from "./storage/index.js"; import { printStartupBanner } from "./startup-banner.js"; import { getBoardClaimWarningUrl, initializeBoardClaimChallenge } from "./board-claim.js"; import { maybePersistWorktreeRuntimePorts } from "./worktree-config.js"; +import { initTelemetry, getTelemetryClient } from "./telemetry.js"; type BetterAuthSessionUser = { id: string; @@ -73,6 +80,7 @@ export interface StartedServer { export async function startServer(): Promise { let config = loadConfig(); + initTelemetry({ enabled: config.telemetryEnabled }); if (process.env.PAPERCLIP_SECRETS_PROVIDER === undefined) { process.env.PAPERCLIP_SECRETS_PROVIDER = config.secretsProvider; } @@ -467,13 +475,6 @@ export async function startServer(): Promise { resolveBetterAuthSession, resolveBetterAuthSessionFromHeaders, } = await import("./auth/better-auth.js"); - const betterAuthSecret = - process.env.BETTER_AUTH_SECRET?.trim() ?? process.env.PAPERCLIP_AGENT_JWT_SECRET?.trim(); - if (!betterAuthSecret) { - throw new Error( - "authenticated mode requires BETTER_AUTH_SECRET (or PAPERCLIP_AGENT_JWT_SECRET) to be set", - ); - } const derivedTrustedOrigins = deriveAuthTrustedOrigins(config); const envTrustedOrigins = (process.env.BETTER_AUTH_TRUSTED_ORIGINS ?? "") .split(",") @@ -516,10 +517,14 @@ export async function startServer(): Promise { }); const uiMode = config.uiDevMiddleware ? "vite-dev" : config.serveUi ? "static" : "none"; const storageService = createStorageServiceFromConfig(config); + const feedback = feedbackService(db as any, { + shareClient: createFeedbackTraceShareClientFromConfig(config), + }); const app = await createApp(db as any, { uiMode, serverPort: listenPort, storageService, + feedbackExportService: feedback, deploymentMode: config.deploymentMode, deploymentExposure: config.deploymentExposure, allowedHostnames: config.allowedHostnames, @@ -530,6 +535,12 @@ export async function startServer(): Promise { resolveSession, }); const server = createServer(app as unknown as Parameters[0]); + + // Increase keep-alive timeouts to safely outlive default idle timeouts + // of common reverse proxies and load balancers (like AWS ALB, Nginx, or Traefik). + // This prevents intermittent 502/ECONNRESET errors caused by Node's 5s default. + server.keepAliveTimeout = 185000; + server.headersTimeout = 186000; if (listenPort !== config.port) { logger.warn(`Requested port is busy; using next free port (requestedPort=${config.port}, selectedPort=${listenPort})`); @@ -656,6 +667,12 @@ export async function startServer(): Promise { }, backupIntervalMs); } + // Wait for external adapters to finish loading before accepting requests. + // Without this, adapter type validation (assertKnownAdapterType) would + // reject valid external adapter types during the startup loading window. + const { waitForExternalAdapters } = await import("./adapters/registry.js"); + await waitForExternalAdapters(); + await new Promise((resolveListen, rejectListen) => { const onError = (err: Error) => { server.off("error", onError); @@ -716,18 +733,26 @@ export async function startServer(): Promise { }); }); - if (embeddedPostgres && embeddedPostgresStartedByThisProcess) { + { const shutdown = async (signal: "SIGINT" | "SIGTERM") => { - logger.info({ signal }, "Stopping embedded PostgreSQL"); - try { - await embeddedPostgres?.stop(); - } catch (err) { - logger.error({ err }, "Failed to stop embedded PostgreSQL cleanly"); - } finally { - process.exit(0); + const telemetryClient = getTelemetryClient(); + if (telemetryClient) { + telemetryClient.stop(); + await telemetryClient.flush(); } + + if (embeddedPostgres && embeddedPostgresStartedByThisProcess) { + logger.info({ signal }, "Stopping embedded PostgreSQL"); + try { + await embeddedPostgres?.stop(); + } catch (err) { + logger.error({ err }, "Failed to stop embedded PostgreSQL cleanly"); + } + } + + process.exit(0); }; - + process.once("SIGINT", () => { void shutdown("SIGINT"); }); diff --git a/server/src/middleware/board-mutation-guard.ts b/server/src/middleware/board-mutation-guard.ts index de66a4ce97a..feff3b409a0 100644 --- a/server/src/middleware/board-mutation-guard.ts +++ b/server/src/middleware/board-mutation-guard.ts @@ -18,7 +18,8 @@ function parseOrigin(value: string | undefined) { function trustedOriginsForRequest(req: Request) { const origins = new Set(DEFAULT_DEV_ORIGINS.map((value) => value.toLowerCase())); - const host = req.header("host")?.trim(); + const forwardedHost = req.header("x-forwarded-host")?.split(",")[0]?.trim(); + const host = forwardedHost || req.header("host")?.trim(); if (host) { origins.add(`http://${host}`.toLowerCase()); origins.add(`https://${host}`.toLowerCase()); diff --git a/server/src/middleware/error-handler.ts b/server/src/middleware/error-handler.ts index 7f86dfd08ea..032b5e1f19e 100644 --- a/server/src/middleware/error-handler.ts +++ b/server/src/middleware/error-handler.ts @@ -1,6 +1,8 @@ import type { Request, Response, NextFunction } from "express"; import { ZodError } from "zod"; import { HttpError } from "../errors.js"; +import { trackErrorHandlerCrash } from "@paperclipai/shared/telemetry"; +import { getTelemetryClient } from "../telemetry.js"; export interface ErrorContext { error: { message: string; stack?: string; name?: string; details?: unknown; raw?: unknown }; @@ -44,6 +46,8 @@ export function errorHandler( { message: err.message, stack: err.stack, name: err.name, details: err.details }, err, ); + const tc = getTelemetryClient(); + if (tc) trackErrorHandlerCrash(tc, { errorCode: err.name }); } res.status(err.status).json({ error: err.message, @@ -67,5 +71,8 @@ export function errorHandler( rootError, ); + const tc = getTelemetryClient(); + if (tc) trackErrorHandlerCrash(tc, { errorCode: rootError.name }); + res.status(500).json({ error: "Internal server error" }); } diff --git a/server/src/middleware/logger.ts b/server/src/middleware/logger.ts index b268ebf39c1..7844e567368 100644 --- a/server/src/middleware/logger.ts +++ b/server/src/middleware/logger.ts @@ -21,13 +21,14 @@ fs.mkdirSync(logDir, { recursive: true }); const logFile = path.join(logDir, "server.log"); const sharedOpts = { - translateTime: "HH:MM:ss", + translateTime: "SYS:HH:MM:ss", ignore: "pid,hostname", singleLine: true, }; export const logger = pino({ level: "debug", + redact: ["req.headers.authorization"], }, pino.transport({ targets: [ { diff --git a/server/src/onboarding-assets/ceo/AGENTS.md b/server/src/onboarding-assets/ceo/AGENTS.md index c9aee7d4235..b88e7758bc9 100644 --- a/server/src/onboarding-assets/ceo/AGENTS.md +++ b/server/src/onboarding-assets/ceo/AGENTS.md @@ -1,6 +1,6 @@ You are the CEO. Your job is to lead the company, not to do individual contributor work. You own strategy, prioritization, and cross-functional coordination. -Your home directory is $AGENT_HOME. Everything personal to you -- life, memory, knowledge -- lives there. Other agents may have their own folders and you may update them when necessary. +Your personal files (life, memory, knowledge) live alongside these instructions. Other agents may have their own folders and you may update them when necessary. Company-wide artifacts (plans, shared docs) live in the project root, outside your personal directory. @@ -49,6 +49,6 @@ Invoke it whenever you need to remember, retrieve, or organize anything. These files are essential. Read them. -- `$AGENT_HOME/HEARTBEAT.md` -- execution and extraction checklist. Run every heartbeat. -- `$AGENT_HOME/SOUL.md` -- who you are and how you should act. -- `$AGENT_HOME/TOOLS.md` -- tools you have access to +- `./HEARTBEAT.md` -- execution and extraction checklist. Run every heartbeat. +- `./SOUL.md` -- who you are and how you should act. +- `./TOOLS.md` -- tools you have access to diff --git a/server/src/onboarding-assets/ceo/HEARTBEAT.md b/server/src/onboarding-assets/ceo/HEARTBEAT.md index bc6273c73c8..8773823d216 100644 --- a/server/src/onboarding-assets/ceo/HEARTBEAT.md +++ b/server/src/onboarding-assets/ceo/HEARTBEAT.md @@ -9,7 +9,7 @@ Run this checklist on every heartbeat. This covers both your local planning/memo ## 2. Local Planning Check -1. Read today's plan from `$AGENT_HOME/memory/YYYY-MM-DD.md` under "## Today's Plan". +1. Read today's plan from `./memory/YYYY-MM-DD.md` under "## Today's Plan". 2. Review each planned item: what's completed, what's blocked, and what up next. 3. For any blockers, resolve them yourself or escalate to the board. 4. If you're ahead, start on the next highest priority. @@ -24,8 +24,8 @@ If `PAPERCLIP_APPROVAL_ID` is set: ## 4. Get Assignments -- `GET /api/companies/{companyId}/issues?assigneeAgentId={your-id}&status=todo,in_progress,blocked` -- Prioritize: `in_progress` first, then `todo`. Skip `blocked` unless you can unblock it. +- `GET /api/companies/{companyId}/issues?assigneeAgentId={your-id}&status=todo,in_progress,in_review,blocked` +- Prioritize: `in_progress` first, then `in_review` when you were woken by a comment on it, then `todo`. Skip `blocked` unless you can unblock it. - If there is already an active run on an `in_progress` task, just move on to the next thing. - If `PAPERCLIP_TASK_ID` is set and assigned to you, prioritize that task. @@ -44,8 +44,8 @@ If `PAPERCLIP_APPROVAL_ID` is set: ## 7. Fact Extraction 1. Check for new conversations since last extraction. -2. Extract durable facts to the relevant entity in `$AGENT_HOME/life/` (PARA). -3. Update `$AGENT_HOME/memory/YYYY-MM-DD.md` with timeline entries. +2. Extract durable facts to the relevant entity in `./life/` (PARA). +3. Update `./memory/YYYY-MM-DD.md` with timeline entries. 4. Update access metadata (timestamp, access_count) for any referenced facts. ## 8. Exit diff --git a/server/src/routes/adapters.ts b/server/src/routes/adapters.ts new file mode 100644 index 00000000000..27e32a062a1 --- /dev/null +++ b/server/src/routes/adapters.ts @@ -0,0 +1,643 @@ +/** + * @fileoverview Adapter management REST API routes + * + * This module provides Express routes for managing external adapter plugins: + * - Listing all registered adapters (built-in + external) + * - Installing external adapters from npm packages or local paths + * - Unregistering external adapters + * + * All routes require board-level authentication (assertBoard middleware). + * + * @module server/routes/adapters + */ + +import { execFile } from "node:child_process"; +import fs from "node:fs"; +import { readFile } from "node:fs/promises"; +import path from "node:path"; +import { promisify } from "node:util"; +import { Router } from "express"; +import { + listServerAdapters, + findServerAdapter, + findActiveServerAdapter, + listEnabledServerAdapters, + registerServerAdapter, + unregisterServerAdapter, + isOverridePaused, + setOverridePaused, +} from "../adapters/registry.js"; +import { getAdapterSessionManagement } from "@paperclipai/adapter-utils"; +import { + listAdapterPlugins, + addAdapterPlugin, + removeAdapterPlugin, + getAdapterPluginByType, + getAdapterPluginsDir, + getDisabledAdapterTypes, + setAdapterDisabled, +} from "../services/adapter-plugin-store.js"; +import type { AdapterPluginRecord } from "../services/adapter-plugin-store.js"; +import type { ServerAdapterModule, AdapterConfigSchema } from "../adapters/types.js"; +import { loadExternalAdapterPackage, getUiParserSource, getOrExtractUiParserSource, reloadExternalAdapter } from "../adapters/plugin-loader.js"; +import { logger } from "../middleware/logger.js"; +import { assertBoard } from "./authz.js"; +import { BUILTIN_ADAPTER_TYPES } from "../adapters/builtin-adapter-types.js"; + +const execFileAsync = promisify(execFile); + +// --------------------------------------------------------------------------- +// Request / Response types +// --------------------------------------------------------------------------- + +interface AdapterInstallRequest { + /** npm package name (e.g., "droid-paperclip-adapter") or local path */ + packageName: string; + /** True if packageName is a local filesystem path */ + isLocalPath?: boolean; + /** Target version for npm packages (optional, defaults to latest) */ + version?: string; +} + +interface AdapterInfo { + type: string; + label: string; + source: "builtin" | "external"; + modelsCount: number; + loaded: boolean; + disabled: boolean; + /** True when an external plugin has replaced a built-in adapter of the same type. */ + overriddenBuiltin?: boolean; + /** True when the external override for a builtin type is currently paused. */ + overridePaused?: boolean; + version?: string; + packageName?: string; + isLocalPath?: boolean; +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +/** + * Resolve the adapter package directory (same rules as plugin-loader). + */ +function resolveAdapterPackageDir(record: AdapterPluginRecord): string { + return record.localPath + ? path.resolve(record.localPath) + : path.resolve(getAdapterPluginsDir(), "node_modules", record.packageName); +} + +/** + * Read `version` from the adapter's package.json on disk. + * This is the source of truth for what is actually installed (npm or local path). + */ +function readAdapterPackageVersionFromDisk(record: AdapterPluginRecord): string | undefined { + try { + const pkgDir = resolveAdapterPackageDir(record); + const raw = fs.readFileSync(path.join(pkgDir, "package.json"), "utf-8"); + const v = JSON.parse(raw).version; + return typeof v === "string" && v.trim().length > 0 ? v.trim() : undefined; + } catch { + return undefined; + } +} + +function buildAdapterInfo(adapter: ServerAdapterModule, externalRecord: AdapterPluginRecord | undefined, disabledSet: Set): AdapterInfo { + const fromDisk = externalRecord ? readAdapterPackageVersionFromDisk(externalRecord) : undefined; + return { + type: adapter.type, + label: adapter.type, // ServerAdapterModule doesn't have a separate "label" field; type serves as label + source: externalRecord ? "external" : "builtin", + modelsCount: (adapter.models ?? []).length, + loaded: true, // If it's in the registry, it's loaded + disabled: disabledSet.has(adapter.type), + overriddenBuiltin: externalRecord ? BUILTIN_ADAPTER_TYPES.has(adapter.type) : undefined, + overridePaused: BUILTIN_ADAPTER_TYPES.has(adapter.type) ? isOverridePaused(adapter.type) : undefined, + // Prefer on-disk package.json so the UI reflects bumps without relying on store-only fields. + version: fromDisk ?? externalRecord?.version, + packageName: externalRecord?.packageName, + isLocalPath: externalRecord?.localPath ? true : undefined, + }; +} + +/** + * Normalize a local path that may be a Windows path into a WSL-compatible path. + * + * - Windows paths (e.g., "C:\\Users\\...") are converted via `wslpath -u`. + * - Paths already starting with `/mnt/` or `/` are returned as-is. + */ +async function normalizeLocalPath(rawPath: string): Promise { + // Already a POSIX path (WSL or native Linux) + if (rawPath.startsWith("/")) { + return rawPath; + } + + // Windows path detection: C:\ or C:/ pattern + if (/^[A-Za-z]:[\\/]/.test(rawPath)) { + try { + const { stdout } = await execFileAsync("wslpath", ["-u", rawPath]); + return stdout.trim(); + } catch (err) { + logger.warn({ err, rawPath }, "wslpath conversion failed; using path as-is"); + return rawPath; + } + } + + return rawPath; +} + +/** + * Register an adapter module into the server registry, filling in + * sessionManagement from the host. + */ +function registerWithSessionManagement(adapter: ServerAdapterModule): void { + const wrapped: ServerAdapterModule = { + ...adapter, + sessionManagement: getAdapterSessionManagement(adapter.type) ?? undefined, + }; + registerServerAdapter(wrapped); +} + +// --------------------------------------------------------------------------- +// Router +// --------------------------------------------------------------------------- + +export function adapterRoutes() { + const router = Router(); + + /** + * GET /api/adapters + * + * List all registered adapters (built-in + external). + * Each entry includes whether the adapter is built-in or external, + * its model count, and load status. + */ + router.get("/adapters", async (_req, res) => { + assertBoard(_req); + + const registeredAdapters = listServerAdapters(); + const externalRecords = new Map( + listAdapterPlugins().map((r) => [r.type, r]), + ); + const disabledSet = new Set(getDisabledAdapterTypes()); + + const result: AdapterInfo[] = registeredAdapters.map((adapter) => + buildAdapterInfo(adapter, externalRecords.get(adapter.type), disabledSet), + ).sort((a, b) => a.type.localeCompare(b.type)); + + res.json(result); + }); + + /** + * POST /api/adapters/install + * + * Install an external adapter from an npm package or local path. + * + * Request body: + * - packageName: string (required) — npm package name or local path + * - isLocalPath?: boolean (default false) + * - version?: string — target version for npm packages + */ + router.post("/adapters/install", async (req, res) => { + assertBoard(req); + + const { packageName, isLocalPath = false, version } = req.body as AdapterInstallRequest; + + if (!packageName || typeof packageName !== "string") { + res.status(400).json({ error: "packageName is required and must be a string." }); + return; + } + + // Strip version suffix if the UI sends "pkg@1.2.3" instead of separating it + // e.g. "@henkey/hermes-paperclip-adapter@0.3.0" → packageName + version + let canonicalName = packageName; + let explicitVersion = version; + const versionSuffix = packageName.match(/@(\d+\.\d+\.\d+.*)$/); + if (versionSuffix) { + // For scoped packages: "@scope/name@1.2.3" → "@scope/name" + "1.2.3" + // For unscoped: "name@1.2.3" → "name" + "1.2.3" + const lastAtIndex = packageName.lastIndexOf("@"); + if (lastAtIndex > 0 && !explicitVersion) { + canonicalName = packageName.slice(0, lastAtIndex); + explicitVersion = versionSuffix[1]; + } + } + + try { + let installedVersion: string | undefined; + let moduleLocalPath: string | undefined; + + if (!isLocalPath) { + // npm install into the managed directory + const pluginsDir = getAdapterPluginsDir(); + const spec = explicitVersion ? `${canonicalName}@${explicitVersion}` : canonicalName; + + logger.info({ spec, pluginsDir }, "Installing adapter package via npm"); + + await execFileAsync("npm", ["install", "--no-save", spec], { + cwd: pluginsDir, + timeout: 120_000, + }); + + // Read installed version from package.json + try { + const pkgJsonPath = path.join(pluginsDir, "node_modules", canonicalName, "package.json"); + const pkgContent = await import("node:fs/promises"); + const pkgRaw = await pkgContent.readFile(pkgJsonPath, "utf-8"); + const pkg = JSON.parse(pkgRaw); + const v = pkg.version; + installedVersion = + typeof v === "string" && v.trim().length > 0 ? v.trim() : explicitVersion; + } catch { + installedVersion = explicitVersion; + } + } else { + // Local path — normalize (e.g., Windows → WSL) and use the resolved path + moduleLocalPath = path.resolve(await normalizeLocalPath(packageName)); + try { + const pkgRaw = await readFile(path.join(moduleLocalPath, "package.json"), "utf-8"); + const v = JSON.parse(pkgRaw).version; + if (typeof v === "string" && v.trim().length > 0) { + installedVersion = v.trim(); + } + } catch { + // leave installedVersion undefined if package.json is missing + } + } + + // Load and register the adapter (use canonicalName for path resolution) + const adapterModule = await loadExternalAdapterPackage(canonicalName, moduleLocalPath); + + // Check if this type conflicts with a built-in adapter + if (BUILTIN_ADAPTER_TYPES.has(adapterModule.type)) { + res.status(409).json({ + error: `Adapter type "${adapterModule.type}" is a built-in adapter and cannot be overwritten.`, + }); + return; + } + + // Check if already registered (indicates a reinstall/update) + const existing = findServerAdapter(adapterModule.type); + const isReinstall = existing !== null; + if (existing) { + unregisterServerAdapter(adapterModule.type); + logger.info({ type: adapterModule.type }, "Unregistered existing adapter for replacement"); + } + + // Register the new adapter + registerWithSessionManagement(adapterModule); + + // Persist the record (use canonicalName without version suffix) + const record: AdapterPluginRecord = { + packageName: canonicalName, + localPath: moduleLocalPath, + version: installedVersion ?? explicitVersion, + type: adapterModule.type, + installedAt: new Date().toISOString(), + }; + addAdapterPlugin(record); + + logger.info( + { type: adapterModule.type, packageName: canonicalName }, + "External adapter installed and registered", + ); + + res.status(201).json({ + type: adapterModule.type, + packageName: canonicalName, + version: installedVersion ?? explicitVersion, + installedAt: record.installedAt, + requiresRestart: isReinstall, + }); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + logger.error({ err, packageName }, "Failed to install external adapter"); + + // Distinguish npm errors from load errors + if (message.includes("npm") || message.includes("ERR!")) { + res.status(500).json({ error: `npm install failed: ${message}` }); + } else { + res.status(500).json({ error: `Failed to install adapter: ${message}` }); + } + } + }); + + /** + * PATCH /api/adapters/:type + * + * Enable or disable an adapter. Disabled adapters are hidden from agent + * creation menus but remain functional for existing agents. + * + * Request body: { "disabled": boolean } + */ + router.patch("/adapters/:type", async (req, res) => { + assertBoard(req); + + const adapterType = req.params.type; + const { disabled } = req.body as { disabled?: boolean }; + + if (typeof disabled !== "boolean") { + res.status(400).json({ error: "Request body must include { \"disabled\": true|false }." }); + return; + } + + // Check that the adapter exists in the registry + const existing = findServerAdapter(adapterType); + if (!existing) { + res.status(404).json({ error: `Adapter "${adapterType}" is not registered.` }); + return; + } + + const changed = setAdapterDisabled(adapterType, disabled); + + if (changed) { + logger.info({ type: adapterType, disabled }, "Adapter enabled/disabled"); + } + + res.json({ type: adapterType, disabled, changed }); + }); + + /** + * PATCH /api/adapters/:type/override + * + * Pause or resume an external adapter's override of a builtin type. + * When paused, the server returns the builtin adapter for all new requests + * (execute, listModels, config schema, etc.). Already-running sessions + * keep the adapter they started with. + */ + router.patch("/adapters/:type/override", async (req, res) => { + assertBoard(req); + + const adapterType = req.params.type; + const { paused } = req.body as { paused?: boolean }; + + if (typeof paused !== "boolean") { + res.status(400).json({ error: "\"paused\" (boolean) is required in request body." }); + return; + } + + if (!BUILTIN_ADAPTER_TYPES.has(adapterType)) { + res.status(400).json({ error: `Type "${adapterType}" is not a builtin adapter.` }); + return; + } + + const changed = setOverridePaused(adapterType, paused); + + logger.info({ type: adapterType, paused, changed }, "Adapter override toggle"); + + res.json({ type: adapterType, paused, changed }); + }); + + /** + * DELETE /api/adapters/:type + * + * Unregister an external adapter. Built-in adapters cannot be removed. + */ + router.delete("/adapters/:type", async (req, res) => { + assertBoard(req); + + const adapterType = req.params.type; + + if (!adapterType) { + res.status(400).json({ error: "Adapter type is required." }); + return; + } + + // Prevent removal of built-in adapters + if (BUILTIN_ADAPTER_TYPES.has(adapterType)) { + res.status(403).json({ + error: `Cannot remove built-in adapter "${adapterType}".`, + }); + return; + } + + // Check that the adapter exists in the registry + const existing = findServerAdapter(adapterType); + if (!existing) { + res.status(404).json({ + error: `Adapter "${adapterType}" is not registered.`, + }); + return; + } + + // Check that it's an external adapter + const externalRecord = getAdapterPluginByType(adapterType); + if (!externalRecord) { + res.status(404).json({ + error: `Adapter "${adapterType}" is not an externally installed adapter.`, + }); + return; + } + + // If installed via npm (has packageName but no localPath), run npm uninstall + if (externalRecord.packageName && !externalRecord.localPath) { + try { + const pluginsDir = getAdapterPluginsDir(); + await execFileAsync("npm", ["uninstall", externalRecord.packageName], { + cwd: pluginsDir, + timeout: 60_000, + }); + logger.info( + { type: adapterType, packageName: externalRecord.packageName }, + "npm uninstall completed for external adapter", + ); + } catch (err) { + logger.warn( + { err, type: adapterType, packageName: externalRecord.packageName }, + "npm uninstall failed for external adapter; continuing with unregister", + ); + } + } + + // Unregister from the runtime registry + unregisterServerAdapter(adapterType); + + // Remove from the persistent store + removeAdapterPlugin(adapterType); + + logger.info({ type: adapterType }, "External adapter unregistered and removed"); + + res.json({ type: adapterType, removed: true }); + }); + + /** + * POST /api/adapters/:type/reload + * + * Reload an external adapter at runtime (for dev iteration without server restart). + * Busts the ESM module cache, re-imports the adapter, and re-registers it. + * + * Cannot be used on built-in adapter types. + */ + router.post("/adapters/:type/reload", async (req, res) => { + assertBoard(req); + + const type = req.params.type; + + // Built-in adapters cannot be reloaded unless overridden by an external one + if (BUILTIN_ADAPTER_TYPES.has(type) && !getAdapterPluginByType(type)) { + res.status(400).json({ error: "Cannot reload built-in adapter." }); + return; + } + + // Reload the adapter module (busts ESM cache, re-imports) + try { + const newModule = await reloadExternalAdapter(type); + + // Not found in the external adapter store + if (!newModule) { + res.status(404).json({ error: `Adapter "${type}" is not an externally installed adapter.` }); + return; + } + + // Swap in the reloaded module + unregisterServerAdapter(type); + registerWithSessionManagement(newModule); + configSchemaCache.delete(type); + + // Sync store.version from package.json (store may be missing version for local installs). + const record = getAdapterPluginByType(type); + let newVersion: string | undefined; + if (record) { + newVersion = readAdapterPackageVersionFromDisk(record); + if (newVersion) { + addAdapterPlugin({ ...record, version: newVersion }); + } + } + + logger.info({ type, version: newVersion }, "External adapter reloaded at runtime"); + + res.json({ type, version: newVersion, reloaded: true }); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + logger.error({ err, type }, "Failed to reload external adapter"); + res.status(500).json({ error: `Failed to reload adapter: ${message}` }); + } + }); + + // ── POST /api/adapters/:type/reinstall ────────────────────────────────── + // Reinstall an npm-sourced external adapter (pulls latest from registry). + // Local-path adapters cannot be reinstalled — use Reload instead. + // + // This is a convenience shortcut for remove + install with the same + // package name, but without the risk of losing the store record. + router.post("/adapters/:type/reinstall", async (req, res) => { + assertBoard(req); + + const type = req.params.type; + + if (BUILTIN_ADAPTER_TYPES.has(type) && !getAdapterPluginByType(type)) { + res.status(400).json({ error: "Cannot reinstall built-in adapter." }); + return; + } + + const record = getAdapterPluginByType(type); + if (!record) { + res.status(404).json({ error: `Adapter "${type}" is not an externally installed adapter.` }); + return; + } + + if (record.localPath) { + res.status(400).json({ error: "Local-path adapters cannot be reinstalled. Use Reload instead." }); + return; + } + + try { + const pluginsDir = getAdapterPluginsDir(); + + logger.info({ type, packageName: record.packageName }, "Reinstalling adapter package via npm"); + + await execFileAsync("npm", ["install", "--no-save", record.packageName], { + cwd: pluginsDir, + timeout: 120_000, + }); + + // Reload the freshly installed adapter + const newModule = await reloadExternalAdapter(type); + if (!newModule) { + res.status(500).json({ error: "npm install succeeded but adapter reload failed." }); + return; + } + + unregisterServerAdapter(type); + registerWithSessionManagement(newModule); + configSchemaCache.delete(type); + + // Sync store version from disk + let newVersion: string | undefined; + const updatedRecord = getAdapterPluginByType(type); + if (updatedRecord) { + newVersion = readAdapterPackageVersionFromDisk(updatedRecord); + if (newVersion) { + addAdapterPlugin({ ...updatedRecord, version: newVersion }); + } + } + + logger.info({ type, version: newVersion }, "Adapter reinstalled from npm"); + + res.json({ type, version: newVersion, reinstalled: true }); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + logger.error({ err, type }, "Failed to reinstall adapter"); + res.status(500).json({ error: `Reinstall failed: ${message}` }); + } + }); + + // ── GET /api/adapters/:type/config-schema ──────────────────────────────── + // Serve a declarative config schema for an adapter's UI form fields. + // The adapter's getConfigSchema() resolves all options (static and dynamic) + // so the UI receives a fully hydrated schema in a single fetch. + const configSchemaCache = new Map(); + const CONFIG_SCHEMA_TTL_MS = 30_000; + + router.get("/adapters/:type/config-schema", async (req, res) => { + assertBoard(req); + const { type } = req.params; + + const adapter = findActiveServerAdapter(type); + if (!adapter) { + res.status(404).json({ error: `Adapter "${type}" is not registered.` }); + return; + } + if (!adapter.getConfigSchema) { + res.status(404).json({ error: `Adapter "${type}" does not provide a config schema.` }); + return; + } + + const cached = configSchemaCache.get(type); + if (cached && Date.now() - cached.fetchedAt < CONFIG_SCHEMA_TTL_MS) { + res.json(cached.schema); + return; + } + + try { + const schema = await adapter.getConfigSchema(); + configSchemaCache.set(type, { schema, fetchedAt: Date.now() }); + res.json(schema); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + logger.error({ err, type }, "Failed to resolve config schema"); + res.status(500).json({ error: `Failed to resolve config schema: ${message}` }); + } + }); + + // ── GET /api/adapters/:type/ui-parser.js ───────────────────────────────── + // Serve the self-contained UI parser JS for an adapter type. + // This allows external adapters to provide custom run-log parsing + // without modifying Paperclip's source code. + // + // The adapter package must export a "./ui-parser" entry in package.json + // pointing to a self-contained ESM module with zero runtime dependencies. + router.get("/adapters/:type/ui-parser.js", (req, res) => { + assertBoard(req); + const { type } = req.params; + const source = getOrExtractUiParserSource(type); + if (!source) { + res.status(404).json({ error: `No UI parser available for adapter "${type}".` }); + return; + } + res.type("application/javascript").send(source); + }); + + return router; +} diff --git a/server/src/routes/agents.ts b/server/src/routes/agents.ts index 2ad85e63133..266803ecd9b 100644 --- a/server/src/routes/agents.ts +++ b/server/src/routes/agents.ts @@ -2,7 +2,7 @@ import { Router, type Request } from "express"; import { generateKeyPairSync, randomUUID } from "node:crypto"; import path from "node:path"; import type { Db } from "@paperclipai/db"; -import { agents as agentsTable, companies, heartbeatRuns } from "@paperclipai/db"; +import { agents as agentsTable, companies, heartbeatRuns, issues as issuesTable } from "@paperclipai/db"; import { and, desc, eq, inArray, not, sql } from "drizzle-orm"; import { agentSkillSyncSchema, @@ -27,6 +27,7 @@ import { readPaperclipSkillSyncPreference, writePaperclipSkillSyncPreference, } from "@paperclipai/adapter-utils/server-utils"; +import { trackAgentCreated } from "@paperclipai/shared/telemetry"; import { validate } from "../middleware/validate.js"; import { agentService, @@ -45,7 +46,13 @@ import { } from "../services/index.js"; import { conflict, forbidden, notFound, unprocessable } from "../errors.js"; import { assertBoard, assertCompanyAccess, assertInstanceAdmin, getActorInfo } from "./authz.js"; -import { findServerAdapter, listAdapterModels, detectAdapterModel } from "../adapters/index.js"; +import { + detectAdapterModel, + findActiveServerAdapter, + findServerAdapter, + listAdapterModels, + requireServerAdapter, +} from "../adapters/index.js"; import { redactEventPayload } from "../redaction.js"; import { redactCurrentUserValue } from "../log-redaction.js"; import { renderOrgChartSvg, renderOrgChartPng, type OrgNode, type OrgChartStyle, ORG_CHART_STYLES } from "./org-chart-svg.js"; @@ -62,12 +69,15 @@ import { loadDefaultAgentInstructionsBundle, resolveDefaultAgentInstructionsBundleRole, } from "../services/default-agent-instructions.js"; +import { getTelemetryClient } from "../telemetry.js"; export function agentRoutes(db: Db) { const DEFAULT_INSTRUCTIONS_PATH_KEYS: Record = { claude_local: "instructionsFilePath", codex_local: "instructionsFilePath", + droid_local: "instructionsFilePath", gemini_local: "instructionsFilePath", + hermes_local: "instructionsFilePath", opencode_local: "instructionsFilePath", cursor: "instructionsFilePath", pi_local: "instructionsFilePath", @@ -220,6 +230,73 @@ export function agentRoutes(db: Db) { return allowedByGrant || canCreateAgents(actorAgent); } + async function buildSkippedWakeupResponse( + agent: NonNullable>>, + payload: Record | null | undefined, + ) { + const issueId = typeof payload?.issueId === "string" && payload.issueId.trim() ? payload.issueId : null; + if (!issueId) { + return { + status: "skipped" as const, + reason: "wakeup_skipped", + message: "Wakeup was skipped.", + issueId: null, + executionRunId: null, + executionAgentId: null, + executionAgentName: null, + }; + } + + const issue = await db + .select({ + id: issuesTable.id, + executionRunId: issuesTable.executionRunId, + }) + .from(issuesTable) + .where(and(eq(issuesTable.id, issueId), eq(issuesTable.companyId, agent.companyId))) + .then((rows) => rows[0] ?? null); + + if (!issue?.executionRunId) { + return { + status: "skipped" as const, + reason: "wakeup_skipped", + message: "Wakeup was skipped.", + issueId, + executionRunId: null, + executionAgentId: null, + executionAgentName: null, + }; + } + + const executionRun = await heartbeat.getRun(issue.executionRunId); + if (!executionRun || (executionRun.status !== "queued" && executionRun.status !== "running")) { + return { + status: "skipped" as const, + reason: "wakeup_skipped", + message: "Wakeup was skipped.", + issueId, + executionRunId: issue.executionRunId, + executionAgentId: null, + executionAgentName: null, + }; + } + + const executionAgent = await svc.getById(executionRun.agentId); + const executionAgentName = executionAgent?.name ?? null; + + return { + status: "skipped" as const, + reason: "issue_execution_deferred", + message: executionAgentName + ? `Wakeup was deferred because this issue is already being executed by ${executionAgentName}.` + : "Wakeup was deferred because this issue already has an active execution run.", + issueId, + executionRunId: executionRun.id, + executionAgentId: executionRun.agentId, + executionAgentName, + }; + } + async function assertCanUpdateAgent(req: Request, targetAgent: { id: string; companyId: string }) { assertCompanyAccess(req, targetAgent.companyId); if (req.actor.type === "board") return; @@ -253,6 +330,21 @@ export function agentRoutes(db: Db) { } } + function assertKnownAdapterType(type: string | null | undefined): string { + const adapterType = typeof type === "string" ? type.trim() : ""; + if (!adapterType) { + throw unprocessable("Adapter type is required"); + } + if (!findServerAdapter(adapterType)) { + throw unprocessable(`Unknown adapter type: ${adapterType}`); + } + return adapterType; + } + + function hasOwn(value: object, key: string): boolean { + return Object.hasOwn(value, key); + } + async function resolveCompanyIdForAgentReference(req: Request): Promise { const companyIdQuery = req.query.companyId; const requestedCompanyId = @@ -357,11 +449,25 @@ export function agentRoutes(db: Db) { function parseSchedulerHeartbeatPolicy(runtimeConfig: unknown) { const heartbeat = asRecord(asRecord(runtimeConfig)?.heartbeat) ?? {}; return { - enabled: parseBooleanLike(heartbeat.enabled) ?? true, + enabled: parseBooleanLike(heartbeat.enabled) ?? false, intervalSec: Math.max(0, parseNumberLike(heartbeat.intervalSec) ?? 0), }; } + function normalizeNewAgentRuntimeConfig(runtimeConfig: unknown): Record { + const parsedRuntimeConfig = asRecord(runtimeConfig); + const normalizedRuntimeConfig = parsedRuntimeConfig ? { ...parsedRuntimeConfig } : {}; + const parsedHeartbeat = asRecord(normalizedRuntimeConfig.heartbeat); + const heartbeat = parsedHeartbeat ? { ...parsedHeartbeat } : {}; + + if (parseBooleanLike(heartbeat.enabled) == null) { + heartbeat.enabled = false; + } + + normalizedRuntimeConfig.heartbeat = heartbeat; + return normalizedRuntimeConfig; + } + function generateEd25519PrivateKeyPem(): string { const { privateKey } = generateKeyPairSync("ed25519"); return privateKey.export({ type: "pkcs8", format: "pem" }).toString(); @@ -532,8 +638,15 @@ export function agentRoutes(db: Db) { }; } + const ADAPTERS_REQUIRING_MATERIALIZED_RUNTIME_SKILLS = new Set([ + "cursor", + "gemini_local", + "opencode_local", + "pi_local", + ]); + function shouldMaterializeRuntimeSkillsForAdapter(adapterType: string) { - return adapterType !== "claude_local"; + return ADAPTERS_REQUIRING_MATERIALIZED_RUNTIME_SKILLS.has(adapterType); } async function buildRuntimeSkillConfig( @@ -667,7 +780,7 @@ export function agentRoutes(db: Db) { router.get("/companies/:companyId/adapters/:type/models", async (req, res) => { const companyId = req.params.companyId as string; assertCompanyAccess(req, companyId); - const type = req.params.type as string; + const type = assertKnownAdapterType(req.params.type as string); const models = await listAdapterModels(type); res.json(models); }); @@ -675,7 +788,7 @@ export function agentRoutes(db: Db) { router.get("/companies/:companyId/adapters/:type/detect-model", async (req, res) => { const companyId = req.params.companyId as string; assertCompanyAccess(req, companyId); - const type = req.params.type as string; + const type = assertKnownAdapterType(req.params.type as string); const detected = await detectAdapterModel(type); res.json(detected); @@ -686,14 +799,10 @@ export function agentRoutes(db: Db) { validate(testAdapterEnvironmentSchema), async (req, res) => { const companyId = req.params.companyId as string; - const type = req.params.type as string; + const type = assertKnownAdapterType(req.params.type as string); await assertCanReadConfigurations(req, companyId); - const adapter = findServerAdapter(type); - if (!adapter) { - res.status(404).json({ error: `Unknown adapter type: ${type}` }); - return; - } + const adapter = requireServerAdapter(type); const inputAdapterConfig = (req.body?.adapterConfig ?? {}) as Record; @@ -726,7 +835,7 @@ export function agentRoutes(db: Db) { } await assertCanReadConfigurations(req, agent.companyId); - const adapter = findServerAdapter(agent.adapterType); + const adapter = findActiveServerAdapter(agent.adapterType); if (!adapter?.listSkills) { const preference = readPaperclipSkillSyncPreference( agent.adapterConfig as Record, @@ -804,7 +913,7 @@ export function agentRoutes(db: Db) { return; } - const adapter = findServerAdapter(updated.adapterType); + const adapter = findActiveServerAdapter(updated.adapterType); const { config: runtimeConfig } = await secretsSvc.resolveAdapterConfigForRuntime( updated.companyId, updated.adapterConfig, @@ -1189,6 +1298,7 @@ export function agentRoutes(db: Db) { sourceIssueIds: _sourceIssueIds, ...hireInput } = req.body; + hireInput.adapterType = assertKnownAdapterType(hireInput.adapterType); const requestedAdapterConfig = applyCreateDefaultsByAdapterType( hireInput.adapterType, ((hireInput.adapterConfig ?? {}) as Record), @@ -1212,6 +1322,7 @@ export function agentRoutes(db: Db) { const normalizedHireInput = { ...hireInput, adapterConfig: normalizedAdapterConfig, + runtimeConfig: normalizeNewAgentRuntimeConfig(hireInput.runtimeConfig), }; const company = await db @@ -1313,6 +1424,10 @@ export function agentRoutes(db: Db) { desiredSkills: desiredSkillAssignment.desiredSkills, }, }); + const telemetryClient = getTelemetryClient(); + if (telemetryClient) { + trackAgentCreated(telemetryClient, { agentRole: agent.role }); + } await applyDefaultAgentTaskAssignGrant( companyId, @@ -1349,6 +1464,7 @@ export function agentRoutes(db: Db) { desiredSkills: requestedDesiredSkills, ...createInput } = req.body; + createInput.adapterType = assertKnownAdapterType(createInput.adapterType); const requestedAdapterConfig = applyCreateDefaultsByAdapterType( createInput.adapterType, ((createInput.adapterConfig ?? {}) as Record), @@ -1373,6 +1489,7 @@ export function agentRoutes(db: Db) { const createdAgent = await svc.create(companyId, { ...createInput, adapterConfig: normalizedAdapterConfig, + runtimeConfig: normalizeNewAgentRuntimeConfig(createInput.runtimeConfig), status: "idle", spentMonthlyCents: 0, lastHeartbeatAt: null, @@ -1395,6 +1512,10 @@ export function agentRoutes(db: Db) { desiredSkills: desiredSkillAssignment.desiredSkills, }, }); + const telemetryClient = getTelemetryClient(); + if (telemetryClient) { + trackAgentCreated(telemetryClient, { agentRole: agent.role }); + } await applyDefaultAgentTaskAssignGrant( companyId, @@ -1723,7 +1844,7 @@ export function agentRoutes(db: Db) { } await assertCanUpdateAgent(req, existing); - if (Object.prototype.hasOwnProperty.call(req.body, "permissions")) { + if (hasOwn(req.body as object, "permissions")) { res.status(422).json({ error: "Use /api/agents/:id/permissions for permission changes" }); return; } @@ -1731,7 +1852,7 @@ export function agentRoutes(db: Db) { const patchData = { ...(req.body as Record) }; const replaceAdapterConfig = patchData.replaceAdapterConfig === true; delete patchData.replaceAdapterConfig; - if (Object.prototype.hasOwnProperty.call(patchData, "adapterConfig")) { + if (hasOwn(patchData, "adapterConfig")) { const adapterConfig = asRecord(patchData.adapterConfig); if (!adapterConfig) { res.status(422).json({ error: "adapterConfig must be an object" }); @@ -1746,16 +1867,17 @@ export function agentRoutes(db: Db) { patchData.adapterConfig = adapterConfig; } - const requestedAdapterType = - typeof patchData.adapterType === "string" ? patchData.adapterType : existing.adapterType; + const requestedAdapterType = hasOwn(patchData, "adapterType") + ? assertKnownAdapterType(patchData.adapterType as string | null | undefined) + : existing.adapterType; const touchesAdapterConfiguration = - Object.prototype.hasOwnProperty.call(patchData, "adapterType") || - Object.prototype.hasOwnProperty.call(patchData, "adapterConfig"); + hasOwn(patchData, "adapterType") || + hasOwn(patchData, "adapterConfig"); if (touchesAdapterConfiguration) { const existingAdapterConfig = asRecord(existing.adapterConfig) ?? {}; const changingAdapterType = typeof patchData.adapterType === "string" && patchData.adapterType !== existing.adapterType; - const requestedAdapterConfig = Object.prototype.hasOwnProperty.call(patchData, "adapterConfig") + const requestedAdapterConfig = hasOwn(patchData, "adapterConfig") ? (asRecord(patchData.adapterConfig) ?? {}) : null; if ( @@ -1772,6 +1894,18 @@ export function agentRoutes(db: Db) { rawEffectiveAdapterConfig = { ...existingAdapterConfig, ...requestedAdapterConfig }; } if (changingAdapterType) { + // Preserve adapter-agnostic keys (env, cwd, etc.) from the existing config + // when the adapter type changes. Without this, a PATCH that includes + // adapterConfig but omits these keys would silently drop them. + const ADAPTER_AGNOSTIC_KEYS = [ + "env", "cwd", "timeoutSec", "graceSec", + "promptTemplate", "bootstrapPromptTemplate", + ] as const; + for (const key of ADAPTER_AGNOSTIC_KEYS) { + if (rawEffectiveAdapterConfig[key] === undefined && existingAdapterConfig[key] !== undefined) { + rawEffectiveAdapterConfig = { ...rawEffectiveAdapterConfig, [key]: existingAdapterConfig[key] }; + } + } rawEffectiveAdapterConfig = preserveInstructionsBundleConfig( existingAdapterConfig, rawEffectiveAdapterConfig, @@ -1982,7 +2116,7 @@ export function agentRoutes(db: Db) { }); if (!run) { - res.status(202).json({ status: "skipped" }); + res.status(202).json(await buildSkippedWakeupResponse(agent, req.body.payload ?? null)); return; } diff --git a/server/src/routes/companies.ts b/server/src/routes/companies.ts index 1a449066d53..ef2eec4fc2b 100644 --- a/server/src/routes/companies.ts +++ b/server/src/routes/companies.ts @@ -1,14 +1,18 @@ import { Router, type Request } from "express"; import type { Db } from "@paperclipai/db"; import { + DEFAULT_FEEDBACK_DATA_SHARING_TERMS_VERSION, companyPortabilityExportSchema, companyPortabilityImportSchema, companyPortabilityPreviewSchema, createCompanySchema, + feedbackTargetTypeSchema, + feedbackTraceStatusSchema, + feedbackVoteValueSchema, updateCompanyBrandingSchema, updateCompanySchema, } from "@paperclipai/shared"; -import { forbidden } from "../errors.js"; +import { badRequest, forbidden } from "../errors.js"; import { validate } from "../middleware/validate.js"; import { accessService, @@ -16,6 +20,7 @@ import { budgetService, companyPortabilityService, companyService, + feedbackService, logActivity, } from "../services/index.js"; import type { StorageService } from "../storage/types.js"; @@ -28,6 +33,20 @@ export function companyRoutes(db: Db, storage?: StorageService) { const portability = companyPortabilityService(db, storage); const access = accessService(db); const budgets = budgetService(db); + const feedback = feedbackService(db); + + function parseBooleanQuery(value: unknown) { + return value === true || value === "true" || value === "1"; + } + + function parseDateQuery(value: unknown, field: string) { + if (typeof value !== "string" || value.trim().length === 0) return undefined; + const parsed = new Date(value); + if (Number.isNaN(parsed.getTime())) { + throw badRequest(`Invalid ${field} query value`); + } + return parsed; + } async function assertCanUpdateBranding(req: Request, companyId: string) { assertCompanyAccess(req, companyId); @@ -104,6 +123,34 @@ export function companyRoutes(db: Db, storage?: StorageService) { res.json(company); }); + router.get("/:companyId/feedback-traces", async (req, res) => { + const companyId = req.params.companyId as string; + assertCompanyAccess(req, companyId); + assertBoard(req); + + const targetTypeRaw = typeof req.query.targetType === "string" ? req.query.targetType : undefined; + const voteRaw = typeof req.query.vote === "string" ? req.query.vote : undefined; + const statusRaw = typeof req.query.status === "string" ? req.query.status : undefined; + const issueId = typeof req.query.issueId === "string" && req.query.issueId.trim().length > 0 ? req.query.issueId : undefined; + const projectId = typeof req.query.projectId === "string" && req.query.projectId.trim().length > 0 + ? req.query.projectId + : undefined; + + const traces = await feedback.listFeedbackTraces({ + companyId, + issueId, + projectId, + targetType: targetTypeRaw ? feedbackTargetTypeSchema.parse(targetTypeRaw) : undefined, + vote: voteRaw ? feedbackVoteValueSchema.parse(voteRaw) : undefined, + status: statusRaw ? feedbackTraceStatusSchema.parse(statusRaw) : undefined, + from: parseDateQuery(req.query.from, "from"), + to: parseDateQuery(req.query.to, "to"), + sharedOnly: parseBooleanQuery(req.query.sharedOnly), + includePayload: parseBooleanQuery(req.query.includePayload), + }); + res.json(traces); + }); + router.post("/:companyId/export", validate(companyPortabilityExportSchema), async (req, res) => { const companyId = req.params.companyId as string; assertCompanyAccess(req, companyId); @@ -246,6 +293,11 @@ export function companyRoutes(db: Db, storage?: StorageService) { assertCompanyAccess(req, companyId); const actor = getActorInfo(req); + const existingCompany = await svc.getById(companyId); + if (!existingCompany) { + res.status(404).json({ error: "Company not found" }); + return; + } let body: Record; if (req.actor.type === "agent") { @@ -262,6 +314,18 @@ export function companyRoutes(db: Db, storage?: StorageService) { } else { assertBoard(req); body = updateCompanySchema.parse(req.body); + + if (body.feedbackDataSharingEnabled === true && !existingCompany.feedbackDataSharingEnabled) { + body = { + ...body, + feedbackDataSharingConsentAt: new Date(), + feedbackDataSharingConsentByUserId: req.actor.userId ?? "local-board", + feedbackDataSharingTermsVersion: + typeof body.feedbackDataSharingTermsVersion === "string" && body.feedbackDataSharingTermsVersion.length > 0 + ? body.feedbackDataSharingTermsVersion + : DEFAULT_FEEDBACK_DATA_SHARING_TERMS_VERSION, + }; + } } const company = await svc.update(companyId, body); diff --git a/server/src/routes/company-skills.ts b/server/src/routes/company-skills.ts index 7b239832283..9e91bf26f60 100644 --- a/server/src/routes/company-skills.ts +++ b/server/src/routes/company-skills.ts @@ -6,10 +6,20 @@ import { companySkillImportSchema, companySkillProjectScanRequestSchema, } from "@paperclipai/shared"; +import { trackSkillImported } from "@paperclipai/shared/telemetry"; import { validate } from "../middleware/validate.js"; import { accessService, agentService, companySkillService, logActivity } from "../services/index.js"; import { forbidden } from "../errors.js"; import { assertCompanyAccess, getActorInfo } from "./authz.js"; +import { getTelemetryClient } from "../telemetry.js"; + +type SkillTelemetryInput = { + key: string; + slug: string; + sourceType: string; + sourceLocator: string | null; + metadata: Record | null; +}; export function companySkillRoutes(db: Db) { const router = Router(); @@ -22,6 +32,26 @@ export function companySkillRoutes(db: Db) { return Boolean((agent.permissions as Record).canCreateAgents); } + function asString(value: unknown): string | null { + if (typeof value !== "string") return null; + const trimmed = value.trim(); + return trimmed.length > 0 ? trimmed : null; + } + + function deriveTrackedSkillRef(skill: SkillTelemetryInput): string | null { + if (skill.sourceType === "skills_sh") { + return skill.key; + } + if (skill.sourceType !== "github") { + return null; + } + const hostname = asString(skill.metadata?.hostname); + if (hostname !== "github.com") { + return null; + } + return skill.key; + } + async function assertCanMutateCompanySkills(req: Request, companyId: string) { assertCompanyAccess(req, companyId); @@ -183,6 +213,15 @@ export function companySkillRoutes(db: Db) { warningCount: result.warnings.length, }, }); + const telemetryClient = getTelemetryClient(); + if (telemetryClient) { + for (const skill of result.imported) { + trackSkillImported(telemetryClient, { + sourceType: skill.sourceType, + skillRef: deriveTrackedSkillRef(skill), + }); + } + } res.status(201).json(result); }, diff --git a/server/src/routes/goals.ts b/server/src/routes/goals.ts index 450f946757e..2f090dad8e4 100644 --- a/server/src/routes/goals.ts +++ b/server/src/routes/goals.ts @@ -1,9 +1,11 @@ import { Router } from "express"; import type { Db } from "@paperclipai/db"; import { createGoalSchema, updateGoalSchema } from "@paperclipai/shared"; +import { trackGoalCreated } from "@paperclipai/shared/telemetry"; import { validate } from "../middleware/validate.js"; import { goalService, logActivity } from "../services/index.js"; import { assertCompanyAccess, getActorInfo } from "./authz.js"; +import { getTelemetryClient } from "../telemetry.js"; export function goalRoutes(db: Db) { const router = Router(); @@ -42,6 +44,10 @@ export function goalRoutes(db: Db) { entityId: goal.id, details: { title: goal.title }, }); + const telemetryClient = getTelemetryClient(); + if (telemetryClient) { + trackGoalCreated(telemetryClient, { goalLevel: goal.level }); + } res.status(201).json(goal); }); diff --git a/server/src/routes/inbox-dismissals.ts b/server/src/routes/inbox-dismissals.ts new file mode 100644 index 00000000000..1b51633e56f --- /dev/null +++ b/server/src/routes/inbox-dismissals.ts @@ -0,0 +1,69 @@ +import { Router } from "express"; +import { z } from "zod"; +import type { Db } from "@paperclipai/db"; +import { validate } from "../middleware/validate.js"; +import { assertCompanyAccess, getActorInfo } from "./authz.js"; +import { inboxDismissalService, logActivity } from "../services/index.js"; + +const inboxDismissalSchema = z.object({ + itemKey: z.string().trim().min(1).regex(/^(approval|join|run):.+$/, "Unsupported inbox item key"), +}); + +export function inboxDismissalRoutes(db: Db) { + const router = Router(); + const svc = inboxDismissalService(db); + + router.get("/companies/:companyId/inbox-dismissals", async (req, res) => { + const companyId = req.params.companyId as string; + assertCompanyAccess(req, companyId); + if (req.actor.type !== "board") { + res.status(403).json({ error: "Board authentication required" }); + return; + } + if (!req.actor.userId) { + res.status(403).json({ error: "Board user context required" }); + return; + } + const dismissals = await svc.list(companyId, req.actor.userId); + res.json(dismissals); + }); + + router.post( + "/companies/:companyId/inbox-dismissals", + validate(inboxDismissalSchema), + async (req, res) => { + const companyId = req.params.companyId as string; + assertCompanyAccess(req, companyId); + if (req.actor.type !== "board") { + res.status(403).json({ error: "Board authentication required" }); + return; + } + if (!req.actor.userId) { + res.status(403).json({ error: "Board user context required" }); + return; + } + + const dismissal = await svc.dismiss(companyId, req.actor.userId, req.body.itemKey, new Date()); + const actor = getActorInfo(req); + await logActivity(db, { + companyId, + actorType: actor.actorType, + actorId: actor.actorId, + agentId: actor.agentId, + runId: actor.runId, + action: "inbox.dismissed", + entityType: "company", + entityId: companyId, + details: { + userId: req.actor.userId, + itemKey: dismissal.itemKey, + dismissedAt: dismissal.dismissedAt, + }, + }); + + res.status(201).json(dismissal); + }, + ); + + return router; +} diff --git a/server/src/routes/index.ts b/server/src/routes/index.ts index dd9c0b548e1..bca529400d2 100644 --- a/server/src/routes/index.ts +++ b/server/src/routes/index.ts @@ -12,6 +12,7 @@ export { costRoutes } from "./costs.js"; export { activityRoutes } from "./activity.js"; export { dashboardRoutes } from "./dashboard.js"; export { sidebarBadgeRoutes } from "./sidebar-badges.js"; +export { inboxDismissalRoutes } from "./inbox-dismissals.js"; export { llmRoutes } from "./llms.js"; export { accessRoutes } from "./access.js"; export { instanceSettingsRoutes } from "./instance-settings.js"; diff --git a/server/src/routes/instance-settings.ts b/server/src/routes/instance-settings.ts index 1c9493caa3a..7bd24e36a3b 100644 --- a/server/src/routes/instance-settings.ts +++ b/server/src/routes/instance-settings.ts @@ -21,7 +21,11 @@ export function instanceSettingsRoutes(db: Db) { const svc = instanceSettingsService(db); router.get("/instance/settings/general", async (req, res) => { - assertCanManageInstanceSettings(req); + // General settings (e.g. keyboardShortcuts) are readable by any + // authenticated board user. Only PATCH requires instance-admin. + if (req.actor.type !== "board") { + throw forbidden("Board access required"); + } res.json(await svc.getGeneral()); }); @@ -56,7 +60,11 @@ export function instanceSettingsRoutes(db: Db) { ); router.get("/instance/settings/experimental", async (req, res) => { - assertCanManageInstanceSettings(req); + // Experimental settings are readable by any authenticated board user. + // Only PATCH requires instance-admin. + if (req.actor.type !== "board") { + throw forbidden("Board access required"); + } res.json(await svc.getExperimental()); }); diff --git a/server/src/routes/issues.ts b/server/src/routes/issues.ts index 688547aeb2e..1be66ff7a49 100644 --- a/server/src/routes/issues.ts +++ b/server/src/routes/issues.ts @@ -1,7 +1,9 @@ +import { randomUUID } from "node:crypto"; import { Router, type Request, type Response } from "express"; import multer from "multer"; import { z } from "zod"; import type { Db } from "@paperclipai/db"; +import { issueExecutionDecisions } from "@paperclipai/db"; import { addIssueCommentSchema, createIssueAttachmentMetadataSchema, @@ -9,20 +11,32 @@ import { createIssueLabelSchema, checkoutIssueSchema, createIssueSchema, + feedbackTargetTypeSchema, + feedbackTraceStatusSchema, + feedbackVoteValueSchema, + upsertIssueFeedbackVoteSchema, linkIssueApprovalSchema, issueDocumentKeySchema, + restoreIssueDocumentRevisionSchema, updateIssueWorkProductSchema, upsertIssueDocumentSchema, updateIssueSchema, + getClosedIsolatedExecutionWorkspaceMessage, + isClosedIsolatedExecutionWorkspace, + type ExecutionWorkspace, } from "@paperclipai/shared"; +import { trackAgentTaskCompleted } from "@paperclipai/shared/telemetry"; +import { getTelemetryClient } from "../telemetry.js"; import type { StorageService } from "../storage/types.js"; import { validate } from "../middleware/validate.js"; import { accessService, agentService, executionWorkspaceService, + feedbackService, goalService, heartbeatService, + instanceSettingsService, issueApprovalService, issueService, documentService, @@ -35,19 +49,87 @@ import { logger } from "../middleware/logger.js"; import { forbidden, HttpError, unauthorized } from "../errors.js"; import { assertCompanyAccess, getActorInfo } from "./authz.js"; import { shouldWakeAssigneeOnCheckout } from "./issues-checkout-wakeup.js"; -import { isAllowedContentType, MAX_ATTACHMENT_BYTES } from "../attachment-types.js"; +import { + isInlineAttachmentContentType, + MAX_ATTACHMENT_BYTES, + normalizeContentType, + SVG_CONTENT_TYPE, +} from "../attachment-types.js"; import { queueIssueAssignmentWakeup } from "../services/issue-assignment-wakeup.js"; +import { + applyIssueExecutionPolicyTransition, + normalizeIssueExecutionPolicy, + parseIssueExecutionState, +} from "../services/issue-execution-policy.js"; const MAX_ISSUE_COMMENT_LIMIT = 500; const updateIssueRouteSchema = updateIssueSchema.extend({ interrupt: z.boolean().optional(), }); -export function issueRoutes(db: Db, storage: StorageService) { + if (nextState.status === "changes_requested") { + const agentId = nextState.returnAssignee?.type === "agent" ? (nextState.returnAssignee.agentId ?? null) : null; + const becameChangesRequested = + previousState?.status !== "changes_requested" || + previousState?.lastDecisionId !== nextState.lastDecisionId || + !executionPrincipalsEqual(previousState?.returnAssignee ?? null, nextState.returnAssignee ?? null); + if (!agentId || !becameChangesRequested) return null; + + const executionStage = buildExecutionStageWakeContext({ + state: nextState, + wakeRole: "executor", + allowedActions: ["address_changes", "resubmit"], + }); + + return { + agentId, + wakeup: { + source: "assignment" as const, + triggerDetail: "system" as const, + reason: "execution_changes_requested", + payload: { + issueId, + mutation: "update", + executionStage, + ...(interruptedRunId ? { interruptedRunId } : {}), + }, + requestedByActorType: input.requestedByActorType, + requestedByActorId: input.requestedByActorId, + contextSnapshot: { + issueId, + taskId: issueId, + wakeReason: "execution_changes_requested", + source: "issue.execution_stage", + executionStage, + ...(interruptedRunId ? { interruptedRunId } : {}), + }, + }, + }; + } + + return null; +} + +export function issueRoutes( + db: Db, + storage: StorageService, + opts?: { + feedbackExportService?: { + flushPendingFeedbackTraces(input?: { + companyId?: string; + traceId?: string; + limit?: number; + now?: Date; + }): Promise; + }; + }, +) { const router = Router(); const svc = issueService(db); const access = accessService(db); const heartbeat = heartbeatService(db); + const feedback = feedbackService(db); + const instanceSettings = instanceSettingsService(db); const agentsSvc = agentService(db); const projectsSvc = projectService(db); const goalsSvc = goalService(db); @@ -56,6 +138,7 @@ export function issueRoutes(db: Db, storage: StorageService) { const workProductsSvc = workProductService(db); const documentsSvc = documentService(db); const routinesSvc = routineService(db); + const feedbackExportService = opts?.feedbackExportService; const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: MAX_ATTACHMENT_BYTES, files: 1 }, @@ -68,6 +151,19 @@ export function issueRoutes(db: Db, storage: StorageService) { }; } + function parseBooleanQuery(value: unknown) { + return value === true || value === "true" || value === "1"; + } + + function parseDateQuery(value: unknown, field: string) { + if (typeof value !== "string" || value.trim().length === 0) return undefined; + const parsed = new Date(value); + if (Number.isNaN(parsed.getTime())) { + throw new HttpError(400, `Invalid ${field} query value`); + } + return parsed; + } + async function runSingleFileUpload(req: Request, res: Response) { await new Promise((resolve, reject) => { upload.single("file")(req, res, (err: unknown) => { @@ -94,6 +190,13 @@ export function issueRoutes(db: Db, storage: StorageService) { return false; } + function actorCanAccessCompany(req: Request, companyId: string) { + if (req.actor.type === "none") return false; + if (req.actor.type === "agent") return req.actor.companyId === companyId; + if (req.actor.source === "local_implicit" || req.actor.isInstanceAdmin) return true; + return (req.actor.companyIds ?? []).includes(companyId); + } + function canCreateAgentsLegacy(agent: { permissions: Record | null | undefined; role: string }) { if (agent.role === "ceo") return true; if (!agent.permissions || typeof agent.permissions !== "object") return false; @@ -276,6 +379,9 @@ export function issueRoutes(db: Db, storage: StorageService) { unreadForUserFilterRaw === "me" && req.actor.type === "board" ? req.actor.userId : unreadForUserFilterRaw; + const rawLimit = req.query.limit as string | undefined; + const parsedLimit = rawLimit ? Number.parseInt(rawLimit, 10) : null; + const limit = parsedLimit ?? undefined; if (assigneeUserFilterRaw === "me" && (!assigneeUserId || req.actor.type !== "board")) { res.status(403).json({ error: "assigneeUserId=me requires board authentication" }); @@ -293,6 +399,10 @@ export function issueRoutes(db: Db, storage: StorageService) { res.status(403).json({ error: "unreadForUserId=me requires board authentication" }); return; } + if (rawLimit !== undefined && (parsedLimit === null || !Number.isInteger(parsedLimit) || parsedLimit <= 0)) { + res.status(400).json({ error: "limit must be a positive integer" }); + return; + } const result = await svc.list(companyId, { status: req.query.status as string | undefined, @@ -311,6 +421,7 @@ export function issueRoutes(db: Db, storage: StorageService) { includeRoutineExecutions: req.query.includeRoutineExecutions === "true" || req.query.includeRoutineExecutions === "1", q: req.query.q as string | undefined, + limit, }); res.json(result); }); @@ -377,11 +488,12 @@ export function issueRoutes(db: Db, storage: StorageService) { return; } assertCompanyAccess(req, issue.companyId); - const [{ project, goal }, ancestors, mentionedProjectIds, documentPayload] = await Promise.all([ + const [{ project, goal }, ancestors, mentionedProjectIds, documentPayload, relations] = await Promise.all([ resolveIssueProjectAndGoal(issue), svc.getAncestors(issue.id), svc.findMentionedProjectIds(issue.id), documentsSvc.getIssueDocumentPayload(issue), + svc.getRelationSummaries(issue.id), ]); const mentionedProjects = mentionedProjectIds.length > 0 ? await projectsSvc.listByIds(issue.companyId, mentionedProjectIds) @@ -394,6 +506,8 @@ export function issueRoutes(db: Db, storage: StorageService) { ...issue, goalId: goal?.id ?? issue.goalId, ancestors, + blockedBy: relations.blockedBy, + blocks: relations.blocks, ...documentPayload, project: project ?? null, goal: goal ?? null, @@ -417,11 +531,14 @@ export function issueRoutes(db: Db, storage: StorageService) { ? req.query.wakeCommentId.trim() : null; - const [{ project, goal }, ancestors, commentCursor, wakeComment] = await Promise.all([ + const [{ project, goal }, ancestors, commentCursor, wakeComment, relations, attachments] = + await Promise.all([ resolveIssueProjectAndGoal(issue), svc.getAncestors(issue.id), svc.getCommentCursor(issue.id), wakeCommentId ? svc.getComment(wakeCommentId) : null, + svc.getRelationSummaries(issue.id), + svc.listAttachments(issue.id), ]); res.json({ @@ -435,6 +552,8 @@ export function issueRoutes(db: Db, storage: StorageService) { projectId: issue.projectId, goalId: goal?.id ?? issue.goalId, parentId: issue.parentId, + blockedBy: relations.blockedBy, + blocks: relations.blocks, assigneeAgentId: issue.assigneeAgentId, assigneeUserId: issue.assigneeUserId, updatedAt: issue.updatedAt, @@ -468,6 +587,14 @@ export function issueRoutes(db: Db, storage: StorageService) { wakeComment && wakeComment.issueId === issue.id ? wakeComment : null, + attachments: attachments.map((a) => ({ + id: a.id, + filename: a.originalFilename, + contentType: a.contentType, + byteSize: a.byteSize, + contentPath: withContentPath(a).contentPath, + createdAt: a.createdAt, + })), }); }); @@ -541,6 +668,7 @@ export function issueRoutes(db: Db, storage: StorageService) { baseRevisionId: req.body.baseRevisionId ?? null, createdByAgentId: actor.agentId ?? null, createdByUserId: actor.actorType === "user" ? actor.actorId : null, + createdByRunId: actor.runId ?? null, }); const doc = result.document; @@ -582,6 +710,57 @@ export function issueRoutes(db: Db, storage: StorageService) { res.json(revisions); }); + router.post( + "/issues/:id/documents/:key/revisions/:revisionId/restore", + validate(restoreIssueDocumentRevisionSchema), + async (req, res) => { + const id = req.params.id as string; + const revisionId = req.params.revisionId as string; + const issue = await svc.getById(id); + if (!issue) { + res.status(404).json({ error: "Issue not found" }); + return; + } + assertCompanyAccess(req, issue.companyId); + const keyParsed = issueDocumentKeySchema.safeParse(String(req.params.key ?? "").trim().toLowerCase()); + if (!keyParsed.success) { + res.status(400).json({ error: "Invalid document key", details: keyParsed.error.issues }); + return; + } + + const actor = getActorInfo(req); + const result = await documentsSvc.restoreIssueDocumentRevision({ + issueId: issue.id, + key: keyParsed.data, + revisionId, + createdByAgentId: actor.agentId ?? null, + createdByUserId: actor.actorType === "user" ? actor.actorId : null, + }); + + await logActivity(db, { + companyId: issue.companyId, + actorType: actor.actorType, + actorId: actor.actorId, + agentId: actor.agentId, + runId: actor.runId, + action: "issue.document_restored", + entityType: "issue", + entityId: issue.id, + details: { + key: result.document.key, + documentId: result.document.id, + title: result.document.title, + format: result.document.format, + revisionNumber: result.document.latestRevisionNumber, + restoredFromRevisionId: result.restoredFromRevisionId, + restoredFromRevisionNumber: result.restoredFromRevisionNumber, + }, + }); + + res.json(result.document); + }, + ); + router.delete("/issues/:id/documents/:key", async (req, res) => { const id = req.params.id as string; const issue = await svc.getById(id); @@ -917,8 +1096,10 @@ export function issueRoutes(db: Db, storage: StorageService) { } const actor = getActorInfo(req); + const executionPolicy = normalizeIssueExecutionPolicy(req.body.executionPolicy); const issue = await svc.create(companyId, { ...req.body, + executionPolicy, createdByAgentId: actor.agentId, createdByUserId: actor.actorType === "user" ? actor.actorId : null, }); @@ -932,7 +1113,11 @@ export function issueRoutes(db: Db, storage: StorageService) { action: "issue.created", entityType: "issue", entityId: issue.id, - details: { title: issue.title, identifier: issue.identifier }, + details: { + title: issue.title, + identifier: issue.identifier, + ...(Array.isArray(req.body.blockedByIssueIds) ? { blockedByIssueIds: req.body.blockedByIssueIds } : {}), + }, }); void queueIssueAssignmentWakeup({ @@ -956,25 +1141,149 @@ export function issueRoutes(db: Db, storage: StorageService) { return; } assertCompanyAccess(req, existing.companyId); - const assigneeWillChange = - (req.body.assigneeAgentId !== undefined && req.body.assigneeAgentId !== existing.assigneeAgentId) || - (req.body.assigneeUserId !== undefined && req.body.assigneeUserId !== existing.assigneeUserId); + if (!(await assertAgentRunCheckoutOwnership(req, res, existing))) return; + const actor = getActorInfo(req); + const isClosed = existing.status === "done" || existing.status === "cancelled"; + const existingRelations = + Array.isArray(req.body.blockedByIssueIds) + ? await svc.getRelationSummaries(existing.id) + : null; + const { + comment: commentBody, + reopen: reopenRequested, + interrupt: interruptRequested, + hiddenAt: hiddenAtRaw, + ...updateFields + } = req.body; + let interruptedRunId: string | null = null; + const closedExecutionWorkspace = await getClosedIssueExecutionWorkspace(existing); + const isAgentWorkUpdate = req.actor.type === "agent" && Object.keys(updateFields).length > 0; + + if (closedExecutionWorkspace && (commentBody || isAgentWorkUpdate)) { + respondClosedIssueExecutionWorkspace(res, closedExecutionWorkspace); + return; + } + + if (interruptRequested) { + if (!commentBody) { + res.status(400).json({ error: "Interrupt is only supported when posting a comment" }); + return; + } + if (req.actor.type !== "board") { + res.status(403).json({ error: "Only board users can interrupt active runs from issue comments" }); + return; + } + + const runToInterrupt = await resolveActiveIssueRun(existing); + if (runToInterrupt) { + const cancelled = await heartbeat.cancelRun(runToInterrupt.id); + if (cancelled) { + interruptedRunId = cancelled.id; + await logActivity(db, { + companyId: cancelled.companyId, + actorType: actor.actorType, + actorId: actor.actorId, + agentId: actor.agentId, + runId: actor.runId, + action: "heartbeat.cancelled", + entityType: "heartbeat_run", + entityId: cancelled.id, + details: { agentId: cancelled.agentId, source: "issue_comment_interrupt", issueId: existing.id }, + }); + } + } + } + + if (hiddenAtRaw !== undefined) { + updateFields.hiddenAt = hiddenAtRaw ? new Date(hiddenAtRaw) : null; + } + if (commentBody && reopenRequested === true && isClosed && updateFields.status === undefined) { + updateFields.status = "todo"; + } + if (req.body.executionPolicy !== undefined) { + updateFields.executionPolicy = normalizeIssueExecutionPolicy(req.body.executionPolicy); + } + const previousExecutionPolicy = normalizeIssueExecutionPolicy(existing.executionPolicy ?? null); + const nextExecutionPolicy = + updateFields.executionPolicy !== undefined + ? (updateFields.executionPolicy as NormalizedExecutionPolicy | null) + : previousExecutionPolicy; + + const requestedStatus = typeof updateFields.status === "string" ? updateFields.status : undefined; + const requestedAssigneePatchProvided = + req.body.assigneeAgentId !== undefined || req.body.assigneeUserId !== undefined; + + const transition = applyIssueExecutionPolicyTransition({ + issue: existing, + policy: nextExecutionPolicy, + requestedStatus, + requestedAssigneePatch: { + assigneeAgentId: + req.body.assigneeAgentId === undefined ? undefined : (req.body.assigneeAgentId as string | null), + assigneeUserId: + req.body.assigneeUserId === undefined ? undefined : (req.body.assigneeUserId as string | null), + }, + actor: { + agentId: actor.agentId ?? null, + userId: actor.actorType === "user" ? actor.actorId : null, + }, + commentBody, + }); + const decisionId = transition.decision ? randomUUID() : null; + if (decisionId) { + const nextExecutionState = transition.patch.executionState; + if (!nextExecutionState || typeof nextExecutionState !== "object") { + throw new Error("Execution policy decision patch is missing executionState"); + } + transition.patch.executionState = { + ...nextExecutionState, + lastDecisionId: decisionId, + }; + } + Object.assign(updateFields, transition.patch); + + const effectiveExecutionState = parseIssueExecutionState( + transition.patch.executionState !== undefined ? transition.patch.executionState : existing.executionState, + ); + const isUnauthorizedAgentStageMutation = + req.actor.type === "agent" && + req.actor.agentId && + existing.status === "in_review" && + transition.workflowControlledAssignment && + !transition.decision && + effectiveExecutionState?.status === "pending" && + ( + (requestedStatus !== undefined && requestedStatus !== "in_review") || + requestedAssigneePatchProvided + ) && + !executionParticipantMatchesAgent(effectiveExecutionState.currentParticipant, req.actor.agentId); + if (isUnauthorizedAgentStageMutation) { + const stageLabel = effectiveExecutionState.currentStageType ?? "execution"; + res.status(403).json({ error: `Only the active ${stageLabel} participant can update this stage` }); + return; + } + + const nextAssigneeAgentId = + updateFields.assigneeAgentId === undefined ? existing.assigneeAgentId : (updateFields.assigneeAgentId as string | null); + const nextAssigneeUserId = + updateFields.assigneeUserId === undefined ? existing.assigneeUserId : (updateFields.assigneeUserId as string | null); + const assigneeWillChange = + nextAssigneeAgentId !== existing.assigneeAgentId || nextAssigneeUserId !== existing.assigneeUserId; const isAgentReturningIssueToCreator = req.actor.type === "agent" && !!req.actor.agentId && existing.assigneeAgentId === req.actor.agentId && - req.body.assigneeAgentId === null && - typeof req.body.assigneeUserId === "string" && + nextAssigneeAgentId === null && + typeof nextAssigneeUserId === "string" && !!existing.createdByUserId && - req.body.assigneeUserId === existing.createdByUserId; + nextAssigneeUserId === existing.createdByUserId; - if (assigneeWillChange) { + if (assigneeWillChange && !transition.workflowControlledAssignment) { if (!isAgentReturningIssueToCreator) { await assertCanAssignTasks(req, existing.companyId); } } - if (!(await assertAgentRunCheckoutOwnership(req, res, existing))) return; const actor = getActorInfo(req); const isClosed = existing.status === "done" || existing.status === "cancelled"; @@ -1025,7 +1334,42 @@ export function issueRoutes(db: Db, storage: StorageService) { } let issue; try { - issue = await svc.update(id, updateFields); + if (transition.decision && decisionId) { + const decision = transition.decision; + issue = await db.transaction(async (tx) => { + const updated = await svc.update( + id, + { + ...updateFields, + actorAgentId: actor.agentId ?? null, + actorUserId: actor.actorType === "user" ? actor.actorId : null, + }, + tx, + ); + if (!updated) return null; + + await tx.insert(issueExecutionDecisions).values({ + id: decisionId, + companyId: updated.companyId, + issueId: updated.id, + stageId: decision.stageId, + stageType: decision.stageType, + actorAgentId: actor.agentId ?? null, + actorUserId: actor.actorType === "user" ? actor.actorId : null, + outcome: decision.outcome, + body: decision.body, + createdByRunId: actor.runId ?? null, + }); + + return updated; + }); + } else { + issue = await svc.update(id, { + ...updateFields, + actorAgentId: actor.agentId ?? null, + actorUserId: actor.actorType === "user" ? actor.actorId : null, + }); + } } catch (err) { if (err instanceof HttpError && err.status === 422) { logger.warn( @@ -1054,6 +1398,16 @@ export function issueRoutes(db: Db, storage: StorageService) { res.status(404).json({ error: "Issue not found" }); return; } + let issueResponse: typeof issue & { blockedBy?: unknown; blocks?: unknown } = issue; + let updatedRelations: Awaited> | null = null; + if (issue && Array.isArray(req.body.blockedByIssueIds)) { + updatedRelations = await svc.getRelationSummaries(issue.id); + issueResponse = { + ...issue, + blockedBy: updatedRelations.blockedBy, + blocks: updatedRelations.blocks, + }; + } await routinesSvc.syncRunStatusForIssue(issue.id); if (actor.runId) { @@ -1068,6 +1422,9 @@ export function issueRoutes(db: Db, storage: StorageService) { previous[key] = (existing as Record)[key]; } } + if (Array.isArray(req.body.blockedByIssueIds)) { + previous.blockedByIssueIds = existingRelations?.blockedBy.map((relation) => relation.id) ?? []; + } const hasFieldChanges = Object.keys(previous).length > 0; const reopened = @@ -1096,11 +1453,96 @@ export function issueRoutes(db: Db, storage: StorageService) { }, }); + if (Array.isArray(req.body.blockedByIssueIds)) { + const previousBlockedByIds = new Set((existingRelations?.blockedBy ?? []).map((relation) => relation.id)); + const nextBlockedByIds = new Set(req.body.blockedByIssueIds as string[]); + const addedBlockedByIssueIds = [...nextBlockedByIds].filter((candidate) => !previousBlockedByIds.has(candidate)); + const removedBlockedByIssueIds = [...previousBlockedByIds].filter((candidate) => !nextBlockedByIds.has(candidate)); + const nextBlockedByRelations = updatedRelations?.blockedBy ?? []; + const previousBlockedByRelations = existingRelations?.blockedBy ?? []; + if (addedBlockedByIssueIds.length > 0 || removedBlockedByIssueIds.length > 0) { + await logActivity(db, { + companyId: issue.companyId, + actorType: actor.actorType, + actorId: actor.actorId, + agentId: actor.agentId, + runId: actor.runId, + action: "issue.blockers_updated", + entityType: "issue", + entityId: issue.id, + details: { + identifier: issue.identifier, + blockedByIssueIds: req.body.blockedByIssueIds, + addedBlockedByIssueIds, + removedBlockedByIssueIds, + blockedByIssues: nextBlockedByRelations.map(summarizeIssueRelationForActivity), + addedBlockedByIssues: nextBlockedByRelations + .filter((relation) => addedBlockedByIssueIds.includes(relation.id)) + .map(summarizeIssueRelationForActivity), + removedBlockedByIssues: previousBlockedByRelations + .filter((relation) => removedBlockedByIssueIds.includes(relation.id)) + .map(summarizeIssueRelationForActivity), + }, + }); + } + } + + const reviewerChanges = diffExecutionParticipants(previousExecutionPolicy, nextExecutionPolicy, "review"); + if (reviewerChanges.addedParticipants.length > 0 || reviewerChanges.removedParticipants.length > 0) { + await logActivity(db, { + companyId: issue.companyId, + actorType: actor.actorType, + actorId: actor.actorId, + agentId: actor.agentId, + runId: actor.runId, + action: "issue.reviewers_updated", + entityType: "issue", + entityId: issue.id, + details: { + identifier: issue.identifier, + participants: reviewerChanges.participants, + addedParticipants: reviewerChanges.addedParticipants, + removedParticipants: reviewerChanges.removedParticipants, + }, + }); + } + + const approverChanges = diffExecutionParticipants(previousExecutionPolicy, nextExecutionPolicy, "approval"); + if (approverChanges.addedParticipants.length > 0 || approverChanges.removedParticipants.length > 0) { + await logActivity(db, { + companyId: issue.companyId, + actorType: actor.actorType, + actorId: actor.actorId, + agentId: actor.agentId, + runId: actor.runId, + action: "issue.approvers_updated", + entityType: "issue", + entityId: issue.id, + details: { + identifier: issue.identifier, + participants: approverChanges.participants, + addedParticipants: approverChanges.addedParticipants, + removedParticipants: approverChanges.removedParticipants, + }, + }); + } + + if (issue.status === "done" && existing.status !== "done") { + const tc = getTelemetryClient(); + if (tc && actor.agentId) { + const actorAgent = await agentsSvc.getById(actor.agentId); + if (actorAgent) { + trackAgentTaskCompleted(tc, { agentRole: actorAgent.role }); + } + } + } + let comment = null; if (commentBody) { comment = await svc.addComment(id, commentBody, { agentId: actor.agentId ?? undefined, userId: actor.actorType === "user" ? actor.actorId : undefined, + runId: actor.runId, }); await logActivity(db, { @@ -1124,19 +1566,39 @@ export function issueRoutes(db: Db, storage: StorageService) { }); } - - const assigneeChanged = assigneeWillChange; + const assigneeChanged = + issue.assigneeAgentId !== existing.assigneeAgentId || issue.assigneeUserId !== existing.assigneeUserId; const statusChangedFromBacklog = existing.status === "backlog" && issue.status !== "backlog" && req.body.status !== undefined; + const previousExecutionState = parseIssueExecutionState(existing.executionState); + const nextExecutionState = parseIssueExecutionState(issue.executionState); + const executionStageWakeup = buildExecutionStageWakeup({ + issueId: issue.id, + previousState: previousExecutionState, + nextState: nextExecutionState, + interruptedRunId, + requestedByActorType: actor.actorType, + requestedByActorId: actor.actorId, + }); // Merge all wakeups from this update into one enqueue per agent to avoid duplicate runs. void (async () => { - const wakeups = new Map[1]>(); + type WakeupRequest = NonNullable[1]>; + const wakeups = new Map(); + const addWakeup = (agentId: string, wakeup: WakeupRequest) => { + const wakeIssueId = + wakeup.payload && typeof wakeup.payload === "object" && typeof wakeup.payload.issueId === "string" + ? wakeup.payload.issueId + : issue.id; + wakeups.set(`${agentId}:${wakeIssueId}`, { agentId, wakeup }); + }; - if (assigneeChanged && issue.assigneeAgentId && issue.status !== "backlog") { - wakeups.set(issue.assigneeAgentId, { + if (executionStageWakeup) { + addWakeup(executionStageWakeup.agentId, executionStageWakeup.wakeup); + } else if (assigneeChanged && issue.assigneeAgentId && issue.status !== "backlog") { + addWakeup(issue.assigneeAgentId, { source: "assignment", triggerDetail: "system", reason: "issue_assigned", @@ -1156,7 +1618,7 @@ export function issueRoutes(db: Db, storage: StorageService) { } if (!assigneeChanged && statusChangedFromBacklog && issue.assigneeAgentId) { - wakeups.set(issue.assigneeAgentId, { + addWakeup(issue.assigneeAgentId, { source: "automation", triggerDetail: "system", reason: "issue_status_changed", @@ -1184,9 +1646,8 @@ export function issueRoutes(db: Db, storage: StorageService) { } for (const mentionedId of mentionedIds) { - if (wakeups.has(mentionedId)) continue; if (actor.actorType === "agent" && actor.actorId === mentionedId) continue; - wakeups.set(mentionedId, { + addWakeup(mentionedId, { source: "automation", triggerDetail: "system", reason: "issue_comment_mentioned", @@ -1205,14 +1666,69 @@ export function issueRoutes(db: Db, storage: StorageService) { } } - for (const [agentId, wakeup] of wakeups.entries()) { + const becameDone = existing.status !== "done" && issue.status === "done"; + if (becameDone) { + const dependents = await svc.listWakeableBlockedDependents(issue.id); + for (const dependent of dependents) { + addWakeup(dependent.assigneeAgentId, { + source: "automation", + triggerDetail: "system", + reason: "issue_blockers_resolved", + payload: { + issueId: dependent.id, + resolvedBlockerIssueId: issue.id, + blockerIssueIds: dependent.blockerIssueIds, + }, + requestedByActorType: actor.actorType, + requestedByActorId: actor.actorId, + contextSnapshot: { + issueId: dependent.id, + taskId: dependent.id, + wakeReason: "issue_blockers_resolved", + source: "issue.blockers_resolved", + resolvedBlockerIssueId: issue.id, + blockerIssueIds: dependent.blockerIssueIds, + }, + }); + } + } + + const becameTerminal = + !["done", "cancelled"].includes(existing.status) && ["done", "cancelled"].includes(issue.status); + if (becameTerminal && issue.parentId) { + const parent = await svc.getWakeableParentAfterChildCompletion(issue.parentId); + if (parent) { + addWakeup(parent.assigneeAgentId, { + source: "automation", + triggerDetail: "system", + reason: "issue_children_completed", + payload: { + issueId: parent.id, + completedChildIssueId: issue.id, + childIssueIds: parent.childIssueIds, + }, + requestedByActorType: actor.actorType, + requestedByActorId: actor.actorId, + contextSnapshot: { + issueId: parent.id, + taskId: parent.id, + wakeReason: "issue_children_completed", + source: "issue.children_completed", + completedChildIssueId: issue.id, + childIssueIds: parent.childIssueIds, + }, + }); + } + } + + for (const { agentId, wakeup } of wakeups.values()) { heartbeat .wakeup(agentId, wakeup) .catch((err) => logger.warn({ err, issueId: issue.id, agentId }, "failed to wake agent on issue update")); } })(); - res.json({ ...issue, comment }); + res.json({ ...issueResponse, comment }); }); router.delete("/issues/:id", async (req, res) => { @@ -1281,6 +1797,12 @@ export function issueRoutes(db: Db, storage: StorageService) { return; } + const closedExecutionWorkspace = await getClosedIssueExecutionWorkspace(issue); + if (closedExecutionWorkspace) { + respondClosedIssueExecutionWorkspace(res, closedExecutionWorkspace); + return; + } + const checkoutRunId = requireAgentRunId(req, res); if (req.actor.type === "agent" && !checkoutRunId) return; const updated = await svc.checkout(id, req.body.agentId, req.body.expectedStatuses, checkoutRunId); @@ -1410,6 +1932,86 @@ export function issueRoutes(db: Db, storage: StorageService) { res.json(comment); }); + router.get("/issues/:id/feedback-votes", async (req, res) => { + const id = req.params.id as string; + const issue = await svc.getById(id); + if (!issue) { + res.status(404).json({ error: "Issue not found" }); + return; + } + assertCompanyAccess(req, issue.companyId); + if (req.actor.type !== "board") { + res.status(403).json({ error: "Only board users can view feedback votes" }); + return; + } + + const votes = await feedback.listIssueVotesForUser(id, req.actor.userId ?? "local-board"); + res.json(votes); + }); + + router.get("/issues/:id/feedback-traces", async (req, res) => { + const id = req.params.id as string; + const issue = await svc.getById(id); + if (!issue) { + res.status(404).json({ error: "Issue not found" }); + return; + } + assertCompanyAccess(req, issue.companyId); + if (req.actor.type !== "board") { + res.status(403).json({ error: "Only board users can view feedback traces" }); + return; + } + + const targetTypeRaw = typeof req.query.targetType === "string" ? req.query.targetType : undefined; + const voteRaw = typeof req.query.vote === "string" ? req.query.vote : undefined; + const statusRaw = typeof req.query.status === "string" ? req.query.status : undefined; + const targetType = targetTypeRaw ? feedbackTargetTypeSchema.parse(targetTypeRaw) : undefined; + const vote = voteRaw ? feedbackVoteValueSchema.parse(voteRaw) : undefined; + const status = statusRaw ? feedbackTraceStatusSchema.parse(statusRaw) : undefined; + + const traces = await feedback.listFeedbackTraces({ + companyId: issue.companyId, + issueId: issue.id, + targetType, + vote, + status, + from: parseDateQuery(req.query.from, "from"), + to: parseDateQuery(req.query.to, "to"), + sharedOnly: parseBooleanQuery(req.query.sharedOnly), + includePayload: parseBooleanQuery(req.query.includePayload), + }); + res.json(traces); + }); + + router.get("/feedback-traces/:traceId", async (req, res) => { + const traceId = req.params.traceId as string; + if (req.actor.type !== "board") { + res.status(403).json({ error: "Only board users can view feedback traces" }); + return; + } + const includePayload = parseBooleanQuery(req.query.includePayload) || req.query.includePayload === undefined; + const trace = await feedback.getFeedbackTraceById(traceId, includePayload); + if (!trace || !actorCanAccessCompany(req, trace.companyId)) { + res.status(404).json({ error: "Feedback trace not found" }); + return; + } + res.json(trace); + }); + + router.get("/feedback-traces/:traceId/bundle", async (req, res) => { + const traceId = req.params.traceId as string; + if (req.actor.type !== "board") { + res.status(403).json({ error: "Only board users can view feedback trace bundles" }); + return; + } + const bundle = await feedback.getFeedbackTraceBundle(traceId); + if (!bundle || !actorCanAccessCompany(req, bundle.companyId)) { + res.status(404).json({ error: "Feedback trace not found" }); + return; + } + res.json(bundle); + }); + router.post("/issues/:id/comments", validate(addIssueCommentSchema), async (req, res) => { const id = req.params.id as string; const issue = await svc.getById(id); @@ -1419,6 +2021,11 @@ export function issueRoutes(db: Db, storage: StorageService) { } assertCompanyAccess(req, issue.companyId); if (!(await assertAgentRunCheckoutOwnership(req, res, issue))) return; + const closedExecutionWorkspace = await getClosedIssueExecutionWorkspace(issue); + if (closedExecutionWorkspace) { + respondClosedIssueExecutionWorkspace(res, closedExecutionWorkspace); + return; + } const actor = getActorInfo(req); const reopenRequested = req.body.reopen === true; @@ -1487,6 +2094,7 @@ export function issueRoutes(db: Db, storage: StorageService) { const comment = await svc.addComment(id, req.body.body, { agentId: actor.agentId ?? undefined, userId: actor.actorType === "user" ? actor.actorId : undefined, + runId: actor.runId, }); if (actor.runId) { @@ -1539,6 +2147,7 @@ export function issueRoutes(db: Db, storage: StorageService) { issueId: currentIssue.id, taskId: currentIssue.id, commentId: comment.id, + wakeCommentId: comment.id, source: "issue.comment.reopen", wakeReason: "issue_reopened_via_comment", reopenedFrom: reopenFromStatus, @@ -1562,6 +2171,7 @@ export function issueRoutes(db: Db, storage: StorageService) { issueId: currentIssue.id, taskId: currentIssue.id, commentId: comment.id, + wakeCommentId: comment.id, source: "issue.comment", wakeReason: "issue_commented", ...(interruptedRunId ? { interruptedRunId } : {}), @@ -1608,6 +2218,105 @@ export function issueRoutes(db: Db, storage: StorageService) { res.status(201).json(comment); }); + router.post("/issues/:id/feedback-votes", validate(upsertIssueFeedbackVoteSchema), async (req, res) => { + const id = req.params.id as string; + const issue = await svc.getById(id); + if (!issue) { + res.status(404).json({ error: "Issue not found" }); + return; + } + assertCompanyAccess(req, issue.companyId); + if (req.actor.type !== "board") { + res.status(403).json({ error: "Only board users can vote on AI feedback" }); + return; + } + + const actor = getActorInfo(req); + const result = await feedback.saveIssueVote({ + issueId: id, + targetType: req.body.targetType, + targetId: req.body.targetId, + vote: req.body.vote, + reason: req.body.reason, + authorUserId: req.actor.userId ?? "local-board", + allowSharing: req.body.allowSharing === true, + }); + + await logActivity(db, { + companyId: issue.companyId, + actorType: actor.actorType, + actorId: actor.actorId, + agentId: actor.agentId, + runId: actor.runId, + action: "issue.feedback_vote_saved", + entityType: "issue", + entityId: issue.id, + details: { + identifier: issue.identifier, + targetType: result.vote.targetType, + targetId: result.vote.targetId, + vote: result.vote.vote, + hasReason: Boolean(result.vote.reason), + sharingEnabled: result.sharingEnabled, + }, + }); + + if (result.consentEnabledNow) { + await logActivity(db, { + companyId: issue.companyId, + actorType: actor.actorType, + actorId: actor.actorId, + agentId: actor.agentId, + runId: actor.runId, + action: "company.feedback_data_sharing_updated", + entityType: "company", + entityId: issue.companyId, + details: { + feedbackDataSharingEnabled: true, + source: "issue_feedback_vote", + }, + }); + } + + if (result.persistedSharingPreference) { + const settings = await instanceSettings.get(); + const companyIds = await instanceSettings.listCompanyIds(); + await Promise.all( + companyIds.map((companyId) => + logActivity(db, { + companyId, + actorType: actor.actorType, + actorId: actor.actorId, + agentId: actor.agentId, + runId: actor.runId, + action: "instance.settings.general_updated", + entityType: "instance_settings", + entityId: settings.id, + details: { + general: settings.general, + changedKeys: ["feedbackDataSharingPreference"], + source: "issue_feedback_vote", + }, + }), + ), + ); + } + + if (result.sharingEnabled && result.traceId && feedbackExportService) { + try { + await feedbackExportService.flushPendingFeedbackTraces({ + companyId: issue.companyId, + traceId: result.traceId, + limit: 1, + }); + } catch (err) { + logger.warn({ err, issueId: issue.id, traceId: result.traceId }, "failed to flush shared feedback trace immediately"); + } + } + + res.status(201).json(result.vote); + }); + router.get("/issues/:id/attachments", async (req, res) => { const issueId = req.params.id as string; const issue = await svc.getById(issueId); @@ -1653,11 +2362,7 @@ export function issueRoutes(db: Db, storage: StorageService) { res.status(400).json({ error: "Missing file field 'file'" }); return; } - const contentType = (file.mimetype || "").toLowerCase(); - if (!isAllowedContentType(contentType)) { - res.status(422).json({ error: `Unsupported attachment type: ${contentType || "unknown"}` }); - return; - } + const contentType = normalizeContentType(file.mimetype); if (file.buffer.length <= 0) { res.status(422).json({ error: "Attachment is empty" }); return; @@ -1721,11 +2426,17 @@ export function issueRoutes(db: Db, storage: StorageService) { assertCompanyAccess(req, attachment.companyId); const object = await storage.getObject(attachment.companyId, attachment.objectKey); - res.setHeader("Content-Type", attachment.contentType || object.contentType || "application/octet-stream"); + const responseContentType = normalizeContentType(attachment.contentType || object.contentType); + res.setHeader("Content-Type", responseContentType); res.setHeader("Content-Length", String(attachment.byteSize || object.contentLength || 0)); res.setHeader("Cache-Control", "private, max-age=60"); + res.setHeader("X-Content-Type-Options", "nosniff"); + if (responseContentType === SVG_CONTENT_TYPE) { + res.setHeader("Content-Security-Policy", "sandbox; default-src 'none'; img-src 'self' data:; style-src 'unsafe-inline'"); + } const filename = attachment.originalFilename ?? "attachment"; - res.setHeader("Content-Disposition", `inline; filename=\"${filename.replaceAll("\"", "")}\"`); + const disposition = isInlineAttachmentContentType(responseContentType) ? "inline" : "attachment"; + res.setHeader("Content-Disposition", `${disposition}; filename=\"${filename.replaceAll("\"", "")}\"`); object.stream.on("error", (err) => { next(err); diff --git a/server/src/routes/projects.ts b/server/src/routes/projects.ts index b200b35472f..1da70c89285 100644 --- a/server/src/routes/projects.ts +++ b/server/src/routes/projects.ts @@ -7,6 +7,7 @@ import { updateProjectSchema, updateProjectWorkspaceSchema, } from "@paperclipai/shared"; +import { trackProjectCreated } from "@paperclipai/shared/telemetry"; import { validate } from "../middleware/validate.js"; import { projectService, logActivity, workspaceOperationService } from "../services/index.js"; import { conflict } from "../errors.js"; @@ -80,6 +81,13 @@ export function projectRoutes(db: Db) { }; const { workspace, ...projectData } = req.body as CreateProjectPayload; + if (projectData.env !== undefined) { + projectData.env = await secretsSvc.normalizeEnvBindingsForPersistence( + companyId, + projectData.env, + { strictMode: strictSecretsMode, fieldPath: "env" }, + ); + } const project = await svc.create(companyId, projectData); let createdWorkspaceId: string | null = null; if (workspace) { @@ -105,8 +113,13 @@ export function projectRoutes(db: Db) { details: { name: project.name, workspaceId: createdWorkspaceId, + envKeys: project.env ? Object.keys(project.env).sort() : [], }, }); + const telemetryClient = getTelemetryClient(); + if (telemetryClient) { + trackProjectCreated(telemetryClient); + } res.status(201).json(hydratedProject ?? project); }); @@ -122,6 +135,12 @@ export function projectRoutes(db: Db) { if (typeof body.archivedAt === "string") { body.archivedAt = new Date(body.archivedAt); } + if (body.env !== undefined) { + body.env = await secretsSvc.normalizeEnvBindingsForPersistence(existing.companyId, body.env, { + strictMode: strictSecretsMode, + fieldPath: "env", + }); + } const project = await svc.update(id, body); if (!project) { res.status(404).json({ error: "Project not found" }); @@ -137,7 +156,13 @@ export function projectRoutes(db: Db) { action: "project.updated", entityType: "project", entityId: project.id, - details: req.body, + details: { + changedKeys: Object.keys(req.body).sort(), + envKeys: + body.env && typeof body.env === "object" && !Array.isArray(body.env) + ? Object.keys(body.env as Record).sort() + : undefined, + }, }); res.json(project); diff --git a/server/src/routes/routines.ts b/server/src/routes/routines.ts index e7887e8845d..6a17c06c2d2 100644 --- a/server/src/routes/routines.ts +++ b/server/src/routes/routines.ts @@ -8,10 +8,12 @@ import { updateRoutineSchema, updateRoutineTriggerSchema, } from "@paperclipai/shared"; +import { trackRoutineCreated } from "@paperclipai/shared/telemetry"; import { validate } from "../middleware/validate.js"; import { accessService, logActivity, routineService } from "../services/index.js"; import { assertCompanyAccess, getActorInfo } from "./authz.js"; import { forbidden, unauthorized } from "../errors.js"; +import { getTelemetryClient } from "../telemetry.js"; export function routineRoutes(db: Db) { const router = Router(); @@ -32,7 +34,7 @@ export function routineRoutes(db: Db) { assertCompanyAccess(req, companyId); if (req.actor.type === "board") return; if (req.actor.type !== "agent" || !req.actor.agentId) throw unauthorized(); - if (assigneeAgentId && assigneeAgentId !== req.actor.agentId) { + if (assigneeAgentId !== req.actor.agentId) { throw forbidden("Agents can only manage routines assigned to themselves"); } } @@ -76,6 +78,10 @@ export function routineRoutes(db: Db) { entityId: created.id, details: { title: created.title, assigneeAgentId: created.assigneeAgentId }, }); + const telemetryClient = getTelemetryClient(); + if (telemetryClient) { + trackRoutineCreated(telemetryClient); + } res.status(201).json(created); }); @@ -108,7 +114,11 @@ export function routineRoutes(db: Db) { if (statusWillActivate) { await assertBoardCanAssignTasks(req, routine.companyId); } - if (req.actor.type === "agent" && req.body.assigneeAgentId && req.body.assigneeAgentId !== req.actor.agentId) { + if ( + req.actor.type === "agent" && + req.body.assigneeAgentId !== undefined && + req.body.assigneeAgentId !== req.actor.agentId + ) { throw forbidden("Agents can only assign routines to themselves"); } const updated = await svc.update(routine.id, req.body, { @@ -287,6 +297,7 @@ export function routineRoutes(db: Db) { const result = await svc.firePublicTrigger(req.params.publicId as string, { authorizationHeader: req.header("authorization"), signatureHeader: req.header("x-paperclip-signature"), + hubSignatureHeader: req.header("x-hub-signature-256"), timestampHeader: req.header("x-paperclip-timestamp"), idempotencyKey: req.header("idempotency-key"), rawBody: (req as { rawBody?: Buffer }).rawBody ?? null, diff --git a/server/src/routes/sidebar-badges.ts b/server/src/routes/sidebar-badges.ts index 03cb4cb0a8d..505b7704753 100644 --- a/server/src/routes/sidebar-badges.ts +++ b/server/src/routes/sidebar-badges.ts @@ -1,12 +1,20 @@ import { Router } from "express"; import type { Db } from "@paperclipai/db"; -import { and, eq, sql } from "drizzle-orm"; -import { joinRequests } from "@paperclipai/db"; +import { and, eq } from "drizzle-orm"; +import { inboxDismissals, joinRequests } from "@paperclipai/db"; import { sidebarBadgeService } from "../services/sidebar-badges.js"; import { accessService } from "../services/access.js"; import { dashboardService } from "../services/dashboard.js"; import { assertCompanyAccess } from "./authz.js"; +function buildDismissedAtByKey( + dismissals: Array<{ itemKey: string; dismissedAt: Date | string }>, +): Map { + return new Map( + dismissals.map((dismissal) => [dismissal.itemKey, new Date(dismissal.dismissedAt).getTime()]), + ); +} + export function sidebarBadgeRoutes(db: Db) { const router = Router(); const svc = sidebarBadgeService(db); @@ -26,23 +34,36 @@ export function sidebarBadgeRoutes(db: Db) { canApproveJoins = await access.hasPermission(companyId, "agent", req.actor.agentId, "joins:approve"); } - const joinRequestCount = canApproveJoins + const visibleJoinRequests = canApproveJoins ? await db - .select({ count: sql`count(*)` }) + .select({ + id: joinRequests.id, + updatedAt: joinRequests.updatedAt, + createdAt: joinRequests.createdAt, + }) .from(joinRequests) .where(and(eq(joinRequests.companyId, companyId), eq(joinRequests.status, "pending_approval"))) - .then((rows) => Number(rows[0]?.count ?? 0)) - : 0; + : []; + + const dismissedAtByKey = + req.actor.type === "board" && req.actor.userId + ? await db + .select({ itemKey: inboxDismissals.itemKey, dismissedAt: inboxDismissals.dismissedAt }) + .from(inboxDismissals) + .where(and(eq(inboxDismissals.companyId, companyId), eq(inboxDismissals.userId, req.actor.userId))) + .then(buildDismissedAtByKey) + : new Map(); const badges = await svc.get(companyId, { - joinRequests: joinRequestCount, + dismissals: dismissedAtByKey, + joinRequests: visibleJoinRequests, }); const summary = await dashboard.summary(companyId); const hasFailedRuns = badges.failedRuns > 0; const alertsCount = (summary.agents.error > 0 && !hasFailedRuns ? 1 : 0) + (summary.costs.monthBudgetCents > 0 && summary.costs.monthUtilizationPercent >= 80 ? 1 : 0); - badges.inbox = badges.failedRuns + alertsCount + joinRequestCount + badges.approvals; + badges.inbox = badges.failedRuns + alertsCount + badges.joinRequests + badges.approvals; res.json(badges); }); diff --git a/server/src/services/adapter-plugin-store.ts b/server/src/services/adapter-plugin-store.ts new file mode 100644 index 00000000000..8c26abe841d --- /dev/null +++ b/server/src/services/adapter-plugin-store.ts @@ -0,0 +1,177 @@ +/** + * JSON-file-backed store for external adapter registrations. + * + * Stores metadata about externally installed adapter packages at + * ~/.paperclip/adapter-plugins.json. This is the source of truth for which + * external adapters should be loaded at startup. + * + * Both the plugin store and the settings store are cached in memory after + * the first read. Writes invalidate the cache so the next read picks up + * the new state without a redundant disk round-trip. + * + * @module server/services/adapter-plugin-store + */ + +import fs from "node:fs"; +import path from "node:path"; +import os from "node:os"; + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +export interface AdapterPluginRecord { + /** npm package name (e.g., "droid-paperclip-adapter") */ + packageName: string; + /** Absolute local filesystem path (for locally linked adapters) */ + localPath?: string; + /** Installed version string (for npm packages) */ + version?: string; + /** Adapter type identifier (matches ServerAdapterModule.type) */ + type: string; + /** ISO 8601 timestamp of when the adapter was installed */ + installedAt: string; + /** Whether this adapter is disabled (hidden from menus but still functional) */ + disabled?: boolean; +} + +interface AdapterSettings { + disabledTypes: string[]; +} + +// --------------------------------------------------------------------------- +// Paths +// --------------------------------------------------------------------------- + +const PAPERCLIP_DIR = path.join(os.homedir(), ".paperclip"); +const ADAPTER_PLUGINS_DIR = path.join(PAPERCLIP_DIR, "adapter-plugins"); +const ADAPTER_PLUGINS_STORE_PATH = path.join(PAPERCLIP_DIR, "adapter-plugins.json"); +const ADAPTER_SETTINGS_PATH = path.join(PAPERCLIP_DIR, "adapter-settings.json"); + +// --------------------------------------------------------------------------- +// In-memory caches (invalidated on write) +// --------------------------------------------------------------------------- + +let storeCache: AdapterPluginRecord[] | null = null; +let settingsCache: AdapterSettings | null = null; + +// --------------------------------------------------------------------------- +// Store functions +// --------------------------------------------------------------------------- + +function ensureDirs(): void { + fs.mkdirSync(ADAPTER_PLUGINS_DIR, { recursive: true }); + const pkgJsonPath = path.join(ADAPTER_PLUGINS_DIR, "package.json"); + if (!fs.existsSync(pkgJsonPath)) { + fs.writeFileSync(pkgJsonPath, JSON.stringify({ + name: "paperclip-adapter-plugins", + version: "0.0.0", + private: true, + description: "Managed directory for Paperclip external adapter plugins. Do not edit manually.", + }, null, 2) + "\n"); + } +} + +function readStore(): AdapterPluginRecord[] { + if (storeCache) return storeCache; + try { + const raw = fs.readFileSync(ADAPTER_PLUGINS_STORE_PATH, "utf-8"); + const parsed = JSON.parse(raw); + storeCache = Array.isArray(parsed) ? (parsed as AdapterPluginRecord[]) : []; + } catch { + storeCache = []; + } + return storeCache; +} + +function writeStore(records: AdapterPluginRecord[]): void { + ensureDirs(); + fs.writeFileSync(ADAPTER_PLUGINS_STORE_PATH, JSON.stringify(records, null, 2), "utf-8"); + storeCache = records; +} + +function readSettings(): AdapterSettings { + if (settingsCache) return settingsCache; + try { + const raw = fs.readFileSync(ADAPTER_SETTINGS_PATH, "utf-8"); + const parsed = JSON.parse(raw); + settingsCache = parsed && Array.isArray(parsed.disabledTypes) + ? (parsed as AdapterSettings) + : { disabledTypes: [] }; + } catch { + settingsCache = { disabledTypes: [] }; + } + return settingsCache; +} + +function writeSettings(settings: AdapterSettings): void { + ensureDirs(); + fs.writeFileSync(ADAPTER_SETTINGS_PATH, JSON.stringify(settings, null, 2), "utf-8"); + settingsCache = settings; +} + +// --------------------------------------------------------------------------- +// Public API +// --------------------------------------------------------------------------- + +export function listAdapterPlugins(): AdapterPluginRecord[] { + return readStore(); +} + +export function addAdapterPlugin(record: AdapterPluginRecord): void { + const store = [...readStore()]; + const idx = store.findIndex((r) => r.type === record.type); + if (idx >= 0) { + store[idx] = record; + } else { + store.push(record); + } + writeStore(store); +} + +export function removeAdapterPlugin(type: string): boolean { + const store = [...readStore()]; + const idx = store.findIndex((r) => r.type === type); + if (idx < 0) return false; + store.splice(idx, 1); + writeStore(store); + return true; +} + +export function getAdapterPluginByType(type: string): AdapterPluginRecord | undefined { + return readStore().find((r) => r.type === type); +} + +export function getAdapterPluginsDir(): string { + ensureDirs(); + return ADAPTER_PLUGINS_DIR; +} + +// --------------------------------------------------------------------------- +// Adapter enable/disable (settings) +// --------------------------------------------------------------------------- + +export function getDisabledAdapterTypes(): string[] { + return readSettings().disabledTypes; +} + +export function isAdapterDisabled(type: string): boolean { + return readSettings().disabledTypes.includes(type); +} + +export function setAdapterDisabled(type: string, disabled: boolean): boolean { + const settings = { ...readSettings(), disabledTypes: [...readSettings().disabledTypes] }; + const idx = settings.disabledTypes.indexOf(type); + + if (disabled && idx < 0) { + settings.disabledTypes.push(type); + writeSettings(settings); + return true; + } + if (!disabled && idx >= 0) { + settings.disabledTypes.splice(idx, 1); + writeSettings(settings); + return true; + } + return false; +} diff --git a/server/src/services/agents.ts b/server/src/services/agents.ts index 17d2e46d7e6..abba741491c 100644 --- a/server/src/services/agents.ts +++ b/server/src/services/agents.ts @@ -1,5 +1,5 @@ import { createHash, randomBytes } from "node:crypto"; -import { and, desc, eq, gte, inArray, lt, ne, sql } from "drizzle-orm"; +import { and, desc, eq, gte, inArray, lt, ne, or, sql } from "drizzle-orm"; import type { Db } from "@paperclipai/db"; import { agents, @@ -8,9 +8,13 @@ import { agentRuntimeState, agentTaskSessions, agentWakeupRequests, + activityLog, costEvents, heartbeatRunEvents, heartbeatRuns, + issueExecutionDecisions, + issues, + issueComments, } from "@paperclipai/db"; import { isUuidLike, normalizeAgentUrlKey } from "@paperclipai/shared"; import { conflict, notFound, unprocessable } from "../errors.js"; @@ -474,8 +478,20 @@ export function agentService(db: Db) { return db.transaction(async (tx) => { await tx.update(agents).set({ reportsTo: null }).where(eq(agents.reportsTo, id)); + await tx + .update(issues) + .set({ assigneeAgentId: null, createdByAgentId: null }) + .where(or(eq(issues.assigneeAgentId, id), eq(issues.createdByAgentId, id))); await tx.delete(heartbeatRunEvents).where(eq(heartbeatRunEvents.agentId, id)); await tx.delete(agentTaskSessions).where(eq(agentTaskSessions.agentId, id)); + await tx.delete(activityLog).where( + or( + eq(activityLog.agentId, id), + sql`${activityLog.runId} in (select ${heartbeatRuns.id} from ${heartbeatRuns} where ${heartbeatRuns.agentId} = ${id})`, + ), + ); + await tx.delete(issueExecutionDecisions).where(eq(issueExecutionDecisions.actorAgentId, id)); + await tx.delete(issueComments).where(eq(issueComments.authorAgentId, id)); await tx.delete(heartbeatRuns).where(eq(heartbeatRuns.agentId, id)); await tx.delete(agentWakeupRequests).where(eq(agentWakeupRequests.agentId, id)); await tx.delete(agentApiKeys).where(eq(agentApiKeys.agentId, id)); diff --git a/server/src/services/companies.ts b/server/src/services/companies.ts index 893bea9e9bc..dcdeb27dd3c 100644 --- a/server/src/services/companies.ts +++ b/server/src/services/companies.ts @@ -17,6 +17,7 @@ import { heartbeatRunEvents, costEvents, financeEvents, + issueReadStates, approvalComments, approvals, activityLog, @@ -25,6 +26,7 @@ import { invites, principalPermissionGrants, companyMemberships, + companySkills, } from "@paperclipai/db"; import { notFound, unprocessable } from "../errors.js"; @@ -41,6 +43,10 @@ export function companyService(db: Db) { budgetMonthlyCents: companies.budgetMonthlyCents, spentMonthlyCents: companies.spentMonthlyCents, requireBoardApprovalForNewAgents: companies.requireBoardApprovalForNewAgents, + feedbackDataSharingEnabled: companies.feedbackDataSharingEnabled, + feedbackDataSharingConsentAt: companies.feedbackDataSharingConsentAt, + feedbackDataSharingConsentByUserId: companies.feedbackDataSharingConsentByUserId, + feedbackDataSharingTermsVersion: companies.feedbackDataSharingTermsVersion, brandColor: companies.brandColor, logoAssetId: companyLogos.assetId, createdAt: companies.createdAt, @@ -256,6 +262,7 @@ export function companyService(db: Db) { // Delete from child tables in dependency order await tx.delete(heartbeatRunEvents).where(eq(heartbeatRunEvents.companyId, id)); await tx.delete(agentTaskSessions).where(eq(agentTaskSessions.companyId, id)); + await tx.delete(activityLog).where(eq(activityLog.companyId, id)); 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)); @@ -270,13 +277,14 @@ export function companyService(db: Db) { 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)); + await tx.delete(companySkills).where(eq(companySkills.companyId, id)); + await tx.delete(issueReadStates).where(eq(issueReadStates.companyId, id)); await tx.delete(issues).where(eq(issues.companyId, id)); await tx.delete(companyLogos).where(eq(companyLogos.companyId, id)); await tx.delete(assets).where(eq(assets.companyId, id)); 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)); - await tx.delete(activityLog).where(eq(activityLog.companyId, id)); const rows = await tx .delete(companies) .where(eq(companies.id, id)) diff --git a/server/src/services/company-portability.ts b/server/src/services/company-portability.ts index db4be18a812..9dc3262fb4e 100644 --- a/server/src/services/company-portability.ts +++ b/server/src/services/company-portability.ts @@ -27,6 +27,8 @@ import type { CompanyPortabilitySidebarOrder, CompanyPortabilitySkillManifestEntry, CompanySkill, + AgentEnvConfig, + RoutineVariable, } from "@paperclipai/shared"; import { ISSUE_PRIORITIES, @@ -38,6 +40,7 @@ import { ROUTINE_TRIGGER_KINDS, ROUTINE_TRIGGER_SIGNING_MODES, deriveProjectUrlKey, + envConfigSchema, normalizeAgentUrlKey, } from "@paperclipai/shared"; import { @@ -45,6 +48,7 @@ import { writePaperclipSkillSyncPreference, } from "@paperclipai/adapter-utils/server-utils"; import { notFound, unprocessable } from "../errors.js"; +import { ghFetch, gitHubApiBase, resolveRawGitHubUrl } from "./github-fetch.js"; import type { StorageService } from "../storage/types.js"; import { accessService } from "./access.js"; import { agentService } from "./agents.js"; @@ -385,6 +389,88 @@ function isSensitiveEnvKey(key: string) { ); } +function normalizePortableProjectEnv(value: unknown): AgentEnvConfig | null { + const parsed = envConfigSchema.safeParse(value); + return parsed.success ? parsed.data : null; +} + +function extractPortableScopedEnvInputs( + scope: { + label: string; + warningPrefix: string; + agentSlug: string | null; + projectSlug: string | null; + }, + envValue: unknown, + warnings: string[], +): CompanyPortabilityEnvInput[] { + if (!isPlainRecord(envValue)) return []; + const env = envValue as Record; + const inputs: CompanyPortabilityEnvInput[] = []; + + for (const [key, binding] of Object.entries(env)) { + if (key.toUpperCase() === "PATH") { + warnings.push(`${scope.warningPrefix} PATH override was omitted from export because it is system-dependent.`); + continue; + } + + if (isPlainRecord(binding) && binding.type === "secret_ref") { + inputs.push({ + key, + description: `Provide ${key} for ${scope.label}`, + agentSlug: scope.agentSlug, + projectSlug: scope.projectSlug, + kind: "secret", + requirement: "optional", + defaultValue: "", + portability: "portable", + }); + continue; + } + + if (isPlainRecord(binding) && binding.type === "plain") { + const defaultValue = asString(binding.value); + const isSensitive = isSensitiveEnvKey(key); + const portability = defaultValue && isAbsoluteCommand(defaultValue) + ? "system_dependent" + : "portable"; + if (portability === "system_dependent") { + warnings.push(`${scope.warningPrefix} env ${key} default was exported as system-dependent.`); + } + inputs.push({ + key, + description: `Optional default for ${key} on ${scope.label}`, + agentSlug: scope.agentSlug, + projectSlug: scope.projectSlug, + kind: isSensitive ? "secret" : "plain", + requirement: "optional", + defaultValue: isSensitive ? "" : defaultValue ?? "", + portability, + }); + continue; + } + + if (typeof binding === "string") { + const portability = isAbsoluteCommand(binding) ? "system_dependent" : "portable"; + if (portability === "system_dependent") { + warnings.push(`${scope.warningPrefix} env ${key} default was exported as system-dependent.`); + } + inputs.push({ + key, + description: `Optional default for ${key} on ${scope.label}`, + agentSlug: scope.agentSlug, + projectSlug: scope.projectSlug, + kind: isSensitiveEnvKey(key) ? "secret" : "plain", + requirement: "optional", + defaultValue: isSensitiveEnvKey(key) ? "" : binding, + portability, + }); + } + } + + return inputs; +} + type ResolvedSource = { manifest: CompanyPortabilityManifest; files: Record; @@ -417,6 +503,7 @@ type ProjectLike = { targetDate: string | null; color: string | null; status: string; + env: Record | null; executionWorkspacePolicy: Record | null; workspaces?: Array<{ id: string; @@ -522,7 +609,7 @@ const ADAPTER_DEFAULT_RULES_BY_TYPE: Record asString(entry)).filter((entry): entry is string => Boolean(entry)) + : []; + const defaultValue = + typeof value.defaultValue === "string" || typeof value.defaultValue === "number" || typeof value.defaultValue === "boolean" + ? value.defaultValue + : null; + return { + name, + label: asString(value.label), + type: type as RoutineVariable["type"], + defaultValue, + required: asBoolean(value.required) ?? true, + options, + }; +} + function normalizeRoutineExtension(value: unknown): CompanyPortabilityIssueRoutineManifestEntry | null { if (!isPlainRecord(value)) return null; const triggers = Array.isArray(value.triggers) @@ -574,9 +684,15 @@ function normalizeRoutineExtension(value: unknown): CompanyPortabilityIssueRouti .map((entry) => normalizeRoutineTriggerExtension(entry)) .filter((entry): entry is CompanyPortabilityIssueRoutineTriggerManifestEntry => entry !== null) : []; + const variables = Array.isArray(value.variables) + ? value.variables + .map((entry) => normalizeRoutineVariableExtension(entry)) + .filter((entry): entry is RoutineVariable => entry !== null) + : null; const routine = { concurrencyPolicy: asString(value.concurrencyPolicy), catchUpPolicy: asString(value.catchUpPolicy), + variables, triggers, }; return stripEmptyValues(routine) ? routine : null; @@ -586,6 +702,7 @@ function buildRoutineManifestFromLiveRoutine(routine: RoutineLike): CompanyPorta return { concurrencyPolicy: routine.concurrencyPolicy, catchUpPolicy: routine.catchUpPolicy, + variables: routine.variables, triggers: routine.triggers.map((trigger) => ({ kind: trigger.kind, label: trigger.label ?? null, @@ -1085,11 +1202,13 @@ function resolvePortableRoutineDefinition( ? { concurrencyPolicy: issue.routine.concurrencyPolicy, catchUpPolicy: issue.routine.catchUpPolicy, + variables: issue.routine.variables ?? null, triggers: [...issue.routine.triggers], } : { concurrencyPolicy: null, catchUpPolicy: null, + variables: null, triggers: [] as CompanyPortabilityIssueRoutineTriggerManifestEntry[], }; @@ -1494,68 +1613,33 @@ function extractPortableEnvInputs( envValue: unknown, warnings: string[], ): CompanyPortabilityEnvInput[] { - if (!isPlainRecord(envValue)) return []; - const env = envValue as Record; - const inputs: CompanyPortabilityEnvInput[] = []; + return extractPortableScopedEnvInputs( + { + label: `agent ${agentSlug}`, + warningPrefix: `Agent ${agentSlug}`, + agentSlug, + projectSlug: null, + }, + envValue, + warnings, + ); +} - for (const [key, binding] of Object.entries(env)) { - if (key.toUpperCase() === "PATH") { - warnings.push(`Agent ${agentSlug} PATH override was omitted from export because it is system-dependent.`); - continue; - } - - if (isPlainRecord(binding) && binding.type === "secret_ref") { - inputs.push({ - key, - description: `Provide ${key} for agent ${agentSlug}`, - agentSlug, - kind: "secret", - requirement: "optional", - defaultValue: "", - portability: "portable", - }); - continue; - } - - if (isPlainRecord(binding) && binding.type === "plain") { - const defaultValue = asString(binding.value); - const isSensitive = isSensitiveEnvKey(key); - const portability = defaultValue && isAbsoluteCommand(defaultValue) - ? "system_dependent" - : "portable"; - if (portability === "system_dependent") { - warnings.push(`Agent ${agentSlug} env ${key} default was exported as system-dependent.`); - } - inputs.push({ - key, - description: `Optional default for ${key} on agent ${agentSlug}`, - agentSlug, - kind: isSensitive ? "secret" : "plain", - requirement: "optional", - defaultValue: isSensitive ? "" : defaultValue ?? "", - portability, - }); - continue; - } - - if (typeof binding === "string") { - const portability = isAbsoluteCommand(binding) ? "system_dependent" : "portable"; - if (portability === "system_dependent") { - warnings.push(`Agent ${agentSlug} env ${key} default was exported as system-dependent.`); - } - inputs.push({ - key, - description: `Optional default for ${key} on agent ${agentSlug}`, - agentSlug, - kind: isSensitiveEnvKey(key) ? "secret" : "plain", - requirement: "optional", - defaultValue: binding, - portability, - }); - } - } - - return inputs; +function extractPortableProjectEnvInputs( + projectSlug: string, + envValue: unknown, + warnings: string[], +): CompanyPortabilityEnvInput[] { + return extractPortableScopedEnvInputs( + { + label: `project ${projectSlug}`, + warningPrefix: `Project ${projectSlug}`, + agentSlug: null, + projectSlug, + }, + envValue, + warnings, + ); } function jsonEqual(left: unknown, right: unknown): boolean { @@ -2101,7 +2185,7 @@ function parseFrontmatterMarkdown(raw: string): MarkdownDoc { } async function fetchText(url: string) { - const response = await fetch(url); + const response = await ghFetch(url); if (!response.ok) { throw unprocessable(`Failed to fetch ${url}: ${response.status}`); } @@ -2109,7 +2193,7 @@ async function fetchText(url: string) { } async function fetchOptionalText(url: string) { - const response = await fetch(url); + const response = await ghFetch(url); if (response.status === 404) return null; if (!response.ok) { throw unprocessable(`Failed to fetch ${url}: ${response.status}`); @@ -2118,7 +2202,7 @@ async function fetchOptionalText(url: string) { } async function fetchBinary(url: string) { - const response = await fetch(url); + const response = await ghFetch(url); if (!response.ok) { throw unprocessable(`Failed to fetch ${url}: ${response.status}`); } @@ -2126,7 +2210,7 @@ async function fetchBinary(url: string) { } async function fetchJson(url: string): Promise { - const response = await fetch(url, { + const response = await ghFetch(url, { headers: { accept: "application/vnd.github+json", }, @@ -2141,7 +2225,7 @@ function dedupeEnvInputs(values: CompanyPortabilityManifest["envInputs"]) { const seen = new Set(); const out: CompanyPortabilityManifest["envInputs"] = []; for (const value of values) { - const key = `${value.agentSlug ?? ""}:${value.key.toUpperCase()}`; + const key = `${value.agentSlug ?? ""}:${value.projectSlug ?? ""}:${value.key.toUpperCase()}`; if (seen.has(key)) continue; seen.add(key); out.push(value); @@ -2198,6 +2282,31 @@ function readAgentEnvInputs( key, description: asString(record.description) ?? null, agentSlug, + projectSlug: null, + kind: record.kind === "plain" ? "plain" : "secret", + requirement: record.requirement === "required" ? "required" : "optional", + defaultValue: typeof record.default === "string" ? record.default : null, + portability: record.portability === "system_dependent" ? "system_dependent" : "portable", + }]; + }); +} + +function readProjectEnvInputs( + extension: Record, + projectSlug: string, +): CompanyPortabilityManifest["envInputs"] { + const inputs = isPlainRecord(extension.inputs) ? extension.inputs : null; + const env = inputs && isPlainRecord(inputs.env) ? inputs.env : null; + if (!env) return []; + + return Object.entries(env).flatMap(([key, value]) => { + if (!isPlainRecord(value)) return []; + const record = value as EnvInputRecord; + return [{ + key, + description: asString(record.description) ?? null, + agentSlug: null, + projectSlug, kind: record.kind === "plain" ? "plain" : "secret", requirement: record.requirement === "required" ? "required" : "optional", defaultValue: typeof record.default === "string" ? record.default : null, @@ -2288,7 +2397,7 @@ function buildManifestFromPackageFiles( const skillPaths = Array.from(new Set([...referencedSkillPaths, ...discoveredSkillPaths])).sort(); const manifest: CompanyPortabilityManifest = { - schemaVersion: 4, + schemaVersion: 5, generatedAt: new Date().toISOString(), source: opts?.sourceLabel ?? null, includes: { @@ -2308,6 +2417,18 @@ function buildManifestFromPackageFiles( typeof paperclipCompany.requireBoardApprovalForNewAgents === "boolean" ? paperclipCompany.requireBoardApprovalForNewAgents : readCompanyApprovalDefault(companyFrontmatter), + feedbackDataSharingEnabled: + typeof paperclipCompany.feedbackDataSharingEnabled === "boolean" + ? paperclipCompany.feedbackDataSharingEnabled + : false, + feedbackDataSharingConsentAt: + typeof paperclipCompany.feedbackDataSharingConsentAt === "string" + ? paperclipCompany.feedbackDataSharingConsentAt + : null, + feedbackDataSharingConsentByUserId: + asString(paperclipCompany.feedbackDataSharingConsentByUserId), + feedbackDataSharingTermsVersion: + asString(paperclipCompany.feedbackDataSharingTermsVersion), }, sidebar: paperclipSidebar, agents: [], @@ -2347,7 +2468,7 @@ function buildManifestFromPackageFiles( name: asString(frontmatter.name) ?? title ?? slug, path: agentPath, skills: readAgentSkillRefs(frontmatter), - role: asString(extension.role) ?? "agent", + role: asString(extension.role) ?? asString(frontmatter.role) ?? "agent", title, icon: asString(extension.icon), capabilities: asString(extension.capabilities), @@ -2411,14 +2532,16 @@ function buildManifestFromPackageFiles( const repoPath = asString(primarySource?.path); const commit = asString(primarySource?.commit); const trackingRef = asString(primarySource?.trackingRef); + const sourceHostname = asString(primarySource?.hostname) || "github.com"; const [owner, repoName] = (repo ?? "").split("/"); sourceType = "github"; sourceLocator = asString(primarySource?.url) - ?? (repo ? `https://github.com/${repo}${repoPath ? `/tree/${trackingRef ?? commit ?? "main"}/${repoPath}` : ""}` : null); + ?? (repo ? `https://${sourceHostname}/${repo}${repoPath ? `/tree/${trackingRef ?? commit ?? "main"}/${repoPath}` : ""}` : null); sourceRef = commit; normalizedMetadata = owner && repoName ? { sourceKind: "github", + ...(sourceHostname !== "github.com" ? { hostname: sourceHostname } : {}), owner, repo: repoName, ref: commit, @@ -2483,12 +2606,14 @@ function buildManifestFromPackageFiles( targetDate: asString(extension.targetDate), color: asString(extension.color), status: asString(extension.status), + env: normalizePortableProjectEnv(extension.env), executionWorkspacePolicy: isPlainRecord(extension.executionWorkspacePolicy) ? extension.executionWorkspacePolicy : null, workspaces, metadata: isPlainRecord(extension.metadata) ? extension.metadata : null, }); + manifest.envInputs.push(...readProjectEnvInputs(extension, slug)); if (frontmatter.kind && frontmatter.kind !== "project") { warnings.push(`Project markdown ${projectPath} does not declare kind: project in frontmatter.`); } @@ -2564,9 +2689,10 @@ function normalizeGitHubSourcePath(value: string | null | undefined) { export function parseGitHubSourceUrl(rawUrl: string) { const url = new URL(rawUrl); - if (url.hostname !== "github.com") { - throw unprocessable("GitHub source must use github.com URL"); + if (url.protocol !== "https:") { + throw unprocessable("GitHub source URL must use HTTPS"); } + const hostname = url.hostname; const parts = url.pathname.split("/").filter(Boolean); if (parts.length < 2) { throw unprocessable("Invalid GitHub URL"); @@ -2584,6 +2710,7 @@ export function parseGitHubSourceUrl(rawUrl: string) { if (basePath === ".") basePath = ""; } return { + hostname, owner, repo, ref: queryRef || "main", @@ -2607,13 +2734,9 @@ export function parseGitHubSourceUrl(rawUrl: string) { basePath = path.posix.dirname(blobPath); if (basePath === ".") basePath = ""; } - return { owner, repo, ref, basePath, companyPath }; + return { hostname, owner, repo, ref, basePath, companyPath }; } -function resolveRawGitHubUrl(owner: string, repo: string, ref: string, filePath: string) { - const normalizedFilePath = filePath.replace(/^\/+/, ""); - return `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${normalizedFilePath}`; -} export function companyPortabilityService(db: Db, storage?: StorageService) { const companies = companyService(db); @@ -2641,14 +2764,14 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { let companyMarkdown: string | null = null; try { companyMarkdown = await fetchOptionalText( - resolveRawGitHubUrl(parsed.owner, parsed.repo, ref, companyRelativePath), + resolveRawGitHubUrl(parsed.hostname, parsed.owner, parsed.repo, ref, companyRelativePath), ); } catch (err) { if (ref === "main") { ref = "master"; warnings.push("GitHub ref main not found; falling back to master."); companyMarkdown = await fetchOptionalText( - resolveRawGitHubUrl(parsed.owner, parsed.repo, ref, companyRelativePath), + resolveRawGitHubUrl(parsed.hostname, parsed.owner, parsed.repo, ref, companyRelativePath), ); } else { throw err; @@ -2664,8 +2787,9 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { const files: Record = { [companyPath]: companyMarkdown, }; + const apiBase = gitHubApiBase(parsed.hostname); const tree = await fetchJson<{ tree?: Array<{ path: string; type: string }> }>( - `https://api.github.com/repos/${parsed.owner}/${parsed.repo}/git/trees/${ref}?recursive=1`, + `${apiBase}/repos/${parsed.owner}/${parsed.repo}/git/trees/${ref}?recursive=1`, ).catch(() => ({ tree: [] })); const basePrefix = parsed.basePath ? `${parsed.basePath.replace(/^\/+|\/+$/g, "")}/` : ""; const candidatePaths = (tree.tree ?? []) @@ -2686,7 +2810,7 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { const relativePath = basePrefix ? repoPath.slice(basePrefix.length) : repoPath; if (files[relativePath] !== undefined) continue; files[normalizePortablePath(relativePath)] = await fetchText( - resolveRawGitHubUrl(parsed.owner, parsed.repo, ref, repoPath), + resolveRawGitHubUrl(parsed.hostname, parsed.owner, parsed.repo, ref, repoPath), ); } const companyDoc = parseFrontmatterMarkdown(companyMarkdown); @@ -2697,7 +2821,7 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { if (files[relativePath] !== undefined) continue; if (!(repoPath.endsWith(".md") || repoPath.endsWith(".yaml") || repoPath.endsWith(".yml"))) continue; files[relativePath] = await fetchText( - resolveRawGitHubUrl(parsed.owner, parsed.repo, ref, repoPath), + resolveRawGitHubUrl(parsed.hostname, parsed.owner, parsed.repo, ref, repoPath), ); } @@ -2707,7 +2831,7 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { const repoPath = [parsed.basePath, companyLogoPath].filter(Boolean).join("/"); try { const binary = await fetchBinary( - resolveRawGitHubUrl(parsed.owner, parsed.repo, ref, repoPath), + resolveRawGitHubUrl(parsed.hostname, parsed.owner, parsed.repo, ref, repoPath), ); resolved.files[companyLogoPath] = bufferToPortableBinaryFile(binary, inferContentTypeFromPath(companyLogoPath)); } catch (err) { @@ -3097,6 +3221,14 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { for (const project of selectedProjectRows) { const slug = projectSlugById.get(project.id)!; const projectPath = `projects/${slug}/PROJECT.md`; + const envInputsStart = envInputs.length; + const exportedEnvInputs = extractPortableProjectEnvInputs(slug, project.env, warnings); + envInputs.push(...exportedEnvInputs); + const projectEnvInputs = dedupeEnvInputs( + envInputs + .slice(envInputsStart) + .filter((inputValue) => inputValue.projectSlug === slug), + ); const portableWorkspaces = await buildPortableProjectWorkspaces(slug, project.workspaces, warnings); projectWorkspaceKeyByProjectId.set(project.id, portableWorkspaces.workspaceKeyById); files[projectPath] = buildMarkdown( @@ -3120,6 +3252,11 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { ) ?? undefined, workspaces: portableWorkspaces.extension, }); + if (isPlainRecord(extension) && projectEnvInputs.length > 0) { + extension.inputs = { + env: buildEnvInputMap(projectEnvInputs), + }; + } paperclipProjectsOut[slug] = isPlainRecord(extension) ? extension : {}; } @@ -3173,9 +3310,9 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { for (const routine of selectedRoutineRows) { const taskSlug = taskSlugByRoutineId.get(routine.id)!; - const projectSlug = projectSlugById.get(routine.projectId) ?? null; + const projectSlug = routine.projectId ? (projectSlugById.get(routine.projectId) ?? null) : null; const taskPath = `tasks/${taskSlug}/TASK.md`; - const assigneeSlug = idToSlug.get(routine.assigneeAgentId) ?? null; + const assigneeSlug = routine.assigneeAgentId ? (idToSlug.get(routine.assigneeAgentId) ?? null) : null; files[taskPath] = buildMarkdown( { name: routine.title, @@ -3190,6 +3327,7 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { priority: routine.priority !== "medium" ? routine.priority : undefined, concurrencyPolicy: routine.concurrencyPolicy !== "coalesce_if_active" ? routine.concurrencyPolicy : undefined, catchUpPolicy: routine.catchUpPolicy !== "skip_missed" ? routine.catchUpPolicy : undefined, + variables: (routine.variables ?? []).length > 0 ? routine.variables : undefined, triggers: routine.triggers.map((trigger) => stripEmptyValues({ kind: trigger.kind, label: trigger.label ?? null, @@ -3225,6 +3363,10 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { brandColor: company.brandColor ?? null, logoPath: companyLogoPath, requireBoardApprovalForNewAgents: company.requireBoardApprovalForNewAgents ? undefined : false, + feedbackDataSharingEnabled: company.feedbackDataSharingEnabled ? true : undefined, + feedbackDataSharingConsentAt: company.feedbackDataSharingConsentAt?.toISOString() ?? null, + feedbackDataSharingConsentByUserId: company.feedbackDataSharingConsentByUserId ?? null, + feedbackDataSharingTermsVersion: company.feedbackDataSharingTermsVersion ?? null, }), sidebar: stripEmptyValues(sidebarOrder), agents: Object.keys(paperclipAgents).length > 0 ? paperclipAgents : undefined, @@ -3454,7 +3596,12 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { for (const envInput of manifest.envInputs) { if (envInput.portability === "system_dependent") { - warnings.push(`Environment input ${envInput.key}${envInput.agentSlug ? ` for ${envInput.agentSlug}` : ""} is system-dependent and may need manual adjustment after import.`); + const scope = envInput.agentSlug + ? ` for agent ${envInput.agentSlug}` + : envInput.projectSlug + ? ` for project ${envInput.projectSlug}` + : ""; + warnings.push(`Environment input ${envInput.key}${scope} is system-dependent and may need manual adjustment after import.`); } } @@ -3734,6 +3881,18 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { requireBoardApprovalForNewAgents: include.company ? (sourceManifest.company?.requireBoardApprovalForNewAgents ?? true) : true, + feedbackDataSharingEnabled: include.company + ? (sourceManifest.company?.feedbackDataSharingEnabled ?? false) + : false, + feedbackDataSharingConsentAt: include.company && sourceManifest.company?.feedbackDataSharingConsentAt + ? new Date(sourceManifest.company.feedbackDataSharingConsentAt) + : null, + feedbackDataSharingConsentByUserId: include.company + ? (sourceManifest.company?.feedbackDataSharingConsentByUserId ?? null) + : null, + feedbackDataSharingTermsVersion: include.company + ? (sourceManifest.company?.feedbackDataSharingTermsVersion ?? null) + : null, }); if (mode === "agent_safe" && options?.sourceCompanyId) { await access.copyActiveUserMemberships(options.sourceCompanyId, created.id); @@ -3751,6 +3910,12 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { description: sourceManifest.company.description, brandColor: sourceManifest.company.brandColor, requireBoardApprovalForNewAgents: sourceManifest.company.requireBoardApprovalForNewAgents, + feedbackDataSharingEnabled: sourceManifest.company.feedbackDataSharingEnabled, + feedbackDataSharingConsentAt: sourceManifest.company.feedbackDataSharingConsentAt + ? new Date(sourceManifest.company.feedbackDataSharingConsentAt) + : null, + feedbackDataSharingConsentByUserId: sourceManifest.company.feedbackDataSharingConsentByUserId, + feedbackDataSharingTermsVersion: sourceManifest.company.feedbackDataSharingTermsVersion, }); targetCompany = updated ?? targetCompany; companyAction = "updated"; @@ -4025,6 +4190,7 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { status: manifestProject.status && PROJECT_STATUSES.includes(manifestProject.status as any) ? manifestProject.status as typeof PROJECT_STATUSES[number] : "backlog", + env: manifestProject.env, executionWorkspacePolicy: stripPortableProjectExecutionWorkspaceRefs(manifestProject.executionWorkspacePolicy), }; @@ -4137,6 +4303,7 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { const routineDefinition = resolvedRoutine.routine ?? { concurrencyPolicy: null, catchUpPolicy: null, + variables: null, triggers: [], }; const createdRoutine = await routines.create(targetCompany.id, { @@ -4160,6 +4327,7 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { routineDefinition.catchUpPolicy && ROUTINE_CATCH_UP_POLICIES.includes(routineDefinition.catchUpPolicy as any) ? routineDefinition.catchUpPolicy as typeof ROUTINE_CATCH_UP_POLICIES[number] : "skip_missed", + variables: routineDefinition.variables ?? [], }, { agentId: null, userId: actorUserId ?? null, diff --git a/server/src/services/company-skills.ts b/server/src/services/company-skills.ts index 2b97da208c5..60fc06b40f5 100644 --- a/server/src/services/company-skills.ts +++ b/server/src/services/company-skills.ts @@ -5,7 +5,7 @@ import { fileURLToPath } from "node:url"; import { and, asc, eq } from "drizzle-orm"; import type { Db } from "@paperclipai/db"; import { companySkills } from "@paperclipai/db"; -import { readPaperclipSkillSyncPreference, writePaperclipSkillSyncPreference } from "@paperclipai/adapter-utils/server-utils"; +import { readPaperclipSkillSyncPreference } from "@paperclipai/adapter-utils/server-utils"; import type { PaperclipSkillEntry } from "@paperclipai/adapter-utils/server-utils"; import type { CompanySkill, @@ -27,9 +27,10 @@ import type { CompanySkillUsageAgent, } from "@paperclipai/shared"; import { normalizeAgentUrlKey } from "@paperclipai/shared"; -import { findServerAdapter } from "../adapters/index.js"; +import { findActiveServerAdapter } from "../adapters/index.js"; import { resolvePaperclipInstanceRoot } from "../home-paths.js"; import { notFound, unprocessable } from "../errors.js"; +import { ghFetch, gitHubApiBase, resolveRawGitHubUrl } from "./github-fetch.js"; import { agentService } from "./agents.js"; import { projectService } from "./projects.js"; import { secretService } from "./secrets.js"; @@ -73,6 +74,7 @@ type ParsedSkillImportSource = { type SkillSourceMeta = { skillKey?: string; sourceKind?: string; + hostname?: string; owner?: string; repo?: string; ref?: string; @@ -470,7 +472,7 @@ function parseFrontmatterMarkdown(raw: string): { frontmatter: Record(url: string): Promise { - const response = await fetch(url, { + const response = await ghFetch(url, { headers: { accept: "application/vnd.github+json", }, @@ -489,16 +491,17 @@ async function fetchJson(url: string): Promise { return response.json() as Promise; } -async function resolveGitHubDefaultBranch(owner: string, repo: string) { + +async function resolveGitHubDefaultBranch(owner: string, repo: string, apiBase: string) { const response = await fetchJson<{ default_branch?: string }>( - `https://api.github.com/repos/${owner}/${repo}`, + `${apiBase}/repos/${owner}/${repo}`, ); return asString(response.default_branch) ?? "main"; } -async function resolveGitHubCommitSha(owner: string, repo: string, ref: string) { +async function resolveGitHubCommitSha(owner: string, repo: string, ref: string, apiBase: string) { const response = await fetchJson<{ sha?: string }>( - `https://api.github.com/repos/${owner}/${repo}/commits/${encodeURIComponent(ref)}`, + `${apiBase}/repos/${owner}/${repo}/commits/${encodeURIComponent(ref)}`, ); const sha = asString(response.sha); if (!sha) { @@ -509,8 +512,8 @@ async function resolveGitHubCommitSha(owner: string, repo: string, ref: string) function parseGitHubSourceUrl(rawUrl: string) { const url = new URL(rawUrl); - if (url.hostname !== "github.com") { - throw unprocessable("GitHub source must use github.com URL"); + if (url.protocol !== "https:") { + throw unprocessable("GitHub source URL must use HTTPS"); } const parts = url.pathname.split("/").filter(Boolean); if (parts.length < 2) { @@ -532,10 +535,11 @@ function parseGitHubSourceUrl(rawUrl: string) { basePath = filePath ? path.posix.dirname(filePath) : ""; explicitRef = true; } - return { owner, repo, ref, basePath, filePath, explicitRef }; + return { hostname: url.hostname, owner, repo, ref, basePath, filePath, explicitRef }; } async function resolveGitHubPinnedRef(parsed: ReturnType) { + const apiBase = gitHubApiBase(parsed.hostname); if (/^[0-9a-f]{40}$/i.test(parsed.ref.trim())) { return { pinnedRef: parsed.ref, @@ -545,14 +549,11 @@ async function resolveGitHubPinnedRef(parsed: ReturnType { const url = sourceUrl.trim(); const warnings: string[] = []; - if (url.includes("github.com/")) { + const looksLikeRepoUrl = (() => { try { + const parsed = new URL(url); + if (parsed.protocol !== "https:") return false; + const h = parsed.hostname.toLowerCase(); + if (h.endsWith(".githubusercontent.com") || h === "gist.github.com") return false; + const segments = parsed.pathname.split("/").filter(Boolean); + return segments.length >= 2 && !parsed.pathname.endsWith(".md"); + } catch { return false; } })(); + if (looksLikeRepoUrl) { const parsed = parseGitHubSourceUrl(url); + const apiBase = gitHubApiBase(parsed.hostname); const { pinnedRef, trackingRef } = await resolveGitHubPinnedRef(parsed); let ref = pinnedRef; const tree = await fetchJson<{ tree?: Array<{ path: string; type: string }> }>( - `https://api.github.com/repos/${parsed.owner}/${parsed.repo}/git/trees/${ref}?recursive=1`, + `${apiBase}/repos/${parsed.owner}/${parsed.repo}/git/trees/${ref}?recursive=1`, ).catch(() => { throw unprocessable(`Failed to read GitHub tree for ${url}`); }); @@ -1013,7 +1025,7 @@ async function readUrlSkillImports( const skills: ImportedSkill[] = []; for (const relativeSkillPath of skillPaths) { const repoSkillPath = basePrefix ? `${basePrefix}${relativeSkillPath}` : relativeSkillPath; - const markdown = await fetchText(resolveRawGitHubUrl(parsed.owner, parsed.repo, ref, repoSkillPath)); + const markdown = await fetchText(resolveRawGitHubUrl(parsed.hostname, parsed.owner, parsed.repo, ref, repoSkillPath)); const parsedMarkdown = parseFrontmatterMarkdown(markdown); const skillDir = path.posix.dirname(relativeSkillPath); const slug = deriveImportedSkillSlug(parsedMarkdown.frontmatter, path.posix.basename(skillDir)); @@ -1027,9 +1039,10 @@ async function readUrlSkillImports( const metadata = { ...(skillKey ? { skillKey } : {}), sourceKind: "github", + ...(parsed.hostname !== "github.com" ? { hostname: parsed.hostname } : {}), owner: parsed.owner, repo: parsed.repo, - ref: ref, + ref, trackingRef, repoSkillDir: normalizeGitHubSkillDirectory( basePrefix ? `${basePrefix}${skillDir}` : skillDir, @@ -1562,7 +1575,7 @@ export function companySkillService(db: Db) { return Promise.all( desiredAgents.map(async (agent) => { - const adapter = findServerAdapter(agent.adapterType); + const adapter = findActiveServerAdapter(agent.adapterType); let actualState: string | null = null; if (!adapter?.listSkills) { @@ -1641,7 +1654,9 @@ export function companySkillService(db: Db) { }; } - const latestRef = await resolveGitHubCommitSha(owner, repo, trackingRef); + const hostname = asString(metadata.hostname) || "github.com"; + const apiBase = gitHubApiBase(hostname); + const latestRef = await resolveGitHubCommitSha(owner, repo, trackingRef, apiBase); return { supported: true, reason: null, @@ -1679,13 +1694,14 @@ export function companySkillService(db: Db) { const metadata = getSkillMeta(skill); const owner = asString(metadata.owner); const repo = asString(metadata.repo); + const hostname = asString(metadata.hostname) || "github.com"; const ref = skill.sourceRef ?? asString(metadata.ref) ?? "main"; const repoSkillDir = normalizeGitHubSkillDirectory(asString(metadata.repoSkillDir), skill.slug); if (!owner || !repo) { throw unprocessable("Skill source metadata is incomplete."); } const repoPath = normalizePortablePath(path.posix.join(repoSkillDir, normalizedPath)); - content = await fetchText(resolveRawGitHubUrl(owner, repo, ref, repoPath)); + content = await fetchText(resolveRawGitHubUrl(hostname, owner, repo, ref, repoPath)); } else if (skill.sourceType === "url") { if (normalizedPath !== "SKILL.md") { throw notFound("This skill source only exposes SKILL.md"); @@ -2298,26 +2314,23 @@ export function companySkillService(db: Db) { if (!row) return null; const skill = toCompanySkill(row); + const usedByAgents = await usage(companyId, skill.key); - // Remove from any agent desiredSkills that reference this skill - const agentRows = await agents.list(companyId); - const allSkills = await listFull(companyId); - for (const agent of agentRows) { - const config = agent.adapterConfig as Record; - const preference = readPaperclipSkillSyncPreference(config); - const referencesSkill = preference.desiredSkills.some((ref) => { - const resolved = resolveSkillReference(allSkills, ref); - return resolved.skill?.id === skillId; - }); - if (referencesSkill) { - const filtered = preference.desiredSkills.filter((ref) => { - const resolved = resolveSkillReference(allSkills, ref); - return resolved.skill?.id !== skillId; - }); - await agents.update(agent.id, { - adapterConfig: writePaperclipSkillSyncPreference(config, filtered), - }); - } + if (usedByAgents.length > 0) { + const agentNames = usedByAgents.map((agent) => agent.name).sort((left, right) => left.localeCompare(right)); + throw unprocessable( + `Cannot delete skill "${skill.name}" while it is still used by ${agentNames.join(", ")}. Detach it from those agents first.`, + { + skillId: skill.id, + skillKey: skill.key, + usedByAgents: usedByAgents.map((agent) => ({ + id: agent.id, + name: agent.name, + urlKey: agent.urlKey, + adapterType: agent.adapterType, + })), + }, + ); } // Delete DB row diff --git a/server/src/services/documents.ts b/server/src/services/documents.ts index 99b091858b8..1a56af27d1c 100644 --- a/server/src/services/documents.ts +++ b/server/src/services/documents.ts @@ -64,50 +64,36 @@ function mapIssueDocumentRow( }; } +const issueDocumentSelect = { + id: documents.id, + companyId: documents.companyId, + issueId: issueDocuments.issueId, + key: issueDocuments.key, + title: documents.title, + format: documents.format, + latestBody: documents.latestBody, + latestRevisionId: documents.latestRevisionId, + latestRevisionNumber: documents.latestRevisionNumber, + createdByAgentId: documents.createdByAgentId, + createdByUserId: documents.createdByUserId, + updatedByAgentId: documents.updatedByAgentId, + updatedByUserId: documents.updatedByUserId, + createdAt: documents.createdAt, + updatedAt: documents.updatedAt, +}; + export function documentService(db: Db) { return { getIssueDocumentPayload: async (issue: { id: string; description: string | null }) => { const [planDocument, documentSummaries] = await Promise.all([ db - .select({ - id: documents.id, - companyId: documents.companyId, - issueId: issueDocuments.issueId, - key: issueDocuments.key, - title: documents.title, - format: documents.format, - latestBody: documents.latestBody, - latestRevisionId: documents.latestRevisionId, - latestRevisionNumber: documents.latestRevisionNumber, - createdByAgentId: documents.createdByAgentId, - createdByUserId: documents.createdByUserId, - updatedByAgentId: documents.updatedByAgentId, - updatedByUserId: documents.updatedByUserId, - createdAt: documents.createdAt, - updatedAt: documents.updatedAt, - }) + .select(issueDocumentSelect) .from(issueDocuments) .innerJoin(documents, eq(issueDocuments.documentId, documents.id)) .where(and(eq(issueDocuments.issueId, issue.id), eq(issueDocuments.key, "plan"))) .then((rows) => rows[0] ?? null), db - .select({ - id: documents.id, - companyId: documents.companyId, - issueId: issueDocuments.issueId, - key: issueDocuments.key, - title: documents.title, - format: documents.format, - latestBody: documents.latestBody, - latestRevisionId: documents.latestRevisionId, - latestRevisionNumber: documents.latestRevisionNumber, - createdByAgentId: documents.createdByAgentId, - createdByUserId: documents.createdByUserId, - updatedByAgentId: documents.updatedByAgentId, - updatedByUserId: documents.updatedByUserId, - createdAt: documents.createdAt, - updatedAt: documents.updatedAt, - }) + .select(issueDocumentSelect) .from(issueDocuments) .innerJoin(documents, eq(issueDocuments.documentId, documents.id)) .where(eq(issueDocuments.issueId, issue.id)) @@ -131,23 +117,7 @@ export function documentService(db: Db) { listIssueDocuments: async (issueId: string) => { const rows = await db - .select({ - id: documents.id, - companyId: documents.companyId, - issueId: issueDocuments.issueId, - key: issueDocuments.key, - title: documents.title, - format: documents.format, - latestBody: documents.latestBody, - latestRevisionId: documents.latestRevisionId, - latestRevisionNumber: documents.latestRevisionNumber, - createdByAgentId: documents.createdByAgentId, - createdByUserId: documents.createdByUserId, - updatedByAgentId: documents.updatedByAgentId, - updatedByUserId: documents.updatedByUserId, - createdAt: documents.createdAt, - updatedAt: documents.updatedAt, - }) + .select(issueDocumentSelect) .from(issueDocuments) .innerJoin(documents, eq(issueDocuments.documentId, documents.id)) .where(eq(issueDocuments.issueId, issueId)) @@ -158,23 +128,7 @@ export function documentService(db: Db) { getIssueDocumentByKey: async (issueId: string, rawKey: string) => { const key = normalizeDocumentKey(rawKey); const row = await db - .select({ - id: documents.id, - companyId: documents.companyId, - issueId: issueDocuments.issueId, - key: issueDocuments.key, - title: documents.title, - format: documents.format, - latestBody: documents.latestBody, - latestRevisionId: documents.latestRevisionId, - latestRevisionNumber: documents.latestRevisionNumber, - createdByAgentId: documents.createdByAgentId, - createdByUserId: documents.createdByUserId, - updatedByAgentId: documents.updatedByAgentId, - updatedByUserId: documents.updatedByUserId, - createdAt: documents.createdAt, - updatedAt: documents.updatedAt, - }) + .select(issueDocumentSelect) .from(issueDocuments) .innerJoin(documents, eq(issueDocuments.documentId, documents.id)) .where(and(eq(issueDocuments.issueId, issueId), eq(issueDocuments.key, key))) @@ -192,6 +146,8 @@ export function documentService(db: Db) { issueId: issueDocuments.issueId, key: issueDocuments.key, revisionNumber: documentRevisions.revisionNumber, + title: documentRevisions.title, + format: documentRevisions.format, body: documentRevisions.body, changeSummary: documentRevisions.changeSummary, createdByAgentId: documentRevisions.createdByAgentId, @@ -215,6 +171,7 @@ export function documentService(db: Db) { baseRevisionId?: string | null; createdByAgentId?: string | null; createdByUserId?: string | null; + createdByRunId?: string | null; }) => { const key = normalizeDocumentKey(input.key); const issue = await db @@ -269,10 +226,13 @@ export function documentService(db: Db) { companyId: issue.companyId, documentId: existing.id, revisionNumber: nextRevisionNumber, + title: input.title ?? null, + format: input.format, body: input.body, changeSummary: input.changeSummary ?? null, createdByAgentId: input.createdByAgentId ?? null, createdByUserId: input.createdByUserId ?? null, + createdByRunId: input.createdByRunId ?? null, createdAt: now, }) .returning(); @@ -340,10 +300,13 @@ export function documentService(db: Db) { companyId: issue.companyId, documentId: document.id, revisionNumber: 1, + title: input.title ?? null, + format: input.format, body: input.body, changeSummary: input.changeSummary ?? null, createdByAgentId: input.createdByAgentId ?? null, createdByUserId: input.createdByUserId ?? null, + createdByRunId: input.createdByRunId ?? null, createdAt: now, }) .returning(); @@ -391,27 +354,105 @@ export function documentService(db: Db) { } }, + restoreIssueDocumentRevision: async (input: { + issueId: string; + key: string; + revisionId: string; + createdByAgentId?: string | null; + createdByUserId?: string | null; + }) => { + const key = normalizeDocumentKey(input.key); + return db.transaction(async (tx) => { + const existing = await tx + .select(issueDocumentSelect) + .from(issueDocuments) + .innerJoin(documents, eq(issueDocuments.documentId, documents.id)) + .where(and(eq(issueDocuments.issueId, input.issueId), eq(issueDocuments.key, key))) + .then((rows) => rows[0] ?? null); + + if (!existing) throw notFound("Document not found"); + + const revision = await tx + .select({ + id: documentRevisions.id, + companyId: documentRevisions.companyId, + documentId: documentRevisions.documentId, + revisionNumber: documentRevisions.revisionNumber, + title: documentRevisions.title, + format: documentRevisions.format, + body: documentRevisions.body, + }) + .from(documentRevisions) + .where(and(eq(documentRevisions.id, input.revisionId), eq(documentRevisions.documentId, existing.id))) + .then((rows) => rows[0] ?? null); + + if (!revision) throw notFound("Document revision not found"); + if (existing.latestRevisionId === revision.id) { + throw conflict("Selected revision is already the latest revision", { + currentRevisionId: existing.latestRevisionId, + }); + } + + const now = new Date(); + const nextRevisionNumber = existing.latestRevisionNumber + 1; + const [restoredRevision] = await tx + .insert(documentRevisions) + .values({ + companyId: existing.companyId, + documentId: existing.id, + revisionNumber: nextRevisionNumber, + title: revision.title ?? null, + format: revision.format, + body: revision.body, + changeSummary: `Restored from revision ${revision.revisionNumber}`, + createdByAgentId: input.createdByAgentId ?? null, + createdByUserId: input.createdByUserId ?? null, + createdAt: now, + }) + .returning(); + + await tx + .update(documents) + .set({ + title: revision.title ?? null, + format: revision.format, + latestBody: revision.body, + latestRevisionId: restoredRevision.id, + latestRevisionNumber: nextRevisionNumber, + updatedByAgentId: input.createdByAgentId ?? null, + updatedByUserId: input.createdByUserId ?? null, + updatedAt: now, + }) + .where(eq(documents.id, existing.id)); + + await tx + .update(issueDocuments) + .set({ updatedAt: now }) + .where(eq(issueDocuments.documentId, existing.id)); + + return { + restoredFromRevisionId: revision.id, + restoredFromRevisionNumber: revision.revisionNumber, + document: { + ...existing, + title: revision.title ?? null, + format: revision.format, + body: revision.body, + latestRevisionId: restoredRevision.id, + latestRevisionNumber: nextRevisionNumber, + updatedByAgentId: input.createdByAgentId ?? null, + updatedByUserId: input.createdByUserId ?? null, + updatedAt: now, + }, + }; + }); + }, + deleteIssueDocument: async (issueId: string, rawKey: string) => { const key = normalizeDocumentKey(rawKey); return db.transaction(async (tx) => { const existing = await tx - .select({ - id: documents.id, - companyId: documents.companyId, - issueId: issueDocuments.issueId, - key: issueDocuments.key, - title: documents.title, - format: documents.format, - latestBody: documents.latestBody, - latestRevisionId: documents.latestRevisionId, - latestRevisionNumber: documents.latestRevisionNumber, - createdByAgentId: documents.createdByAgentId, - createdByUserId: documents.createdByUserId, - updatedByAgentId: documents.updatedByAgentId, - updatedByUserId: documents.updatedByUserId, - createdAt: documents.createdAt, - updatedAt: documents.updatedAt, - }) + .select(issueDocumentSelect) .from(issueDocuments) .innerJoin(documents, eq(issueDocuments.documentId, documents.id)) .where(and(eq(issueDocuments.issueId, issueId), eq(issueDocuments.key, key))) diff --git a/server/src/services/execution-workspaces.ts b/server/src/services/execution-workspaces.ts index a1d3b41d69a..4c3b66c02ba 100644 --- a/server/src/services/execution-workspaces.ts +++ b/server/src/services/execution-workspaces.ts @@ -317,6 +317,41 @@ function toExecutionWorkspace( }; } +function usesInheritedProjectRuntimeServices(row: ExecutionWorkspaceRow) { + if (row.mode !== "shared_workspace" || !row.projectWorkspaceId) return false; + return !readExecutionWorkspaceConfig((row.metadata as Record | null) ?? null)?.workspaceRuntime; +} + +async function loadEffectiveRuntimeServicesByExecutionWorkspace( + db: Db, + companyId: string, + rows: ExecutionWorkspaceRow[], +) { + const executionRuntimeServices = await listCurrentRuntimeServicesForExecutionWorkspaces( + db, + companyId, + rows.map((row) => row.id), + ); + const projectWorkspaceIds = rows + .filter((row) => usesInheritedProjectRuntimeServices(row)) + .map((row) => row.projectWorkspaceId) + .filter((value): value is string => Boolean(value)); + const projectRuntimeServices = await listCurrentRuntimeServicesForProjectWorkspaces( + db, + companyId, + [...new Set(projectWorkspaceIds)], + ); + + return new Map( + rows.map((row) => [ + row.id, + usesInheritedProjectRuntimeServices(row) + ? (projectRuntimeServices.get(row.projectWorkspaceId!) ?? []) + : (executionRuntimeServices.get(row.id) ?? []), + ]), + ); +} + export function executionWorkspaceService(db: Db) { return { list: async (companyId: string, filters?: { diff --git a/server/src/services/feedback-redaction.ts b/server/src/services/feedback-redaction.ts new file mode 100644 index 00000000000..7c0ed097e2b --- /dev/null +++ b/server/src/services/feedback-redaction.ts @@ -0,0 +1,193 @@ +import { createHash } from "node:crypto"; +import { redactCurrentUserText } from "../log-redaction.js"; +import { sanitizeRecord } from "../redaction.js"; + +export type FeedbackRedactionState = { + redactedFields: Set; + truncatedFields: Set; + omittedFields: Set; + notes: Set; + counts: Map; +}; + +type PatternReplacement = string | ((match: string, ...args: string[]) => string); + +type RedactionPattern = { + kind: string; + regex: RegExp; + replacement: PatternReplacement; +}; + +const SECRET_ASSIGNMENT_RE = + /\b(api[-_]?key|access[-_]?token|auth(?:_?token)?|authorization|bearer|secret|passwd|password|credential|jwt|private[-_]?key|cookie|connectionstring)\s*[:=]\s*([^\s,;]+)/gi; + +const FREE_TEXT_PATTERNS: RedactionPattern[] = [ + { + kind: "pem_block", + regex: /-----BEGIN [^-]+-----[\s\S]+?-----END [^-]+-----/g, + replacement: "[REDACTED_PEM_BLOCK]", + }, + { + kind: "secret_assignment", + regex: SECRET_ASSIGNMENT_RE, + replacement: (_match, key: string) => `${key}=[REDACTED]`, + }, + { + kind: "bearer_token", + regex: /Bearer\s+[A-Za-z0-9._~+/-]+=*/gi, + replacement: "Bearer [REDACTED_TOKEN]", + }, + { + kind: "github_token", + regex: /\bgh[pousr]_[A-Za-z0-9_]{20,}\b/g, + replacement: "[REDACTED_GITHUB_TOKEN]", + }, + { + kind: "provider_api_key", + regex: /\bsk-(?:ant-)?[A-Za-z0-9_-]{12,}\b/g, + replacement: "[REDACTED_API_KEY]", + }, + { + kind: "jwt", + regex: /\b[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)?\b/g, + replacement: "[REDACTED_JWT]", + }, + { + kind: "dsn", + regex: /\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis|amqp|kafka|nats|mssql):\/\/[^\s<>'")]+/gi, + replacement: "[REDACTED_CONNECTION_STRING]", + }, + { + kind: "email", + regex: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi, + replacement: "[REDACTED_EMAIL]", + }, + { + kind: "phone", + regex: /(? { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function increment(state: FeedbackRedactionState, kind: string, count: number) { + if (count <= 0) return; + state.counts.set(kind, (state.counts.get(kind) ?? 0) + count); +} + +function recordField(state: FeedbackRedactionState, fieldPath: string) { + if (fieldPath.trim().length === 0) return; + state.redactedFields.add(fieldPath); +} + +function applyPattern(input: string, pattern: RedactionPattern) { + const matches = Array.from(input.matchAll(pattern.regex)).length; + if (matches === 0) { + pattern.regex.lastIndex = 0; + return { output: input, matches: 0 }; + } + const output = input.replace(pattern.regex, pattern.replacement as never); + pattern.regex.lastIndex = 0; + return { output, matches }; +} + +export function createFeedbackRedactionState(): FeedbackRedactionState { + return { + redactedFields: new Set(), + truncatedFields: new Set(), + omittedFields: new Set(), + notes: new Set(), + counts: new Map(), + }; +} + +export function sanitizeFeedbackText( + input: string, + state: FeedbackRedactionState, + fieldPath: string, + maxLength: number, +) { + let output = redactCurrentUserText(input); + if (output !== input) { + recordField(state, fieldPath); + increment(state, "current_user", 1); + } + + for (const pattern of FREE_TEXT_PATTERNS) { + const result = applyPattern(output, pattern); + if (result.matches > 0) { + output = result.output; + recordField(state, fieldPath); + increment(state, pattern.kind, result.matches); + } + } + + if (output.length > maxLength) { + output = `${output.slice(0, Math.max(0, maxLength - 1))}...`; + state.truncatedFields.add(fieldPath); + } + + return output; +} + +export function sanitizeFeedbackValue( + value: unknown, + state: FeedbackRedactionState, + fieldPath: string, + maxStringLength: number, +): unknown { + if (typeof value === "string") { + return sanitizeFeedbackText(value, state, fieldPath, maxStringLength); + } + if (Array.isArray(value)) { + return value.map((entry, index) => + sanitizeFeedbackValue(entry, state, `${fieldPath}[${index}]`, maxStringLength)); + } + if (!isPlainRecord(value)) { + return value; + } + + const structurallySanitized = sanitizeRecord(value); + if (stableStringify(structurallySanitized) !== stableStringify(value)) { + recordField(state, fieldPath); + increment(state, "structured_secret", 1); + } + + const output: Record = {}; + for (const [key, entry] of Object.entries(structurallySanitized)) { + output[key] = sanitizeFeedbackValue(entry, state, `${fieldPath}.${key}`, maxStringLength); + } + return output; +} + +export function finalizeFeedbackRedactionSummary(state: FeedbackRedactionState) { + return { + strategy: "deterministic_feedback_v2", + redactedFields: Array.from(state.redactedFields).sort(), + truncatedFields: Array.from(state.truncatedFields).sort(), + omittedFields: Array.from(state.omittedFields).sort(), + notes: Array.from(state.notes).sort(), + counts: Object.fromEntries(Array.from(state.counts.entries()).sort(([left], [right]) => left.localeCompare(right))), + } satisfies Record; +} + +export function stableStringify(value: unknown): string { + if (value === null || typeof value !== "object") { + return JSON.stringify(value); + } + if (Array.isArray(value)) { + return `[${value.map((entry) => stableStringify(entry)).join(",")}]`; + } + + const entries = Object.entries(value as Record) + .sort(([left], [right]) => left.localeCompare(right)) + .map(([key, entry]) => `${JSON.stringify(key)}:${stableStringify(entry)}`); + return `{${entries.join(",")}}`; +} + +export function sha256Digest(value: unknown) { + return createHash("sha256").update(stableStringify(value)).digest("hex"); +} diff --git a/server/src/services/feedback-share-client.ts b/server/src/services/feedback-share-client.ts new file mode 100644 index 00000000000..7fdf97ca4af --- /dev/null +++ b/server/src/services/feedback-share-client.ts @@ -0,0 +1,59 @@ +import { gzipSync } from "node:zlib"; +import type { FeedbackTraceBundle } from "@paperclipai/shared"; +import type { Config } from "../config.js"; + +const DEFAULT_FEEDBACK_EXPORT_BACKEND_URL = "https://telemetry.paperclip.ing"; + +function buildFeedbackShareObjectKey(bundle: FeedbackTraceBundle, exportedAt: Date) { + const year = String(exportedAt.getUTCFullYear()); + const month = String(exportedAt.getUTCMonth() + 1).padStart(2, "0"); + const day = String(exportedAt.getUTCDate()).padStart(2, "0"); + return `feedback-traces/${bundle.companyId}/${year}/${month}/${day}/${bundle.exportId ?? bundle.traceId}.json`; +} + +export interface FeedbackTraceShareClient { + uploadTraceBundle(bundle: FeedbackTraceBundle): Promise<{ objectKey: string }>; +} + +export function createFeedbackTraceShareClientFromConfig( + config: Pick, +): FeedbackTraceShareClient { + const baseUrl = config.feedbackExportBackendUrl?.trim() || DEFAULT_FEEDBACK_EXPORT_BACKEND_URL; + const token = config.feedbackExportBackendToken?.trim(); + const endpoint = new URL("/feedback-traces", baseUrl).toString(); + + return { + async uploadTraceBundle(bundle) { + const exportedAt = new Date(); + const objectKey = buildFeedbackShareObjectKey(bundle, exportedAt); + const requestBody = JSON.stringify({ + objectKey, + exportedAt: exportedAt.toISOString(), + bundle, + }); + const response = await fetch(endpoint, { + method: "POST", + headers: { + "content-type": "application/json", + ...(token ? { authorization: `Bearer ${token}` } : {}), + }, + body: JSON.stringify({ + encoding: "gzip+base64+json", + payload: gzipSync(requestBody).toString("base64"), + }), + }); + + if (!response.ok) { + const detail = await response.text().catch(() => ""); + throw new Error(detail.trim() || `Feedback trace upload failed with HTTP ${response.status}`); + } + + const payload = await response.json().catch(() => null) as { objectKey?: unknown } | null; + return { + objectKey: typeof payload?.objectKey === "string" && payload.objectKey.trim().length > 0 + ? payload.objectKey + : objectKey, + }; + }, + }; +} diff --git a/server/src/services/feedback.ts b/server/src/services/feedback.ts new file mode 100644 index 00000000000..26ce6107fd6 --- /dev/null +++ b/server/src/services/feedback.ts @@ -0,0 +1,2087 @@ +import { readFile, readdir } from "node:fs/promises"; +import path from "node:path"; +import { and, asc, desc, eq, getTableColumns, gte, lte, ne, or } from "drizzle-orm"; +import type { Db } from "@paperclipai/db"; +import { + agents, + companies, + companySkills, + costEvents, + documentRevisions, + documents, + feedbackExports, + feedbackVotes, + heartbeatRunEvents, + heartbeatRuns, + instanceSettings, + issueComments, + issueDocuments, + issues, +} from "@paperclipai/db"; +import { readPaperclipSkillSyncPreference } from "@paperclipai/adapter-utils/server-utils"; +import { claudeConfigDir, parseClaudeStreamJson } from "@paperclipai/adapter-claude-local/server"; +import { codexHomeDir, parseCodexJsonl } from "@paperclipai/adapter-codex-local/server"; +import { parseOpenCodeJsonl } from "@paperclipai/adapter-opencode-local/server"; +import { + DEFAULT_FEEDBACK_DATA_SHARING_PREFERENCE, + DEFAULT_FEEDBACK_DATA_SHARING_TERMS_VERSION, + instanceGeneralSettingsSchema, + type FeedbackTargetType, + type FeedbackTraceBundle, + type FeedbackTraceBundleCaptureStatus, + type FeedbackTraceBundleFile, + type FeedbackTrace, + type FeedbackTraceStatus, + type FeedbackTraceTargetSummary, + type FeedbackVoteValue, +} from "@paperclipai/shared"; +import { resolveHomeAwarePath, resolvePaperclipInstanceRoot } from "../home-paths.js"; +import { notFound, unprocessable } from "../errors.js"; +import { agentInstructionsService } from "./agent-instructions.js"; +import { + createFeedbackRedactionState, + finalizeFeedbackRedactionSummary, + sanitizeFeedbackText, + sanitizeFeedbackValue, + sha256Digest, +} from "./feedback-redaction.js"; +import { getRunLogStore } from "./run-log-store.js"; + +const FEEDBACK_SCHEMA_VERSION = "paperclip-feedback-envelope-v2"; +const FEEDBACK_BUNDLE_VERSION = "paperclip-feedback-bundle-v2"; +const FEEDBACK_PAYLOAD_VERSION = "paperclip-feedback-v1"; +const FEEDBACK_DESTINATION = "paperclip_labs_feedback_v1"; +const FEEDBACK_CONTEXT_WINDOW = 3; +const MAX_EXCERPT_CHARS = 200; +const MAX_PRIMARY_CONTENT_CHARS = 8_000; +const MAX_CONTEXT_ITEM_BODY_CHARS = 3_000; +const MAX_TOTAL_CONTEXT_CHARS = 12_000; +const MAX_DESCRIPTION_CHARS = 1_200; +const MAX_INSTRUCTIONS_BODY_CHARS = 8_000; +const MAX_PATH_CHARS = 600; +const MAX_SKILLS = 20; +const MAX_INSTRUCTION_FILES = 20; +const MAX_TRACE_FILE_CHARS = 10_000_000; +const DEFAULT_INSTANCE_SETTINGS_SINGLETON_KEY = "default"; +const FEEDBACK_EXPORT_BACKEND_NOT_CONFIGURED = "Feedback export backend is not configured"; + +type FeedbackTraceRow = typeof feedbackExports.$inferSelect & { + issueIdentifier: string | null; + issueTitle: string; +}; + +type PendingFeedbackExportRow = typeof feedbackExports.$inferSelect; + +type IssueFeedbackContext = { + id: string; + companyId: string; + projectId: string | null; + identifier: string | null; + title: string; + description: string | null; +}; + +type FeedbackTargetRecord = { + targetType: FeedbackTargetType; + targetId: string; + label: string; + body: string; + createdAt: Date; + authorAgentId: string | null; + authorUserId: string | null; + createdByRunId: string | null; + documentId: string | null; + documentKey: string | null; + documentTitle: string | null; + revisionNumber: number | null; + issuePath: string | null; + targetPath: string | null; +}; + +type ResolvedFeedbackTarget = FeedbackTargetRecord & { + payloadTarget: Record; +}; + +const feedbackExportColumns = getTableColumns(feedbackExports); +const instructionsSvc = agentInstructionsService(); + +type FeedbackTraceShareClient = { + uploadTraceBundle(bundle: FeedbackTraceBundle): Promise<{ objectKey: string }>; +}; + +type FeedbackServiceOptions = { + shareClient?: FeedbackTraceShareClient; +}; + +function asRecord(value: unknown): Record | null { + if (!value || typeof value !== "object" || Array.isArray(value)) return null; + return value as Record; +} + +function asString(value: unknown) { + if (typeof value !== "string") return null; + const trimmed = value.trim(); + return trimmed.length > 0 ? trimmed : null; +} + +function asNumber(value: unknown) { + if (typeof value !== "number" || !Number.isFinite(value)) return null; + return value; +} + +function asBoolean(value: unknown) { + return typeof value === "boolean" ? value : null; +} + +function uniqueNonEmpty(values: Array) { + return Array.from(new Set(values.map((value) => value?.trim() ?? "").filter(Boolean))); +} + +function truncateExcerpt(text: string, max = MAX_EXCERPT_CHARS) { + const normalized = text.replace(/\s+/g, " ").trim(); + if (!normalized) return null; + return normalized.length <= max ? normalized : `${normalized.slice(0, max - 1)}...`; +} + +function contentTypeForPath(filePath: string) { + const lower = filePath.toLowerCase(); + if (lower.endsWith(".jsonl") || lower.endsWith(".ndjson")) return "application/x-ndjson"; + if (lower.endsWith(".json")) return "application/json"; + if (lower.endsWith(".md")) return "text/markdown; charset=utf-8"; + return "text/plain; charset=utf-8"; +} + +function normalizeInstanceGeneralSettings(raw: unknown) { + const parsed = instanceGeneralSettingsSchema.safeParse(raw ?? {}); + if (parsed.success) return parsed.data; + return { + censorUsernameInLogs: false, + feedbackDataSharingPreference: DEFAULT_FEEDBACK_DATA_SHARING_PREFERENCE, + }; +} + +function buildIssuePath(identifier: string | null) { + if (!identifier) return null; + const prefix = identifier.split("-")[0]?.trim(); + if (!prefix) return null; + return `/${prefix}/issues/${identifier}`; +} + +function buildTargetSummary(input: { + label: string; + excerpt: string | null; + authorAgentId: string | null; + authorUserId: string | null; + createdAt: Date | null; + documentKey?: string | null; + documentTitle?: string | null; + revisionNumber?: number | null; +}): FeedbackTraceTargetSummary { + return { + label: input.label, + excerpt: input.excerpt, + authorAgentId: input.authorAgentId, + authorUserId: input.authorUserId, + createdAt: input.createdAt, + documentKey: input.documentKey ?? null, + documentTitle: input.documentTitle ?? null, + revisionNumber: input.revisionNumber ?? null, + }; +} + +function normalizeReason(vote: FeedbackVoteValue, reason: string | null | undefined) { + if (vote !== "down" || typeof reason !== "string") return null; + const trimmed = reason.trim(); + return trimmed.length > 0 ? trimmed : null; +} + +function normalizeSkillReference(value: string) { + return value.trim().toLowerCase(); +} + +function matchesSkillReference( + skill: typeof companySkills.$inferSelect, + reference: string, +) { + const normalized = normalizeSkillReference(reference); + if (!normalized) return false; + if (skill.key.toLowerCase() === normalized) return true; + if (skill.slug.toLowerCase() === normalized) return true; + if (skill.name.toLowerCase() === normalized) return true; + const keyTail = skill.key.split("/").pop()?.toLowerCase(); + return keyTail === normalized; +} + +function buildExportId(feedbackVoteId: string, sharedAt: Date) { + return `fbexp_${sha256Digest(`${feedbackVoteId}:${sharedAt.toISOString()}`).slice(0, 24)}`; +} + +function resolveSourceRunId(payloadSnapshot: Record | null) { + const targetRunId = asString(asRecord(payloadSnapshot?.target)?.createdByRunId); + if (targetRunId) return targetRunId; + + const bundle = asRecord(payloadSnapshot?.bundle); + const agentContext = asRecord(bundle?.agentContext); + const runtime = asRecord(agentContext?.runtime); + return asString(asRecord(runtime?.sourceRun)?.id); +} + +function makeBundleFile(input: { + path: string; + contentType: string; + source: FeedbackTraceBundleFile["source"]; + contents: string; +}) { + return { + path: input.path, + contentType: input.contentType, + encoding: "utf8" as const, + byteLength: Buffer.byteLength(input.contents, "utf8"), + sha256: sha256Digest(input.contents), + source: input.source, + contents: input.contents, + } satisfies FeedbackTraceBundleFile; +} + +function appendNote(notes: string[], note: string) { + if (note.trim().length === 0 || notes.includes(note)) return; + notes.push(note); +} + +async function readTextFileIfPresent( + filePath: string | null, + state: ReturnType, + fieldPath: string, +) { + if (!filePath) return null; + const raw = await readFile(filePath, "utf8").catch(() => null); + if (raw == null) return null; + return sanitizeFeedbackText(raw, state, fieldPath, MAX_TRACE_FILE_CHARS); +} + +async function listChildFiles(dirPath: string) { + const entries = await readdir(dirPath, { withFileTypes: true }).catch(() => []); + return entries + .filter((entry) => entry.isFile()) + .map((entry) => path.join(dirPath, entry.name)) + .sort((left, right) => left.localeCompare(right)); +} + +async function listNestedFiles(dirPath: string, maxDepth = 4): Promise { + async function walk(currentPath: string, depth: number): Promise { + const entries = await readdir(currentPath, { withFileTypes: true }).catch(() => []); + const files = entries + .filter((entry) => entry.isFile()) + .map((entry) => path.join(currentPath, entry.name)) + .sort((left, right) => left.localeCompare(right)); + if (depth >= maxDepth) return files; + + const childDirs = entries + .filter((entry) => entry.isDirectory()) + .map((entry) => path.join(currentPath, entry.name)) + .sort((left, right) => left.localeCompare(right)); + const nested = await Promise.all(childDirs.map((childDir) => walk(childDir, depth + 1))); + return [...files, ...nested.flat()]; + } + + return walk(dirPath, 0); +} + +async function findMatchingFile( + rootDir: string, + matcher: (absolutePath: string, name: string) => boolean, + maxDepth = 5, +): Promise { + async function search(dirPath: string, depth: number): Promise { + const entries = await readdir(dirPath, { withFileTypes: true }).catch(() => []); + for (const entry of entries) { + const absolutePath = path.join(dirPath, entry.name); + if (entry.isFile() && matcher(absolutePath, entry.name)) { + return absolutePath; + } + } + if (depth >= maxDepth) return null; + for (const entry of entries) { + if (!entry.isDirectory()) continue; + const found = await search(path.join(dirPath, entry.name), depth + 1); + if (found) return found; + } + return null; + } + + return search(rootDir, 0); +} + +async function readFullRunLog(run: { + logStore: string | null; + logRef: string | null; +}) { + if (run.logStore !== "local_file" || !run.logRef) return null; + const store = getRunLogStore(); + let offset = 0; + let combined = ""; + + while (true) { + const result = await store.read({ store: "local_file", logRef: run.logRef }, { + offset, + limitBytes: 512_000, + }).catch(() => null); + if (!result) return combined || null; + combined += result.content; + if (result.nextOffset == null) break; + offset = result.nextOffset; + } + + return combined || null; +} + +function parseRunLogEntries(logText: string | null) { + if (!logText) return []; + const entries: Array<{ ts: string; stream: string; chunk: string }> = []; + for (const rawLine of logText.split(/\r?\n/)) { + const line = rawLine.trim(); + if (!line) continue; + try { + const parsed = JSON.parse(line) as { ts?: unknown; stream?: unknown; chunk?: unknown }; + const ts = asString(parsed.ts) ?? new Date(0).toISOString(); + const stream = asString(parsed.stream) ?? "stdout"; + const chunk = typeof parsed.chunk === "string" ? parsed.chunk : ""; + entries.push({ ts, stream, chunk }); + } catch { + // Keep malformed lines out of the normalized bundle but preserve the raw log file separately. + } + } + return entries; +} + +function captureStatusFromFiles(files: FeedbackTraceBundleFile[]): FeedbackTraceBundleCaptureStatus { + const sources = new Set(files.map((file) => file.source)); + if (sources.has("codex_session")) return "full"; + if (sources.has("claude_project_session") || sources.has("claude_debug_log")) return "full"; + if ( + sources.has("opencode_session") && + sources.has("opencode_message") && + sources.has("opencode_message_part") + ) { + return "full"; + } + + const hasAdapterFiles = files.some((file) => + file.source !== "paperclip_run" && + file.source !== "paperclip_run_events" && + file.source !== "paperclip_run_log", + ); + if (hasAdapterFiles) return "partial"; + return files.length > 0 ? "partial" : "unavailable"; +} + +async function buildCodexTraceFiles(input: { + companyId: string; + sessionId: string | null; + state: ReturnType; + notes: string[]; +}) { + const files: FeedbackTraceBundleFile[] = []; + if (!input.sessionId) { + appendNote(input.notes, "codex_session_id_missing"); + return { files, raw: null as Record | null, normalized: null as Record | null }; + } + + const managedRoot = path.join( + resolvePaperclipInstanceRoot(), + "companies", + input.companyId, + "codex-home", + "sessions", + ); + const sharedRoot = path.join(codexHomeDir(), "sessions"); + const sessionFile = + await findMatchingFile(managedRoot, (_absolutePath, name) => name.includes(input.sessionId!), 6) ?? + await findMatchingFile(sharedRoot, (_absolutePath, name) => name.includes(input.sessionId!), 6); + + const sessionText = await readTextFileIfPresent(sessionFile, input.state, "bundle.rawAdapterTrace.codex.session"); + if (!sessionText) { + appendNote(input.notes, "codex_session_file_missing"); + return { files, raw: null as Record | null, normalized: null as Record | null }; + } + + files.push(makeBundleFile({ + path: "adapter/codex/session.jsonl", + contentType: "application/x-ndjson", + source: "codex_session", + contents: sessionText, + })); + + return { + files, + raw: { + adapterType: "codex_local", + sessionId: input.sessionId, + sessionFile: sessionFile ? path.basename(sessionFile) : null, + }, + normalized: sanitizeFeedbackValue( + { + adapterType: "codex_local", + sessionId: input.sessionId, + summary: parseCodexJsonl(sessionText), + }, + input.state, + "bundle.normalizedAdapterTrace.codex", + MAX_TRACE_FILE_CHARS, + ) as Record, + }; +} + +async function buildClaudeTraceFiles(input: { + sessionId: string | null; + stdoutText: string; + state: ReturnType; + notes: string[]; +}) { + const files: FeedbackTraceBundleFile[] = []; + const sanitizedStdout = sanitizeFeedbackText( + input.stdoutText, + input.state, + "bundle.rawAdapterTrace.claude.stdout", + MAX_TRACE_FILE_CHARS, + ); + if (sanitizedStdout.trim().length > 0) { + files.push(makeBundleFile({ + path: "adapter/claude/stream-json.ndjson", + contentType: "application/x-ndjson", + source: "claude_stream_json", + contents: sanitizedStdout, + })); + } + + const projectsRoot = path.join(claudeConfigDir(), "projects"); + const projectSessionFile = input.sessionId + ? await findMatchingFile(projectsRoot, (_absolutePath, name) => name === `${input.sessionId}.jsonl`, 6) + : null; + const projectSessionText = await readTextFileIfPresent( + projectSessionFile, + input.state, + "bundle.rawAdapterTrace.claude.projectSession", + ); + if (projectSessionText) { + files.push(makeBundleFile({ + path: "adapter/claude/session.jsonl", + contentType: "application/x-ndjson", + source: "claude_project_session", + contents: projectSessionText, + })); + } else if (input.sessionId) { + appendNote(input.notes, "claude_project_session_missing"); + } + + const projectSessionArtifactsDir = projectSessionFile + ? path.join(path.dirname(projectSessionFile), input.sessionId ?? "") + : null; + const projectSessionArtifactFiles = projectSessionArtifactsDir + ? await listNestedFiles(projectSessionArtifactsDir, 4) + : []; + for (const filePath of projectSessionArtifactFiles) { + const relativePath = path.relative(projectSessionArtifactsDir!, filePath).split(path.sep).join("/"); + const fileText = await readTextFileIfPresent( + filePath, + input.state, + `bundle.rawAdapterTrace.claude.projectArtifacts.${relativePath}`, + ); + if (!fileText) continue; + files.push(makeBundleFile({ + path: `adapter/claude/session/${relativePath}`, + contentType: contentTypeForPath(filePath), + source: "claude_project_artifact", + contents: fileText, + })); + } + + const debugLogText = await readTextFileIfPresent( + input.sessionId ? path.join(claudeConfigDir(), "debug", `${input.sessionId}.txt`) : null, + input.state, + "bundle.rawAdapterTrace.claude.debugLog", + ); + if (debugLogText) { + files.push(makeBundleFile({ + path: "adapter/claude/debug.txt", + contentType: "text/plain; charset=utf-8", + source: "claude_debug_log", + contents: debugLogText, + })); + } + + const taskDir = input.sessionId ? path.join(claudeConfigDir(), "tasks", input.sessionId) : null; + const taskFiles = taskDir ? await listChildFiles(taskDir) : []; + const metadataPieces: string[] = []; + for (const filePath of taskFiles) { + const fileText = await readTextFileIfPresent( + filePath, + input.state, + `bundle.rawAdapterTrace.claude.taskMetadata.${path.basename(filePath)}`, + ); + if (!fileText) continue; + metadataPieces.push(`# ${path.basename(filePath)}\n${fileText}`); + } + if (metadataPieces.length > 0) { + files.push(makeBundleFile({ + path: "adapter/claude/task-metadata.txt", + contentType: "text/plain; charset=utf-8", + source: "claude_task_metadata", + contents: `${metadataPieces.join("\n\n")}\n`, + })); + } else if (input.sessionId) { + appendNote(input.notes, "claude_task_metadata_missing"); + } + + if (files.length === 0) { + appendNote(input.notes, "claude_stream_trace_missing"); + } + + return { + files, + raw: { + adapterType: "claude_local", + sessionId: input.sessionId, + projectSessionFound: Boolean(projectSessionText), + projectArtifactsCount: projectSessionArtifactFiles.length, + debugLogFound: Boolean(debugLogText), + taskDirPresent: taskFiles.length > 0, + }, + normalized: sanitizeFeedbackValue( + { + adapterType: "claude_local", + sessionId: input.sessionId, + summary: parseClaudeStreamJson(input.stdoutText), + }, + input.state, + "bundle.normalizedAdapterTrace.claude", + MAX_TRACE_FILE_CHARS, + ) as Record, + }; +} + +async function buildOpenCodeTraceFiles(input: { + sessionId: string | null; + stdoutText: string; + state: ReturnType; + notes: string[]; +}) { + const files: FeedbackTraceBundleFile[] = []; + if (!input.sessionId) { + appendNote(input.notes, "opencode_session_id_missing"); + return { + files, + raw: null as Record | null, + normalized: sanitizeFeedbackValue( + { + adapterType: "opencode_local", + summary: parseOpenCodeJsonl(input.stdoutText), + }, + input.state, + "bundle.normalizedAdapterTrace.opencode", + MAX_TRACE_FILE_CHARS, + ) as Record, + }; + } + + const opencodeRoot = resolveHomeAwarePath( + process.env.PAPERCLIP_OPENCODE_STORAGE_DIR ?? "~/.local/share/opencode", + ); + const sessionRoot = path.join(opencodeRoot, "storage", "session"); + const diffRoot = path.join(opencodeRoot, "storage", "session_diff"); + const messageRoot = path.join(opencodeRoot, "storage", "message"); + const partRoot = path.join(opencodeRoot, "storage", "part"); + const todoRoot = path.join(opencodeRoot, "storage", "todo"); + const projectRoot = path.join(opencodeRoot, "storage", "project"); + const sessionFile = await findMatchingFile( + sessionRoot, + (_absolutePath, name) => name === `${input.sessionId}.json`, + 6, + ); + const diffFile = path.join(diffRoot, `${input.sessionId}.json`); + + const sessionRaw = sessionFile ? await readFile(sessionFile, "utf8").catch(() => null) : null; + const sessionText = + sessionRaw == null + ? null + : sanitizeFeedbackText(sessionRaw, input.state, "bundle.rawAdapterTrace.opencode.session", MAX_TRACE_FILE_CHARS); + if (sessionText) { + files.push(makeBundleFile({ + path: "adapter/opencode/session.json", + contentType: "application/json", + source: "opencode_session", + contents: sessionText, + })); + } else { + appendNote(input.notes, "opencode_session_file_missing"); + } + + const diffText = await readTextFileIfPresent( + diffFile, + input.state, + "bundle.rawAdapterTrace.opencode.sessionDiff", + ); + if (diffText) { + files.push(makeBundleFile({ + path: "adapter/opencode/session-diff.json", + contentType: "application/json", + source: "opencode_session_diff", + contents: diffText, + })); + } + + const messageFiles = await listChildFiles(path.join(messageRoot, input.sessionId)); + const messageIds: string[] = []; + for (const filePath of messageFiles) { + const messageText = await readTextFileIfPresent( + filePath, + input.state, + `bundle.rawAdapterTrace.opencode.messages.${path.basename(filePath)}`, + ); + if (!messageText) continue; + messageIds.push(path.basename(filePath, path.extname(filePath))); + files.push(makeBundleFile({ + path: `adapter/opencode/messages/${path.basename(filePath)}`, + contentType: "application/json", + source: "opencode_message", + contents: messageText, + })); + } + if (messageFiles.length === 0) { + appendNote(input.notes, "opencode_message_files_missing"); + } + + let partFilesCount = 0; + for (const messageId of messageIds) { + const partFiles = await listChildFiles(path.join(partRoot, messageId)); + for (const filePath of partFiles) { + const partText = await readTextFileIfPresent( + filePath, + input.state, + `bundle.rawAdapterTrace.opencode.parts.${messageId}.${path.basename(filePath)}`, + ); + if (!partText) continue; + partFilesCount += 1; + files.push(makeBundleFile({ + path: `adapter/opencode/parts/${messageId}/${path.basename(filePath)}`, + contentType: "application/json", + source: "opencode_message_part", + contents: partText, + })); + } + } + if (messageIds.length > 0 && partFilesCount === 0) { + appendNote(input.notes, "opencode_message_parts_missing"); + } + + const parsedSession = (() => { + if (!sessionRaw) return null; + try { + return JSON.parse(sessionRaw) as Record; + } catch { + return null; + } + })(); + const projectId = asString(parsedSession?.projectID) ?? asString(parsedSession?.projectId); + const projectText = await readTextFileIfPresent( + projectId ? path.join(projectRoot, `${projectId}.json`) : null, + input.state, + "bundle.rawAdapterTrace.opencode.project", + ); + if (projectText) { + files.push(makeBundleFile({ + path: "adapter/opencode/project.json", + contentType: "application/json", + source: "opencode_project", + contents: projectText, + })); + } + + const todoText = await readTextFileIfPresent( + path.join(todoRoot, `${input.sessionId}.json`), + input.state, + "bundle.rawAdapterTrace.opencode.todo", + ); + if (todoText) { + files.push(makeBundleFile({ + path: "adapter/opencode/todo.json", + contentType: "application/json", + source: "opencode_todo", + contents: todoText, + })); + } + + return { + files, + raw: { + adapterType: "opencode_local", + sessionId: input.sessionId, + sessionFileFound: Boolean(sessionText), + sessionDiffFound: Boolean(diffText), + messageFilesCount: messageFiles.length, + partFilesCount, + projectFound: Boolean(projectText), + todoFound: Boolean(todoText), + }, + normalized: sanitizeFeedbackValue( + { + adapterType: "opencode_local", + sessionId: input.sessionId, + summary: parseOpenCodeJsonl(input.stdoutText), + }, + input.state, + "bundle.normalizedAdapterTrace.opencode", + MAX_TRACE_FILE_CHARS, + ) as Record, + }; +} + +function truncateFailureReason(error: unknown) { + const message = error instanceof Error ? error.message : String(error); + return message.trim().slice(0, 1_000) || "Feedback export failed"; +} + +function mapTraceRow(row: FeedbackTraceRow, includePayload: boolean): FeedbackTrace { + const targetSummary = asRecord(row.targetSummary) as unknown as FeedbackTraceTargetSummary | null; + return { + id: row.id, + companyId: row.companyId, + feedbackVoteId: row.feedbackVoteId, + issueId: row.issueId, + projectId: row.projectId ?? null, + issueIdentifier: row.issueIdentifier, + issueTitle: row.issueTitle, + authorUserId: row.authorUserId, + targetType: row.targetType as FeedbackTargetType, + targetId: row.targetId, + vote: row.vote as FeedbackVoteValue, + status: row.status as FeedbackTraceStatus, + destination: row.destination ?? null, + exportId: row.exportId ?? null, + consentVersion: row.consentVersion ?? null, + schemaVersion: row.schemaVersion, + bundleVersion: row.bundleVersion, + payloadVersion: row.payloadVersion, + payloadDigest: row.payloadDigest ?? null, + payloadSnapshot: includePayload ? asRecord(row.payloadSnapshot) : null, + targetSummary: targetSummary ?? buildTargetSummary({ + label: row.targetType, + excerpt: null, + authorAgentId: null, + authorUserId: null, + createdAt: null, + }), + redactionSummary: asRecord(row.redactionSummary), + attemptCount: row.attemptCount, + lastAttemptedAt: row.lastAttemptedAt ?? null, + exportedAt: row.exportedAt ?? null, + failureReason: row.failureReason ?? null, + createdAt: row.createdAt, + updatedAt: row.updatedAt, + }; +} + +async function resolveFeedbackTarget( + db: Pick, + issue: IssueFeedbackContext, + targetType: FeedbackTargetType, + targetId: string, +): Promise { + const issuePath = buildIssuePath(issue.identifier); + + if (targetType === "issue_comment") { + const targetComment = await db + .select({ + id: issueComments.id, + issueId: issueComments.issueId, + companyId: issueComments.companyId, + authorAgentId: issueComments.authorAgentId, + authorUserId: issueComments.authorUserId, + createdByRunId: issueComments.createdByRunId, + body: issueComments.body, + createdAt: issueComments.createdAt, + }) + .from(issueComments) + .where(eq(issueComments.id, targetId)) + .then((rows) => rows[0] ?? null); + + if (!targetComment || targetComment.issueId !== issue.id || targetComment.companyId !== issue.companyId) { + throw notFound("Feedback target not found"); + } + if (!targetComment.authorAgentId) { + throw unprocessable("Feedback voting is only available on agent-authored issue comments"); + } + + const record: ResolvedFeedbackTarget = { + targetType, + targetId, + label: "Comment", + body: targetComment.body, + createdAt: targetComment.createdAt, + authorAgentId: targetComment.authorAgentId, + authorUserId: targetComment.authorUserId, + createdByRunId: targetComment.createdByRunId ?? null, + documentId: null, + documentKey: null, + documentTitle: null, + revisionNumber: null, + issuePath, + targetPath: issuePath ? `${issuePath}#comment-${targetComment.id}` : null, + payloadTarget: { + type: targetType, + id: targetComment.id, + createdAt: targetComment.createdAt.toISOString(), + authorAgentId: targetComment.authorAgentId, + authorUserId: targetComment.authorUserId, + createdByRunId: targetComment.createdByRunId ?? null, + issuePath, + targetPath: issuePath ? `${issuePath}#comment-${targetComment.id}` : null, + }, + }; + return record; + } + + if (targetType === "issue_document_revision") { + const targetRevision = await db + .select({ + id: documentRevisions.id, + companyId: documentRevisions.companyId, + documentId: documentRevisions.documentId, + revisionNumber: documentRevisions.revisionNumber, + body: documentRevisions.body, + createdByAgentId: documentRevisions.createdByAgentId, + createdByUserId: documentRevisions.createdByUserId, + createdByRunId: documentRevisions.createdByRunId, + createdAt: documentRevisions.createdAt, + issueId: issueDocuments.issueId, + key: issueDocuments.key, + title: documents.title, + }) + .from(documentRevisions) + .innerJoin(documents, eq(documentRevisions.documentId, documents.id)) + .innerJoin(issueDocuments, eq(issueDocuments.documentId, documents.id)) + .where(eq(documentRevisions.id, targetId)) + .then((rows) => rows.find((row) => row.issueId === issue.id) ?? null); + + if (!targetRevision || targetRevision.companyId !== issue.companyId) { + throw notFound("Feedback target not found"); + } + if (!targetRevision.createdByAgentId) { + throw unprocessable("Feedback voting is only available on agent-authored document revisions"); + } + + const record: ResolvedFeedbackTarget = { + targetType, + targetId, + label: `${targetRevision.key} rev ${targetRevision.revisionNumber}`, + body: targetRevision.body, + createdAt: targetRevision.createdAt, + authorAgentId: targetRevision.createdByAgentId, + authorUserId: targetRevision.createdByUserId, + createdByRunId: targetRevision.createdByRunId ?? null, + documentId: targetRevision.documentId, + documentKey: targetRevision.key, + documentTitle: targetRevision.title ?? null, + revisionNumber: targetRevision.revisionNumber, + issuePath, + targetPath: issuePath ? `${issuePath}#document-${encodeURIComponent(targetRevision.key)}` : null, + payloadTarget: { + type: targetType, + id: targetRevision.id, + documentId: targetRevision.documentId, + documentKey: targetRevision.key, + documentTitle: targetRevision.title ?? null, + revisionNumber: targetRevision.revisionNumber, + createdAt: targetRevision.createdAt.toISOString(), + authorAgentId: targetRevision.createdByAgentId, + authorUserId: targetRevision.createdByUserId, + createdByRunId: targetRevision.createdByRunId ?? null, + issuePath, + targetPath: issuePath ? `${issuePath}#document-${encodeURIComponent(targetRevision.key)}` : null, + }, + }; + return record; + } + + throw unprocessable("Unsupported feedback target type"); +} + +async function listIssueContextItems( + db: Pick, + issue: IssueFeedbackContext, +) { + const [commentRows, revisionRows] = await Promise.all([ + db + .select({ + targetId: issueComments.id, + body: issueComments.body, + createdAt: issueComments.createdAt, + authorAgentId: issueComments.authorAgentId, + authorUserId: issueComments.authorUserId, + createdByRunId: issueComments.createdByRunId, + }) + .from(issueComments) + .where(and(eq(issueComments.companyId, issue.companyId), eq(issueComments.issueId, issue.id))), + db + .select({ + targetId: documentRevisions.id, + body: documentRevisions.body, + createdAt: documentRevisions.createdAt, + authorAgentId: documentRevisions.createdByAgentId, + authorUserId: documentRevisions.createdByUserId, + createdByRunId: documentRevisions.createdByRunId, + documentId: documentRevisions.documentId, + documentKey: issueDocuments.key, + documentTitle: documents.title, + revisionNumber: documentRevisions.revisionNumber, + }) + .from(documentRevisions) + .innerJoin(documents, eq(documentRevisions.documentId, documents.id)) + .innerJoin(issueDocuments, eq(issueDocuments.documentId, documents.id)) + .where(and(eq(documentRevisions.companyId, issue.companyId), eq(issueDocuments.issueId, issue.id))), + ]); + + const issuePath = buildIssuePath(issue.identifier); + + const items: FeedbackTargetRecord[] = [ + ...commentRows.map((row) => ({ + targetType: "issue_comment" as const, + targetId: row.targetId, + label: "Comment", + body: row.body, + createdAt: row.createdAt, + authorAgentId: row.authorAgentId, + authorUserId: row.authorUserId, + createdByRunId: row.createdByRunId ?? null, + documentId: null, + documentKey: null, + documentTitle: null, + revisionNumber: null, + issuePath, + targetPath: issuePath ? `${issuePath}#comment-${row.targetId}` : null, + })), + ...revisionRows.map((row) => ({ + targetType: "issue_document_revision" as const, + targetId: row.targetId, + label: `${row.documentKey} rev ${row.revisionNumber}`, + body: row.body, + createdAt: row.createdAt, + authorAgentId: row.authorAgentId, + authorUserId: row.authorUserId, + createdByRunId: row.createdByRunId ?? null, + documentId: row.documentId, + documentKey: row.documentKey, + documentTitle: row.documentTitle ?? null, + revisionNumber: row.revisionNumber, + issuePath, + targetPath: issuePath ? `${issuePath}#document-${encodeURIComponent(row.documentKey)}` : null, + })), + ]; + + return items.sort((left, right) => { + const byDate = left.createdAt.getTime() - right.createdAt.getTime(); + if (byDate !== 0) return byDate; + return left.targetId.localeCompare(right.targetId); + }); +} + +async function buildIssueContext( + db: Pick, + issue: IssueFeedbackContext, + target: ResolvedFeedbackTarget, + state: ReturnType, +) { + const items = await listIssueContextItems(db, issue); + const targetIndex = items.findIndex((item) => item.targetType === target.targetType && item.targetId === target.targetId); + const before = targetIndex >= 0 + ? items.slice(Math.max(0, targetIndex - FEEDBACK_CONTEXT_WINDOW), targetIndex) + : []; + const after = targetIndex >= 0 + ? items.slice(targetIndex + 1, targetIndex + 1 + FEEDBACK_CONTEXT_WINDOW) + : []; + + let remainingChars = MAX_TOTAL_CONTEXT_CHARS; + const serializedItems = [...before, ...after].map((item, index) => { + const relation = index < before.length ? "before" : "after"; + if (remainingChars <= 0) { + state.omittedFields.add("bundle.issueContext.items"); + return null; + } + const maxChars = Math.min(MAX_CONTEXT_ITEM_BODY_CHARS, remainingChars); + const body = sanitizeFeedbackText( + item.body, + state, + `bundle.issueContext.items.${index}.body`, + maxChars, + ); + remainingChars -= body.length; + return { + type: item.targetType, + id: item.targetId, + label: item.label, + relation, + createdAt: item.createdAt.toISOString(), + authorAgentId: item.authorAgentId, + authorUserId: item.authorUserId, + createdByRunId: item.createdByRunId, + documentKey: item.documentKey, + documentTitle: item.documentTitle, + revisionNumber: item.revisionNumber, + targetPath: item.targetPath, + body, + excerpt: truncateExcerpt(body), + }; + }).filter((item): item is NonNullable => item !== null); + + const descriptionExcerpt = issue.description + ? sanitizeFeedbackText(issue.description, state, "bundle.issueContext.issue.description", MAX_DESCRIPTION_CHARS) + : null; + + return { + issue: { + id: issue.id, + identifier: issue.identifier, + title: issue.title, + projectId: issue.projectId, + path: buildIssuePath(issue.identifier), + descriptionExcerpt: descriptionExcerpt ? truncateExcerpt(descriptionExcerpt, MAX_DESCRIPTION_CHARS) : null, + }, + items: serializedItems, + }; +} + +async function buildAgentContext( + db: Pick, + companyId: string, + authorAgentId: string | null, + createdByRunId: string | null, + state: ReturnType, +) { + if (!authorAgentId) { + state.notes.add("author_agent_missing"); + return null; + } + + const agent = await db + .select({ + id: agents.id, + companyId: agents.companyId, + name: agents.name, + role: agents.role, + title: agents.title, + status: agents.status, + adapterType: agents.adapterType, + adapterConfig: agents.adapterConfig, + runtimeConfig: agents.runtimeConfig, + }) + .from(agents) + .where(eq(agents.id, authorAgentId)) + .then((rows) => rows[0] ?? null); + + if (!agent || agent.companyId !== companyId) { + state.notes.add("author_agent_unavailable"); + return null; + } + + const adapterConfig = asRecord(agent.adapterConfig) ?? {}; + const runtimeConfig = asRecord(agent.runtimeConfig) ?? {}; + const desiredSkillRefs = uniqueNonEmpty(readPaperclipSkillSyncPreference(adapterConfig).desiredSkills).slice(0, MAX_SKILLS); + const availableSkills = desiredSkillRefs.length === 0 + ? [] + : await db + .select() + .from(companySkills) + .where(eq(companySkills.companyId, companyId)); + const matchedSkills = availableSkills + .filter((skill) => desiredSkillRefs.some((reference) => matchesSkillReference(skill, reference))) + .slice(0, MAX_SKILLS); + const unresolvedSkillRefs = desiredSkillRefs.filter( + (reference) => !matchedSkills.some((skill) => matchesSkillReference(skill, reference)), + ); + + if (availableSkills.length > MAX_SKILLS || desiredSkillRefs.length > MAX_SKILLS) { + state.omittedFields.add("bundle.agentContext.skills"); + } + + const run = createdByRunId + ? await db + .select({ + id: heartbeatRuns.id, + companyId: heartbeatRuns.companyId, + agentId: heartbeatRuns.agentId, + invocationSource: heartbeatRuns.invocationSource, + status: heartbeatRuns.status, + startedAt: heartbeatRuns.startedAt, + finishedAt: heartbeatRuns.finishedAt, + usageJson: heartbeatRuns.usageJson, + sessionIdBefore: heartbeatRuns.sessionIdBefore, + sessionIdAfter: heartbeatRuns.sessionIdAfter, + externalRunId: heartbeatRuns.externalRunId, + }) + .from(heartbeatRuns) + .where(eq(heartbeatRuns.id, createdByRunId)) + .then((rows) => rows[0] ?? null) + : null; + const runCosts = run + ? await db + .select({ + provider: costEvents.provider, + biller: costEvents.biller, + billingType: costEvents.billingType, + model: costEvents.model, + inputTokens: costEvents.inputTokens, + cachedInputTokens: costEvents.cachedInputTokens, + outputTokens: costEvents.outputTokens, + costCents: costEvents.costCents, + }) + .from(costEvents) + .where(and(eq(costEvents.companyId, companyId), eq(costEvents.heartbeatRunId, run.id))) + : []; + + const usage = asRecord(run?.usageJson) ?? {}; + const runtime = { + configuredModel: asString(adapterConfig.model), + configuredInstructionsBundleMode: asString(adapterConfig.instructionsBundleMode), + configuredInstructionsEntryFile: asString(adapterConfig.instructionsEntryFile), + configuredInstructionsFilePath: asString(adapterConfig.instructionsFilePath), + configuredInstructionsRootPath: asString(adapterConfig.instructionsRootPath), + heartbeatPolicy: sanitizeFeedbackValue(runtimeConfig.heartbeat ?? null, state, "bundle.agentContext.runtime.heartbeatPolicy", 400), + provenanceMode: run ? "source_run" : "vote_time_snapshot", + sourceRun: run + ? sanitizeFeedbackValue({ + id: run.id, + invocationSource: run.invocationSource, + status: run.status, + startedAt: run.startedAt?.toISOString() ?? null, + finishedAt: run.finishedAt?.toISOString() ?? null, + externalRunId: run.externalRunId ?? null, + sessionIdBefore: run.sessionIdBefore ?? null, + sessionIdAfter: run.sessionIdAfter ?? null, + usage: { + provider: asString(usage.provider), + biller: asString(usage.biller), + billingType: asString(usage.billingType), + model: asString(usage.model), + inputTokens: asNumber(usage.inputTokens) ?? asNumber(usage.rawInputTokens), + cachedInputTokens: asNumber(usage.cachedInputTokens) ?? asNumber(usage.rawCachedInputTokens), + outputTokens: asNumber(usage.outputTokens) ?? asNumber(usage.rawOutputTokens), + costUsd: asNumber(usage.costUsd), + usageSource: asString(usage.usageSource), + sessionReused: asBoolean(usage.sessionReused), + taskSessionReused: asBoolean(usage.taskSessionReused), + freshSession: asBoolean(usage.freshSession), + sessionRotated: asBoolean(usage.sessionRotated), + sessionRotationReason: asString(usage.sessionRotationReason), + }, + }, state, "bundle.agentContext.runtime.sourceRun", 400) + : null, + costSummary: runCosts.length > 0 + ? { + providers: uniqueNonEmpty(runCosts.map((row) => row.provider)), + billers: uniqueNonEmpty(runCosts.map((row) => row.biller)), + billingTypes: uniqueNonEmpty(runCosts.map((row) => row.billingType)), + models: uniqueNonEmpty(runCosts.map((row) => row.model)), + inputTokens: runCosts.reduce((sum, row) => sum + row.inputTokens, 0), + cachedInputTokens: runCosts.reduce((sum, row) => sum + row.cachedInputTokens, 0), + outputTokens: runCosts.reduce((sum, row) => sum + row.outputTokens, 0), + costCents: runCosts.reduce((sum, row) => sum + row.costCents, 0), + } + : null, + }; + + const instructionsBundle = await instructionsSvc.getBundle({ + id: agent.id, + companyId: agent.companyId, + name: agent.name, + adapterConfig: agent.adapterConfig, + }).catch(() => null); + + let entryDigest: string | null = null; + let entryBody: string | null = null; + if (instructionsBundle) { + const readableEntryPath = + instructionsBundle.files.find((file) => file.path === instructionsBundle.entryFile)?.path + ?? instructionsBundle.files[0]?.path + ?? null; + if (readableEntryPath) { + const entryFile = await instructionsSvc.readFile({ + id: agent.id, + companyId: agent.companyId, + name: agent.name, + adapterConfig: agent.adapterConfig, + }, readableEntryPath).catch(() => null); + if (entryFile) { + entryDigest = sha256Digest(entryFile.content); + entryBody = sanitizeFeedbackText( + entryFile.content, + state, + "bundle.agentContext.instructions.entryBody", + MAX_INSTRUCTIONS_BODY_CHARS, + ); + } + } + if (instructionsBundle.files.length > MAX_INSTRUCTION_FILES) { + state.omittedFields.add("bundle.agentContext.instructions.files"); + } + } + + return { + agent: { + id: agent.id, + name: agent.name, + role: agent.role, + title: agent.title, + status: agent.status, + adapterType: agent.adapterType, + }, + runtime: sanitizeFeedbackValue(runtime, state, "bundle.agentContext.runtime", 400), + skills: { + desiredRefs: desiredSkillRefs, + unresolvedRefs: unresolvedSkillRefs, + items: matchedSkills.map((skill, index) => ({ + key: skill.key, + slug: skill.slug, + name: skill.name, + sourceType: skill.sourceType, + sourceLocator: skill.sourceLocator == null + ? null + : skill.sourceType === "github" || skill.sourceType === "skills_sh" || skill.sourceType === "url" + ? skill.sourceLocator + : sanitizeFeedbackText( + skill.sourceLocator, + state, + `bundle.agentContext.skills.items.${index}.sourceLocator`, + MAX_PATH_CHARS, + ), + sourceRef: skill.sourceRef, + trustLevel: skill.trustLevel, + compatibility: skill.compatibility, + fileInventory: skill.fileInventory, + })), + }, + instructions: instructionsBundle + ? { + mode: instructionsBundle.mode, + entryFile: instructionsBundle.entryFile, + resolvedEntryPath: instructionsBundle.resolvedEntryPath + ? sanitizeFeedbackText( + instructionsBundle.resolvedEntryPath, + state, + "bundle.agentContext.instructions.resolvedEntryPath", + MAX_PATH_CHARS, + ) + : null, + warnings: instructionsBundle.warnings.map((warning, index) => + sanitizeFeedbackText( + warning, + state, + `bundle.agentContext.instructions.warnings.${index}`, + 400, + )), + legacyPromptTemplateActive: instructionsBundle.legacyPromptTemplateActive, + legacyBootstrapPromptTemplateActive: instructionsBundle.legacyBootstrapPromptTemplateActive, + fileCount: instructionsBundle.files.length, + files: instructionsBundle.files.slice(0, MAX_INSTRUCTION_FILES).map((file) => ({ + path: file.path, + size: file.size, + language: file.language, + markdown: file.markdown, + isEntryFile: file.isEntryFile, + virtual: file.virtual, + })), + entryDigest, + entryBody, + } + : null, + paperclip: { + schemaVersion: FEEDBACK_SCHEMA_VERSION, + bundleVersion: FEEDBACK_BUNDLE_VERSION, + }, + }; +} + +async function buildPayloadArtifacts( + db: Pick, + input: { + issue: IssueFeedbackContext; + target: ResolvedFeedbackTarget; + voteId: string; + vote: FeedbackVoteValue; + reason: string | null; + authorUserId: string; + consentVersion: string | null; + sharedWithLabs: boolean; + now: Date; + }, +) { + const state = createFeedbackRedactionState(); + const primaryBody = sanitizeFeedbackText( + input.target.body, + state, + "bundle.primaryContent.body", + MAX_PRIMARY_CONTENT_CHARS, + ); + const primaryContent = { + type: input.target.targetType, + id: input.target.targetId, + label: input.target.label, + createdAt: input.target.createdAt.toISOString(), + authorAgentId: input.target.authorAgentId, + authorUserId: input.target.authorUserId, + createdByRunId: input.target.createdByRunId, + documentId: input.target.documentId, + documentKey: input.target.documentKey, + documentTitle: input.target.documentTitle, + revisionNumber: input.target.revisionNumber, + targetPath: input.target.targetPath, + body: primaryBody, + excerpt: truncateExcerpt(primaryBody), + }; + const targetSummary = buildTargetSummary({ + label: input.target.label, + excerpt: primaryContent.excerpt, + authorAgentId: input.target.authorAgentId, + authorUserId: input.target.authorUserId, + createdAt: input.target.createdAt, + documentKey: input.target.documentKey, + documentTitle: input.target.documentTitle, + revisionNumber: input.target.revisionNumber, + }); + + const basePayload = { + schemaVersion: FEEDBACK_SCHEMA_VERSION, + bundleVersion: FEEDBACK_BUNDLE_VERSION, + sourceApp: "paperclip", + capturedAt: input.now.toISOString(), + consentVersion: input.consentVersion, + vote: { + id: input.voteId, + value: input.vote, + reason: input.reason, + authorUserId: input.authorUserId, + sharedWithLabs: input.sharedWithLabs, + sharedAt: input.sharedWithLabs ? input.now.toISOString() : null, + }, + target: input.target.payloadTarget, + } satisfies Record; + + if (!input.sharedWithLabs) { + state.notes.add("local_only_trace_stores_metadata_only"); + const payloadSnapshot = { + ...basePayload, + exportId: null, + exportEligible: false, + bundle: null, + }; + const redactionSummary = finalizeFeedbackRedactionSummary(state); + return { + exportId: null, + targetSummary, + redactionSummary, + payloadSnapshot: { + ...payloadSnapshot, + redactionSummary, + }, + payloadDigest: sha256Digest({ + ...payloadSnapshot, + redactionSummary, + }), + }; + } + + const exportId = buildExportId(input.voteId, input.now); + const [issueContext, agentContext] = await Promise.all([ + buildIssueContext(db, input.issue, input.target, state), + buildAgentContext(db, input.issue.companyId, input.target.authorAgentId, input.target.createdByRunId, state), + ]); + + const payloadSnapshot = { + ...basePayload, + exportId, + exportEligible: true, + bundle: { + primaryContent, + issueContext, + agentContext, + }, + }; + const redactionSummary = finalizeFeedbackRedactionSummary(state); + const payloadWithSummary = { + ...payloadSnapshot, + redactionSummary, + }; + return { + exportId, + targetSummary, + redactionSummary, + payloadSnapshot: payloadWithSummary, + payloadDigest: sha256Digest(payloadWithSummary), + }; +} + +async function buildFeedbackTraceBundleFromRow( + db: Db, + row: FeedbackTraceRow, +): Promise { + const trace = mapTraceRow(row, true); + const payloadSnapshot = asRecord(trace.payloadSnapshot); + const notes: string[] = []; + const state = createFeedbackRedactionState(); + const files: FeedbackTraceBundleFile[] = []; + const sourceRunId = resolveSourceRunId(payloadSnapshot); + + let paperclipRun: Record | null = null; + let rawAdapterTrace: Record | null = null; + let normalizedAdapterTrace: Record | null = null; + let adapterType: string | null = null; + + if (!sourceRunId) { + appendNote(notes, "source_run_missing"); + } else { + const run = await db + .select({ + id: heartbeatRuns.id, + companyId: heartbeatRuns.companyId, + agentId: heartbeatRuns.agentId, + invocationSource: heartbeatRuns.invocationSource, + status: heartbeatRuns.status, + startedAt: heartbeatRuns.startedAt, + finishedAt: heartbeatRuns.finishedAt, + createdAt: heartbeatRuns.createdAt, + updatedAt: heartbeatRuns.updatedAt, + error: heartbeatRuns.error, + errorCode: heartbeatRuns.errorCode, + usageJson: heartbeatRuns.usageJson, + resultJson: heartbeatRuns.resultJson, + sessionIdBefore: heartbeatRuns.sessionIdBefore, + sessionIdAfter: heartbeatRuns.sessionIdAfter, + externalRunId: heartbeatRuns.externalRunId, + contextSnapshot: heartbeatRuns.contextSnapshot, + logStore: heartbeatRuns.logStore, + logRef: heartbeatRuns.logRef, + logBytes: heartbeatRuns.logBytes, + logSha256: heartbeatRuns.logSha256, + agentName: agents.name, + agentRole: agents.role, + agentTitle: agents.title, + adapterType: agents.adapterType, + }) + .from(heartbeatRuns) + .innerJoin(agents, eq(heartbeatRuns.agentId, agents.id)) + .where(eq(heartbeatRuns.id, sourceRunId)) + .then((rows) => rows[0] ?? null); + + if (!run || run.companyId !== row.companyId) { + appendNote(notes, "source_run_unavailable"); + } else { + adapterType = run.adapterType; + const events = await db + .select() + .from(heartbeatRunEvents) + .where(eq(heartbeatRunEvents.runId, run.id)) + .orderBy(asc(heartbeatRunEvents.seq)); + const logText = await readFullRunLog(run); + const logEntries = parseRunLogEntries(logText); + const stdoutText = logEntries + .filter((entry) => entry.stream === "stdout") + .map((entry) => entry.chunk) + .join(""); + + paperclipRun = sanitizeFeedbackValue( + { + id: run.id, + companyId: run.companyId, + agentId: run.agentId, + agentName: run.agentName, + agentRole: run.agentRole, + agentTitle: run.agentTitle, + adapterType: run.adapterType, + invocationSource: run.invocationSource, + status: run.status, + startedAt: run.startedAt?.toISOString() ?? null, + finishedAt: run.finishedAt?.toISOString() ?? null, + createdAt: run.createdAt.toISOString(), + updatedAt: run.updatedAt.toISOString(), + error: run.error, + errorCode: run.errorCode, + usage: asRecord(run.usageJson), + result: asRecord(run.resultJson), + sessionIdBefore: run.sessionIdBefore, + sessionIdAfter: run.sessionIdAfter, + externalRunId: run.externalRunId, + contextSnapshot: asRecord(run.contextSnapshot), + logStore: run.logStore, + logRef: run.logRef, + logBytes: run.logBytes, + logSha256: run.logSha256, + eventCount: events.length, + }, + state, + "bundle.paperclipRun", + MAX_TRACE_FILE_CHARS, + ) as Record; + + files.push(makeBundleFile({ + path: "paperclip/run.json", + contentType: "application/json", + source: "paperclip_run", + contents: `${JSON.stringify(paperclipRun, null, 2)}\n`, + })); + + const sanitizedEvents = sanitizeFeedbackValue( + events, + state, + "bundle.paperclipRun.events", + MAX_TRACE_FILE_CHARS, + ); + files.push(makeBundleFile({ + path: "paperclip/run-events.json", + contentType: "application/json", + source: "paperclip_run_events", + contents: `${JSON.stringify(sanitizedEvents, null, 2)}\n`, + })); + + if (logText) { + files.push(makeBundleFile({ + path: "paperclip/run-log.ndjson", + contentType: "application/x-ndjson", + source: "paperclip_run_log", + contents: `${sanitizeFeedbackText(logText, state, "bundle.paperclipRun.log", MAX_TRACE_FILE_CHARS)}\n`, + })); + } else { + appendNote(notes, "run_log_missing"); + } + + if (run.adapterType === "codex_local") { + const adapter = await buildCodexTraceFiles({ + companyId: row.companyId, + sessionId: run.sessionIdAfter ?? run.sessionIdBefore, + state, + notes, + }); + files.push(...adapter.files); + rawAdapterTrace = adapter.raw; + normalizedAdapterTrace = adapter.normalized; + } else if (run.adapterType === "claude_local") { + const adapter = await buildClaudeTraceFiles({ + sessionId: run.sessionIdAfter ?? run.sessionIdBefore, + stdoutText, + state, + notes, + }); + files.push(...adapter.files); + rawAdapterTrace = adapter.raw; + normalizedAdapterTrace = adapter.normalized; + } else if (run.adapterType === "opencode_local") { + const adapter = await buildOpenCodeTraceFiles({ + sessionId: run.sessionIdAfter ?? run.sessionIdBefore, + stdoutText, + state, + notes, + }); + files.push(...adapter.files); + rawAdapterTrace = adapter.raw; + normalizedAdapterTrace = adapter.normalized; + } else { + appendNote(notes, "adapter_specific_trace_not_supported"); + } + } + } + + const privacy = { + ...(asRecord(trace.redactionSummary) ?? {}), + bundleRedactionSummary: finalizeFeedbackRedactionSummary(state), + }; + const captureStatus = captureStatusFromFiles(files); + if (captureStatus !== "full" && files.length > 0) { + appendNote(notes, "adapter_trace_partial"); + } + + const envelope = sanitizeFeedbackValue( + { + traceId: trace.id, + exportId: trace.exportId, + companyId: trace.companyId, + feedbackVoteId: trace.feedbackVoteId, + issueId: trace.issueId, + issueIdentifier: trace.issueIdentifier, + issueTitle: trace.issueTitle, + projectId: trace.projectId, + authorUserId: trace.authorUserId, + targetType: trace.targetType, + targetId: trace.targetId, + vote: trace.vote, + status: trace.status, + destination: trace.destination, + consentVersion: trace.consentVersion, + schemaVersion: trace.schemaVersion, + bundleVersion: trace.bundleVersion, + payloadVersion: trace.payloadVersion, + payloadDigest: trace.payloadDigest, + createdAt: trace.createdAt.toISOString(), + exportedAt: trace.exportedAt?.toISOString() ?? null, + }, + state, + "bundle.envelope", + MAX_TRACE_FILE_CHARS, + ) as Record; + + const surface = sanitizeFeedbackValue( + { + target: asRecord(payloadSnapshot?.target), + summary: trace.targetSummary, + }, + state, + "bundle.surface", + MAX_TRACE_FILE_CHARS, + ) as Record; + + const bundle: FeedbackTraceBundle = { + traceId: trace.id, + exportId: trace.exportId, + companyId: trace.companyId, + issueId: trace.issueId, + issueIdentifier: trace.issueIdentifier, + adapterType, + captureStatus, + notes, + envelope, + surface, + paperclipRun, + rawAdapterTrace, + normalizedAdapterTrace, + privacy, + integrity: { + payloadDigest: trace.payloadDigest, + bundleDigest: sha256Digest({ + traceId: trace.id, + files: files.map((file) => ({ + path: file.path, + source: file.source, + sha256: file.sha256, + })), + captureStatus, + }), + }, + files, + }; + + return bundle; +} + +export function feedbackService(db: Db, options: FeedbackServiceOptions = {}) { + return { + listIssueVotesForUser: async (issueId: string, authorUserId: string) => + db + .select() + .from(feedbackVotes) + .where(and(eq(feedbackVotes.issueId, issueId), eq(feedbackVotes.authorUserId, authorUserId))), + + listFeedbackTraces: async (input: { + companyId: string; + issueId?: string; + projectId?: string; + targetType?: FeedbackTargetType; + vote?: FeedbackVoteValue; + status?: FeedbackTraceStatus; + from?: Date; + to?: Date; + sharedOnly?: boolean; + includePayload?: boolean; + }) => { + const filters = [eq(feedbackExports.companyId, input.companyId)]; + if (input.issueId) filters.push(eq(feedbackExports.issueId, input.issueId)); + if (input.projectId) filters.push(eq(feedbackExports.projectId, input.projectId)); + if (input.targetType) filters.push(eq(feedbackExports.targetType, input.targetType)); + if (input.vote) filters.push(eq(feedbackExports.vote, input.vote)); + if (input.status) filters.push(eq(feedbackExports.status, input.status)); + if (input.sharedOnly) filters.push(ne(feedbackExports.status, "local_only")); + if (input.from) filters.push(gte(feedbackExports.createdAt, input.from)); + if (input.to) filters.push(lte(feedbackExports.createdAt, input.to)); + + const rows = await db + .select({ + ...feedbackExportColumns, + issueIdentifier: issues.identifier, + issueTitle: issues.title, + }) + .from(feedbackExports) + .innerJoin(issues, eq(feedbackExports.issueId, issues.id)) + .where(and(...filters)) + .orderBy(desc(feedbackExports.createdAt)); + + return rows.map((row) => mapTraceRow(row, input.includePayload === true)); + }, + + getFeedbackTraceById: async (traceId: string, includePayload = true) => { + const row = await db + .select({ + ...feedbackExportColumns, + issueIdentifier: issues.identifier, + issueTitle: issues.title, + }) + .from(feedbackExports) + .innerJoin(issues, eq(feedbackExports.issueId, issues.id)) + .where(eq(feedbackExports.id, traceId)) + .then((rows) => rows[0] ?? null); + return row ? mapTraceRow(row, includePayload) : null; + }, + + getFeedbackTraceBundle: async (traceId: string) => { + const row = await db + .select({ + ...feedbackExportColumns, + issueIdentifier: issues.identifier, + issueTitle: issues.title, + }) + .from(feedbackExports) + .innerJoin(issues, eq(feedbackExports.issueId, issues.id)) + .where(eq(feedbackExports.id, traceId)) + .then((rows) => rows[0] ?? null); + return row ? buildFeedbackTraceBundleFromRow(db, row) : null; + }, + + flushPendingFeedbackTraces: async (input?: { + companyId?: string; + traceId?: string; + limit?: number; + now?: Date; + }) => { + const shareClient = options.shareClient; + if (!shareClient) { + const filters = [eq(feedbackExports.status, "pending")]; + if (input?.companyId) { + filters.push(eq(feedbackExports.companyId, input.companyId)); + } + if (input?.traceId) { + filters.push(eq(feedbackExports.id, input.traceId)); + } + + const rows = await db + .select({ + id: feedbackExports.id, + attemptCount: feedbackExports.attemptCount, + }) + .from(feedbackExports) + .where(and(...filters)) + .orderBy(asc(feedbackExports.createdAt), asc(feedbackExports.id)) + .limit(Math.max(1, Math.min(input?.limit ?? 25, 200))); + + const attemptAt = input?.now ?? new Date(); + for (const row of rows) { + await db + .update(feedbackExports) + .set({ + status: "failed", + attemptCount: row.attemptCount + 1, + lastAttemptedAt: attemptAt, + failureReason: FEEDBACK_EXPORT_BACKEND_NOT_CONFIGURED, + updatedAt: attemptAt, + }) + .where(eq(feedbackExports.id, row.id)); + } + + return { + attempted: rows.length, + sent: 0, + failed: rows.length, + }; + } + + const limit = Math.max(1, Math.min(input?.limit ?? 25, 200)); + const filters = [ + or(eq(feedbackExports.status, "pending"), eq(feedbackExports.status, "failed")), + ]; + if (input?.companyId) { + filters.push(eq(feedbackExports.companyId, input.companyId)); + } + if (input?.traceId) { + filters.push(eq(feedbackExports.id, input.traceId)); + } + + const rows = await db + .select({ + ...feedbackExportColumns, + issueIdentifier: issues.identifier, + issueTitle: issues.title, + }) + .from(feedbackExports) + .innerJoin(issues, eq(feedbackExports.issueId, issues.id)) + .where(and(...filters)) + .orderBy(asc(feedbackExports.createdAt), asc(feedbackExports.id)) + .limit(limit); + + let attempted = 0; + let sent = 0; + let failed = 0; + + for (const row of rows) { + const attemptAt = input?.now ?? new Date(); + attempted += 1; + + try { + const bundle = await buildFeedbackTraceBundleFromRow(db, row); + await shareClient.uploadTraceBundle(bundle); + + await db + .update(feedbackExports) + .set({ + status: "sent", + attemptCount: row.attemptCount + 1, + lastAttemptedAt: attemptAt, + exportedAt: attemptAt, + failureReason: null, + updatedAt: attemptAt, + }) + .where(eq(feedbackExports.id, row.id)); + sent += 1; + } catch (error) { + await db + .update(feedbackExports) + .set({ + status: "failed", + attemptCount: row.attemptCount + 1, + lastAttemptedAt: attemptAt, + failureReason: truncateFailureReason(error), + updatedAt: attemptAt, + }) + .where(eq(feedbackExports.id, row.id)); + failed += 1; + } + } + + return { + attempted, + sent, + failed, + }; + }, + + saveIssueVote: async (input: { + issueId: string; + targetType: FeedbackTargetType; + targetId: string; + vote: FeedbackVoteValue; + authorUserId: string; + reason?: string | null; + allowSharing?: boolean; + }) => + db.transaction(async (tx) => { + const issue = await tx + .select({ + id: issues.id, + companyId: issues.companyId, + projectId: issues.projectId, + identifier: issues.identifier, + title: issues.title, + description: issues.description, + }) + .from(issues) + .where(eq(issues.id, input.issueId)) + .then((rows) => rows[0] ?? null); + if (!issue) throw notFound("Issue not found"); + + const target = await resolveFeedbackTarget(tx, issue, input.targetType, input.targetId); + + const existingCompany = await tx + .select({ + feedbackDataSharingEnabled: companies.feedbackDataSharingEnabled, + feedbackDataSharingTermsVersion: companies.feedbackDataSharingTermsVersion, + }) + .from(companies) + .where(eq(companies.id, issue.companyId)) + .then((rows) => rows[0] ?? null); + if (!existingCompany) throw notFound("Company not found"); + + const now = new Date(); + const normalizedReason = normalizeReason(input.vote, input.reason); + const sharedWithLabs = input.allowSharing === true; + let consentEnabledNow = false; + let consentVersion = existingCompany.feedbackDataSharingTermsVersion ?? null; + let persistedSharingPreference: "allowed" | "not_allowed" | null = null; + + if (sharedWithLabs && !existingCompany.feedbackDataSharingEnabled) { + consentEnabledNow = true; + consentVersion = DEFAULT_FEEDBACK_DATA_SHARING_TERMS_VERSION; + await tx + .update(companies) + .set({ + feedbackDataSharingEnabled: true, + feedbackDataSharingConsentAt: now, + feedbackDataSharingConsentByUserId: input.authorUserId, + feedbackDataSharingTermsVersion: consentVersion, + updatedAt: now, + }) + .where(eq(companies.id, issue.companyId)); + } + + const existingInstanceSettings = await tx + .select({ + id: instanceSettings.id, + general: instanceSettings.general, + }) + .from(instanceSettings) + .where(eq(instanceSettings.singletonKey, DEFAULT_INSTANCE_SETTINGS_SINGLETON_KEY)) + .then((rows) => rows[0] ?? null); + + const currentInstanceSettings = + existingInstanceSettings ?? + (await tx + .insert(instanceSettings) + .values({ + singletonKey: DEFAULT_INSTANCE_SETTINGS_SINGLETON_KEY, + general: {}, + experimental: {}, + createdAt: now, + updatedAt: now, + }) + .onConflictDoUpdate({ + target: [instanceSettings.singletonKey], + set: { + updatedAt: now, + }, + }) + .returning({ + id: instanceSettings.id, + general: instanceSettings.general, + }) + .then((rows) => rows[0] ?? null)); + + const currentGeneral = normalizeInstanceGeneralSettings(currentInstanceSettings?.general); + if (currentInstanceSettings && currentGeneral.feedbackDataSharingPreference === "prompt") { + const nextSharingPreference = sharedWithLabs ? "allowed" : "not_allowed"; + const currentGeneralRaw = asRecord(currentInstanceSettings.general) ?? {}; + await tx + .update(instanceSettings) + .set({ + general: { + ...currentGeneralRaw, + censorUsernameInLogs: currentGeneral.censorUsernameInLogs, + feedbackDataSharingPreference: nextSharingPreference, + }, + updatedAt: now, + }) + .where(eq(instanceSettings.id, currentInstanceSettings.id)); + persistedSharingPreference = nextSharingPreference; + } + + const [savedVote] = await tx + .insert(feedbackVotes) + .values({ + companyId: issue.companyId, + issueId: issue.id, + targetType: input.targetType, + targetId: input.targetId, + authorUserId: input.authorUserId, + vote: input.vote, + reason: normalizedReason, + sharedWithLabs, + sharedAt: sharedWithLabs ? now : null, + consentVersion: sharedWithLabs ? (consentVersion ?? DEFAULT_FEEDBACK_DATA_SHARING_TERMS_VERSION) : null, + redactionSummary: null, + updatedAt: now, + }) + .onConflictDoUpdate({ + target: [ + feedbackVotes.companyId, + feedbackVotes.targetType, + feedbackVotes.targetId, + feedbackVotes.authorUserId, + ], + set: { + vote: input.vote, + reason: normalizedReason, + sharedWithLabs, + sharedAt: sharedWithLabs ? now : null, + consentVersion: sharedWithLabs ? (consentVersion ?? DEFAULT_FEEDBACK_DATA_SHARING_TERMS_VERSION) : null, + redactionSummary: null, + updatedAt: now, + }, + }) + .returning(); + + const artifacts = await buildPayloadArtifacts(tx, { + issue, + target, + voteId: savedVote.id, + vote: input.vote, + reason: normalizedReason, + authorUserId: input.authorUserId, + consentVersion: sharedWithLabs ? (consentVersion ?? DEFAULT_FEEDBACK_DATA_SHARING_TERMS_VERSION) : null, + sharedWithLabs, + now, + }); + + await tx + .update(feedbackVotes) + .set({ + redactionSummary: artifacts.redactionSummary, + updatedAt: now, + }) + .where(eq(feedbackVotes.id, savedVote.id)); + + const [savedTrace] = await tx + .insert(feedbackExports) + .values({ + companyId: issue.companyId, + feedbackVoteId: savedVote.id, + issueId: issue.id, + projectId: issue.projectId, + authorUserId: input.authorUserId, + targetType: input.targetType, + targetId: input.targetId, + vote: input.vote, + status: sharedWithLabs ? "pending" : "local_only", + destination: sharedWithLabs ? FEEDBACK_DESTINATION : null, + exportId: artifacts.exportId, + consentVersion: sharedWithLabs ? (consentVersion ?? DEFAULT_FEEDBACK_DATA_SHARING_TERMS_VERSION) : null, + schemaVersion: FEEDBACK_SCHEMA_VERSION, + bundleVersion: FEEDBACK_BUNDLE_VERSION, + payloadVersion: FEEDBACK_PAYLOAD_VERSION, + payloadDigest: artifacts.payloadDigest, + payloadSnapshot: artifacts.payloadSnapshot, + targetSummary: artifacts.targetSummary, + redactionSummary: artifacts.redactionSummary, + updatedAt: now, + }) + .onConflictDoUpdate({ + target: [feedbackExports.feedbackVoteId], + set: { + issueId: issue.id, + projectId: issue.projectId, + authorUserId: input.authorUserId, + targetType: input.targetType, + targetId: input.targetId, + vote: input.vote, + status: sharedWithLabs ? "pending" : "local_only", + destination: sharedWithLabs ? FEEDBACK_DESTINATION : null, + exportId: artifacts.exportId, + consentVersion: sharedWithLabs ? (consentVersion ?? DEFAULT_FEEDBACK_DATA_SHARING_TERMS_VERSION) : null, + schemaVersion: FEEDBACK_SCHEMA_VERSION, + bundleVersion: FEEDBACK_BUNDLE_VERSION, + payloadVersion: FEEDBACK_PAYLOAD_VERSION, + payloadDigest: artifacts.payloadDigest, + payloadSnapshot: artifacts.payloadSnapshot, + targetSummary: artifacts.targetSummary, + redactionSummary: artifacts.redactionSummary, + failureReason: null, + updatedAt: now, + }, + }) + .returning({ + id: feedbackExports.id, + }); + + return { + vote: { + ...savedVote, + redactionSummary: artifacts.redactionSummary, + }, + traceId: savedTrace?.id ?? null, + consentEnabledNow, + persistedSharingPreference, + sharingEnabled: sharedWithLabs, + }; + }), + }; +} diff --git a/server/src/services/github-fetch.ts b/server/src/services/github-fetch.ts new file mode 100644 index 00000000000..787ae0ef5d7 --- /dev/null +++ b/server/src/services/github-fetch.ts @@ -0,0 +1,25 @@ +import { unprocessable } from "../errors.js"; + +function isGitHubDotCom(hostname: string) { + const h = hostname.toLowerCase(); + return h === "github.com" || h === "www.github.com"; +} + +export function gitHubApiBase(hostname: string) { + return isGitHubDotCom(hostname) ? "https://api.github.com" : `https://${hostname}/api/v3`; +} + +export function resolveRawGitHubUrl(hostname: string, owner: string, repo: string, ref: string, filePath: string) { + const p = filePath.replace(/^\/+/, ""); + return isGitHubDotCom(hostname) + ? `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${p}` + : `https://${hostname}/raw/${owner}/${repo}/${ref}/${p}`; +} + +export async function ghFetch(url: string, init?: RequestInit): Promise { + try { + return await fetch(url, init); + } catch { + throw unprocessable(`Could not connect to ${new URL(url).hostname} — ensure the URL points to a GitHub or GitHub Enterprise instance`); + } +} diff --git a/server/src/services/heartbeat-run-summary.ts b/server/src/services/heartbeat-run-summary.ts index 4ef070471d5..441b08826ce 100644 --- a/server/src/services/heartbeat-run-summary.ts +++ b/server/src/services/heartbeat-run-summary.ts @@ -7,6 +7,12 @@ function readNumericField(record: Record, key: string) { return key in record ? record[key] ?? null : undefined; } +function readCommentText(value: unknown) { + if (typeof value !== "string") return null; + const trimmed = value.trim(); + return trimmed.length > 0 ? trimmed : null; +} + export function summarizeHeartbeatRunResultJson( resultJson: Record | null | undefined, ): Record | null { @@ -33,3 +39,18 @@ export function summarizeHeartbeatRunResultJson( return Object.keys(summary).length > 0 ? summary : null; } + +export function buildHeartbeatRunIssueComment( + resultJson: Record | null | undefined, +): string | null { + if (!resultJson || typeof resultJson !== "object" || Array.isArray(resultJson)) { + return null; + } + + return ( + readCommentText(resultJson.summary) + ?? readCommentText(resultJson.result) + ?? readCommentText(resultJson.message) + ?? null + ); +} diff --git a/server/src/services/heartbeat.ts b/server/src/services/heartbeat.ts index 4ab87852274..4260fbc8101 100644 --- a/server/src/services/heartbeat.ts +++ b/server/src/services/heartbeat.ts @@ -2,7 +2,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import { execFile as execFileCallback } from "node:child_process"; import { promisify } from "node:util"; -import { and, asc, desc, eq, gt, inArray, sql } from "drizzle-orm"; +import { and, asc, desc, eq, gt, inArray, isNull, or, sql } from "drizzle-orm"; import type { Db } from "@paperclipai/db"; import type { BillingType, ExecutionWorkspace, ExecutionWorkspaceConfig } from "@paperclipai/shared"; import { @@ -12,6 +12,7 @@ import { agentWakeupRequests, heartbeatRunEvents, heartbeatRuns, + issueComments, issues, projects, projectWorkspaces, @@ -25,11 +26,13 @@ import type { AdapterExecutionResult, AdapterInvocationMeta, AdapterSessionCodec import { createLocalAgentJwt } from "../agent-auth-jwt.js"; import { parseObject, asBoolean, asNumber, appendWithCap, MAX_EXCERPT_BYTES } from "../adapters/utils.js"; import { costService } from "./costs.js"; +import { trackAgentFirstHeartbeat } from "@paperclipai/shared/telemetry"; +import { getTelemetryClient } from "../telemetry.js"; import { companySkillService } from "./company-skills.js"; import { budgetService, type BudgetEnforcementScope } from "./budgets.js"; import { secretService } from "./secrets.js"; import { resolveDefaultAgentWorkspaceDir, resolveManagedProjectWorkspaceDir } from "../home-paths.js"; -import { summarizeHeartbeatRunResultJson } from "./heartbeat-run-summary.js"; +import { buildHeartbeatRunIssueComment, summarizeHeartbeatRunResultJson } from "./heartbeat-run-summary.js"; import { buildWorkspaceReadyComment, cleanupExecutionWorkspaceArtifacts, @@ -64,10 +67,15 @@ const MAX_LIVE_LOG_CHUNK_BYTES = 8 * 1024; const HEARTBEAT_MAX_CONCURRENT_RUNS_DEFAULT = 1; const HEARTBEAT_MAX_CONCURRENT_RUNS_MAX = 10; const DEFERRED_WAKE_CONTEXT_KEY = "_paperclipWakeContext"; +const WAKE_COMMENT_IDS_KEY = "wakeCommentIds"; +const PAPERCLIP_WAKE_PAYLOAD_KEY = "paperclipWake"; const DETACHED_PROCESS_ERROR_CODE = "process_detached"; const startLocksByAgent = new Map>(); const REPO_ONLY_CWD_SENTINEL = "/__paperclip_repo_only__"; const MANAGED_WORKSPACE_GIT_CLONE_TIMEOUT_MS = 10 * 60 * 1000; +const MAX_INLINE_WAKE_COMMENTS = 8; +const MAX_INLINE_WAKE_COMMENT_BODY_CHARS = 4_000; +const MAX_INLINE_WAKE_COMMENT_BODY_TOTAL_CHARS = 12_000; const execFile = promisify(execFileCallback); const SESSIONED_LOCAL_ADAPTERS = new Set([ "claude_local", @@ -607,6 +615,14 @@ function parseIssueAssigneeAdapterOverrides( }; } +/** + * Synthetic task key for timer/heartbeat wakes that have no issue context. + * This allows timer wakes to participate in the `agentTaskSessions` system + * and benefit from robust session resume, instead of relying solely on the + * simpler `agentRuntimeState.sessionId` fallback. + */ +const HEARTBEAT_TASK_KEY = "__heartbeat__"; + function deriveTaskKey( contextSnapshot: Record | null | undefined, payload: Record | null | undefined, @@ -622,13 +638,42 @@ function deriveTaskKey( ); } +/** + * Extended task key derivation that falls back to a stable synthetic key + * for timer/heartbeat wakes. This ensures timer wakes can resume their + * previous session via `agentTaskSessions` instead of starting fresh. + * + * The synthetic key is only used when: + * - No explicit task/issue key exists in the context + * - The wake source is "timer" (scheduled heartbeat) + */ +export function deriveTaskKeyWithHeartbeatFallback( + contextSnapshot: Record | null | undefined, + payload: Record | null | undefined, +) { + const explicit = deriveTaskKey(contextSnapshot, payload); + if (explicit) return explicit; + + const wakeSource = readNonEmptyString(contextSnapshot?.wakeSource); + if (wakeSource === "timer") return HEARTBEAT_TASK_KEY; + + return null; +} + export function shouldResetTaskSessionForWake( contextSnapshot: Record | null | undefined, ) { if (contextSnapshot?.forceFreshSession === true) return true; const wakeReason = readNonEmptyString(contextSnapshot?.wakeReason); - if (wakeReason === "issue_assigned") return true; + if ( + wakeReason === "issue_assigned" || + wakeReason === "execution_review_requested" || + wakeReason === "execution_approval_requested" || + wakeReason === "execution_changes_requested" + ) { + return true; + } return false; } @@ -646,6 +691,9 @@ function describeSessionResetReason( const wakeReason = readNonEmptyString(contextSnapshot?.wakeReason); if (wakeReason === "issue_assigned") return "wake reason is issue_assigned"; + if (wakeReason === "execution_review_requested") return "wake reason is execution_review_requested"; + if (wakeReason === "execution_approval_requested") return "wake reason is execution_approval_requested"; + if (wakeReason === "execution_changes_requested") return "wake reason is execution_changes_requested"; return null; } @@ -653,7 +701,9 @@ function deriveCommentId( contextSnapshot: Record | null | undefined, payload: Record | null | undefined, ) { + const batchedCommentId = extractWakeCommentIds(contextSnapshot).at(-1); return ( + batchedCommentId ?? readNonEmptyString(contextSnapshot?.wakeCommentId) ?? readNonEmptyString(contextSnapshot?.commentId) ?? readNonEmptyString(payload?.commentId) ?? @@ -661,6 +711,50 @@ function deriveCommentId( ); } +export function extractWakeCommentIds( + contextSnapshot: Record | null | undefined, +): string[] { + const raw = contextSnapshot?.[WAKE_COMMENT_IDS_KEY]; + if (!Array.isArray(raw)) return []; + const out: string[] = []; + for (const entry of raw) { + const value = readNonEmptyString(entry); + if (!value || out.includes(value)) continue; + out.push(value); + } + return out; +} + +function mergeWakeCommentIds(...values: Array): string[] { + const merged: string[] = []; + const append = (value: unknown) => { + const normalized = readNonEmptyString(value); + if (!normalized || merged.includes(normalized)) return; + merged.push(normalized); + }; + + for (const value of values) { + if (Array.isArray(value)) { + for (const entry of value) append(entry); + continue; + } + if (typeof value === "object" && value !== null) { + const candidate = value as Record; + const batched = extractWakeCommentIds(candidate); + if (batched.length > 0) { + for (const entry of batched) append(entry); + continue; + } + append(candidate.wakeCommentId); + append(candidate.commentId); + continue; + } + append(value); + } + + return merged; +} + function enrichWakeContextSnapshot(input: { contextSnapshot: Record; reason: string | null; @@ -673,6 +767,7 @@ function enrichWakeContextSnapshot(input: { const commentIdFromPayload = readNonEmptyString(payload?.["commentId"]); const taskKey = deriveTaskKey(contextSnapshot, payload); const wakeCommentId = deriveCommentId(contextSnapshot, payload); + const wakeCommentIds = mergeWakeCommentIds(contextSnapshot, commentIdFromPayload); if (!readNonEmptyString(contextSnapshot["wakeReason"]) && reason) { contextSnapshot.wakeReason = reason; @@ -689,7 +784,15 @@ function enrichWakeContextSnapshot(input: { if (!readNonEmptyString(contextSnapshot["commentId"]) && commentIdFromPayload) { contextSnapshot.commentId = commentIdFromPayload; } - if (!readNonEmptyString(contextSnapshot["wakeCommentId"]) && wakeCommentId) { + if (wakeCommentIds.length > 0) { + const latestCommentId = wakeCommentIds[wakeCommentIds.length - 1]; + contextSnapshot[WAKE_COMMENT_IDS_KEY] = wakeCommentIds; + contextSnapshot.commentId = latestCommentId; + contextSnapshot.wakeCommentId = latestCommentId; + // Once comment ids are normalized into the snapshot, rebuild the structured + // wake payload from those ids later instead of carrying forward stale data. + delete contextSnapshot[PAPERCLIP_WAKE_PAYLOAD_KEY]; + } else if (!readNonEmptyString(contextSnapshot["wakeCommentId"]) && wakeCommentId) { contextSnapshot.wakeCommentId = wakeCommentId; } if (!readNonEmptyString(contextSnapshot["wakeSource"]) && source) { @@ -708,7 +811,7 @@ function enrichWakeContextSnapshot(input: { }; } -function mergeCoalescedContextSnapshot( +export function mergeCoalescedContextSnapshot( existingRaw: unknown, incoming: Record, ) { @@ -717,14 +820,142 @@ function mergeCoalescedContextSnapshot( ...existing, ...incoming, }; - const commentId = deriveCommentId(incoming, null); - if (commentId) { - merged.commentId = commentId; - merged.wakeCommentId = commentId; + const mergedCommentIds = mergeWakeCommentIds(existing, incoming); + if (mergedCommentIds.length > 0) { + const latestCommentId = mergedCommentIds[mergedCommentIds.length - 1]; + merged[WAKE_COMMENT_IDS_KEY] = mergedCommentIds; + merged.commentId = latestCommentId; + merged.wakeCommentId = latestCommentId; + // The merged context should carry canonical comment ids; the next wake will + // regenerate any structured payload from those ids. + delete merged[PAPERCLIP_WAKE_PAYLOAD_KEY]; } return merged; } +async function buildPaperclipWakePayload(input: { + db: Db; + companyId: string; + contextSnapshot: Record; + issueSummary?: + | { + id: string; + identifier: string | null; + title: string; + status: string; + priority: string; + } + | null; +}) { + const executionStage = parseObject(input.contextSnapshot.executionStage); + const commentIds = extractWakeCommentIds(input.contextSnapshot); + const issueId = readNonEmptyString(input.contextSnapshot.issueId); + const issueSummary = + input.issueSummary ?? + (issueId + ? await input.db + .select({ + id: issues.id, + identifier: issues.identifier, + title: issues.title, + status: issues.status, + priority: issues.priority, + }) + .from(issues) + .where(and(eq(issues.id, issueId), eq(issues.companyId, input.companyId))) + .then((rows) => rows[0] ?? null) + : null); + if (commentIds.length === 0 && Object.keys(executionStage).length === 0 && !issueSummary) return null; + + const commentRows = + commentIds.length === 0 + ? [] + : await input.db + .select({ + id: issueComments.id, + issueId: issueComments.issueId, + body: issueComments.body, + authorAgentId: issueComments.authorAgentId, + authorUserId: issueComments.authorUserId, + createdAt: issueComments.createdAt, + }) + .from(issueComments) + .where( + and( + eq(issueComments.companyId, input.companyId), + inArray(issueComments.id, commentIds), + ), + ); + + const commentsById = new Map(commentRows.map((comment) => [comment.id, comment])); + const comments: Array> = []; + let remainingBodyChars = MAX_INLINE_WAKE_COMMENT_BODY_TOTAL_CHARS; + let truncated = false; + let missingCommentCount = 0; + + for (const commentId of commentIds) { + const row = commentsById.get(commentId); + if (!row) { + truncated = true; + missingCommentCount += 1; + continue; + } + if (comments.length >= MAX_INLINE_WAKE_COMMENTS) { + truncated = true; + break; + } + + const fullBody = row.body; + const allowedBodyChars = Math.min(MAX_INLINE_WAKE_COMMENT_BODY_CHARS, remainingBodyChars); + if (allowedBodyChars <= 0) { + truncated = true; + break; + } + + const body = fullBody.length > allowedBodyChars ? fullBody.slice(0, allowedBodyChars) : fullBody; + const bodyTruncated = body.length < fullBody.length; + if (bodyTruncated) truncated = true; + remainingBodyChars -= body.length; + + comments.push({ + id: row.id, + issueId: row.issueId, + body, + bodyTruncated, + createdAt: row.createdAt.toISOString(), + author: row.authorAgentId + ? { type: "agent", id: row.authorAgentId } + : row.authorUserId + ? { type: "user", id: row.authorUserId } + : { type: "system", id: null }, + }); + } + + return { + reason: readNonEmptyString(input.contextSnapshot.wakeReason), + issue: issueSummary + ? { + id: issueSummary.id, + identifier: issueSummary.identifier, + title: issueSummary.title, + status: issueSummary.status, + priority: issueSummary.priority, + } + : null, + executionStage: Object.keys(executionStage).length > 0 ? executionStage : null, + commentIds, + latestCommentId: commentIds[commentIds.length - 1] ?? null, + comments, + commentWindow: { + requestedCount: commentIds.length, + includedCount: comments.length, + missingCount: missingCommentCount, + }, + truncated, + fallbackFetchNeeded: truncated || missingCommentCount > 0, + }; +} + function runTaskKey(run: typeof heartbeatRuns.$inferSelect) { return deriveTaskKey(run.contextSnapshot as Record | null, null); } @@ -1588,6 +1819,222 @@ export function heartbeatService(db: Db) { return updated; } + async function patchRunIssueCommentStatus( + runId: string, + patch: Partial>, + ) { + return db + .update(heartbeatRuns) + .set({ ...patch, updatedAt: new Date() }) + .where(eq(heartbeatRuns.id, runId)) + .returning() + .then((rows) => rows[0] ?? null); + } + + async function findRunIssueComment(runId: string, companyId: string, issueId: string) { + return db + .select({ + id: issueComments.id, + }) + .from(issueComments) + .where( + and( + eq(issueComments.companyId, companyId), + eq(issueComments.issueId, issueId), + eq(issueComments.createdByRunId, runId), + ), + ) + .orderBy(desc(issueComments.createdAt), desc(issueComments.id)) + .limit(1) + .then((rows) => rows[0] ?? null); + } + + async function enqueueMissingIssueCommentRetry( + run: typeof heartbeatRuns.$inferSelect, + agent: typeof agents.$inferSelect, + issueId: string, + ) { + const contextSnapshot = parseObject(run.contextSnapshot); + const taskKey = deriveTaskKeyWithHeartbeatFallback(contextSnapshot, null); + const sessionBefore = await resolveSessionBeforeForWakeup(agent, taskKey); + const retryContextSnapshot = { + ...contextSnapshot, + retryOfRunId: run.id, + wakeReason: "missing_issue_comment", + retryReason: "missing_issue_comment", + missingIssueCommentForRunId: run.id, + }; + const now = new Date(); + + const retryRun = await db.transaction(async (tx) => { + await tx.execute( + sql`select id from issues where company_id = ${run.companyId} and execution_run_id = ${run.id} for update`, + ); + + const issue = await tx + .select({ id: issues.id }) + .from(issues) + .where(and(eq(issues.companyId, run.companyId), eq(issues.executionRunId, run.id))) + .then((rows) => rows[0] ?? null); + if (!issue) return null; + + const wakeupRequest = await tx + .insert(agentWakeupRequests) + .values({ + companyId: run.companyId, + agentId: run.agentId, + source: "automation", + triggerDetail: "system", + reason: "missing_issue_comment", + payload: { + issueId, + retryOfRunId: run.id, + retryReason: "missing_issue_comment", + }, + status: "queued", + requestedByActorType: "system", + requestedByActorId: null, + updatedAt: now, + }) + .returning() + .then((rows) => rows[0]); + + const queuedRun = await tx + .insert(heartbeatRuns) + .values({ + companyId: run.companyId, + agentId: run.agentId, + invocationSource: "automation", + triggerDetail: "system", + status: "queued", + wakeupRequestId: wakeupRequest.id, + contextSnapshot: retryContextSnapshot, + sessionIdBefore: sessionBefore, + retryOfRunId: run.id, + issueCommentStatus: "not_applicable", + updatedAt: now, + }) + .returning() + .then((rows) => rows[0]); + + await tx + .update(agentWakeupRequests) + .set({ + runId: queuedRun.id, + updatedAt: now, + }) + .where(eq(agentWakeupRequests.id, wakeupRequest.id)); + + await tx + .update(issues) + .set({ + executionRunId: queuedRun.id, + executionAgentNameKey: normalizeAgentNameKey(agent.name), + executionLockedAt: now, + updatedAt: now, + }) + .where(eq(issues.id, issue.id)); + + await tx + .update(heartbeatRuns) + .set({ + issueCommentStatus: "retry_queued", + issueCommentRetryQueuedAt: now, + updatedAt: now, + }) + .where(eq(heartbeatRuns.id, run.id)); + + return queuedRun; + }); + + if (!retryRun) return null; + + publishLiveEvent({ + companyId: retryRun.companyId, + type: "heartbeat.run.queued", + payload: { + runId: retryRun.id, + agentId: retryRun.agentId, + invocationSource: retryRun.invocationSource, + triggerDetail: retryRun.triggerDetail, + wakeupRequestId: retryRun.wakeupRequestId, + }, + }); + + return retryRun; + } + + async function finalizeIssueCommentPolicy( + run: typeof heartbeatRuns.$inferSelect, + agent: typeof agents.$inferSelect, + ) { + const contextSnapshot = parseObject(run.contextSnapshot); + const issueId = readNonEmptyString(contextSnapshot.issueId); + if (!issueId) { + if (run.issueCommentStatus !== "not_applicable") { + await patchRunIssueCommentStatus(run.id, { + issueCommentStatus: "not_applicable", + issueCommentSatisfiedByCommentId: null, + issueCommentRetryQueuedAt: null, + }); + } + return { outcome: "not_applicable" as const, queuedRun: null }; + } + + const wakeReason = readNonEmptyString(contextSnapshot.wakeReason); + if (wakeReason === "issue_commented" || wakeReason === "issue_comment_mentioned" || wakeReason === "issue_reopened_via_comment") { + if (run.issueCommentStatus !== "not_applicable") { + await patchRunIssueCommentStatus(run.id, { + issueCommentStatus: "not_applicable", + issueCommentSatisfiedByCommentId: null, + issueCommentRetryQueuedAt: null, + }); + } + return { outcome: "not_applicable" as const, queuedRun: null }; + } + + const postedComment = await findRunIssueComment(run.id, run.companyId, issueId); + if (postedComment) { + await patchRunIssueCommentStatus(run.id, { + issueCommentStatus: "satisfied", + issueCommentSatisfiedByCommentId: postedComment.id, + issueCommentRetryQueuedAt: null, + }); + return { outcome: "satisfied" as const, queuedRun: null }; + } + + if (readNonEmptyString(contextSnapshot.retryReason) === "missing_issue_comment") { + await patchRunIssueCommentStatus(run.id, { + issueCommentStatus: "retry_exhausted", + issueCommentSatisfiedByCommentId: null, + }); + await appendRunEvent(run, await nextRunEventSeq(run.id), { + eventType: "lifecycle", + stream: "system", + level: "warn", + message: "Run ended without an issue comment after one retry; no further comment wake will be queued", + }); + return { outcome: "retry_exhausted" as const, queuedRun: null }; + } + + const queuedRun = await enqueueMissingIssueCommentRetry(run, agent, issueId); + if (queuedRun) { + await appendRunEvent(run, await nextRunEventSeq(run.id), { + eventType: "lifecycle", + stream: "system", + level: "warn", + message: "Run ended without an issue comment; queued one follow-up wake to require a comment", + }); + return { outcome: "retry_queued" as const, queuedRun }; + } + + await patchRunIssueCommentStatus(run.id, { + issueCommentStatus: "retry_exhausted", + issueCommentSatisfiedByCommentId: null, + }); + return { outcome: "retry_exhausted" as const, queuedRun: null }; + } + async function enqueueProcessLossRetry( run: typeof heartbeatRuns.$inferSelect, agent: typeof agents.$inferSelect, @@ -1595,7 +2042,7 @@ export function heartbeatService(db: Db) { ) { const contextSnapshot = parseObject(run.contextSnapshot); const issueId = readNonEmptyString(contextSnapshot.issueId); - const taskKey = deriveTaskKey(contextSnapshot, null); + const taskKey = deriveTaskKeyWithHeartbeatFallback(contextSnapshot, null); const sessionBefore = await resolveSessionBeforeForWakeup(agent, taskKey); const retryContextSnapshot = { ...contextSnapshot, @@ -1696,7 +2143,7 @@ export function heartbeatService(db: Db) { const heartbeat = parseObject(runtimeConfig.heartbeat); return { - enabled: asBoolean(heartbeat.enabled, true), + enabled: asBoolean(heartbeat.enabled, false), intervalSec: Math.max(0, asNumber(heartbeat.intervalSec, 0)), wakeOnDemand: asBoolean(heartbeat.wakeOnDemand ?? heartbeat.wakeOnAssignment ?? heartbeat.wakeOnOnDemand ?? heartbeat.wakeOnAutomation, true), maxConcurrentRuns: normalizeMaxConcurrentRuns(heartbeat.maxConcurrentRuns), @@ -1763,6 +2210,29 @@ export function heartbeatService(db: Db) { }); await setWakeupStatus(claimed.wakeupRequestId, "claimed", { claimedAt }); + + // Fix A (lazy locking): stamp executionRunId now that the run is actually running, + // not at queue time. Guard is idempotent — safe if called more than once. + const claimedIssueId = readNonEmptyString(parseObject(claimed.contextSnapshot).issueId); + if (claimedIssueId) { + const claimedAgent = await getAgent(claimed.agentId); + await db + .update(issues) + .set({ + executionRunId: claimed.id, + executionAgentNameKey: normalizeAgentNameKey(claimedAgent?.name), + executionLockedAt: claimedAt, + updatedAt: claimedAt, + }) + .where( + and( + eq(issues.id, claimedIssueId), + eq(issues.companyId, claimed.companyId), + or(isNull(issues.executionRunId), eq(issues.executionRunId, claimed.id)), + ), + ); + } + return claimed; } @@ -1777,6 +2247,8 @@ export function heartbeatService(db: Db) { return; } + const isFirstHeartbeat = !existing.lastHeartbeatAt; + const runningCount = await countRunningRunsForAgent(agentId); const nextStatus = runningCount > 0 @@ -1796,6 +2268,11 @@ export function heartbeatService(db: Db) { .returning() .then((rows) => rows[0] ?? null); + if (isFirstHeartbeat && updated) { + const tc = getTelemetryClient(); + if (tc) trackAgentFirstHeartbeat(tc, { agentRole: updated.role }); + } + if (updated) { publishLiveEvent({ companyId: updated.companyId, @@ -2050,7 +2527,7 @@ export function heartbeatService(db: Db) { const runtime = await ensureRuntimeState(agent); const context = parseObject(run.contextSnapshot); - const taskKey = deriveTaskKey(context, null); + const taskKey = deriveTaskKeyWithHeartbeatFallback(context, null); const sessionCodec = getAdapterSessionCodec(agent.adapterType); const issueId = readNonEmptyString(context.issueId); const issueContext = issueId @@ -2059,6 +2536,8 @@ export function heartbeatService(db: Db) { id: issues.id, identifier: issues.identifier, title: issues.title, + status: issues.status, + priority: issues.priority, projectId: issues.projectId, projectWorkspaceId: issues.projectWorkspaceId, executionWorkspaceId: issues.executionWorkspaceId, @@ -2083,17 +2562,20 @@ export function heartbeatService(db: Db) { : null; const contextProjectId = readNonEmptyString(context.projectId); const executionProjectId = issueContext?.projectId ?? contextProjectId; - const projectExecutionWorkspacePolicy = executionProjectId + const projectContext = executionProjectId ? await db - .select({ executionWorkspacePolicy: projects.executionWorkspacePolicy }) + .select({ + executionWorkspacePolicy: projects.executionWorkspacePolicy, + env: projects.env, + }) .from(projects) .where(and(eq(projects.id, executionProjectId), eq(projects.companyId, agent.companyId))) - .then((rows) => - gateProjectExecutionWorkspacePolicy( - parseProjectExecutionWorkspacePolicy(rows[0]?.executionWorkspacePolicy), - isolatedWorkspacesEnabled, - )) + .then((rows) => rows[0] ?? null) : null; + const projectExecutionWorkspacePolicy = gateProjectExecutionWorkspacePolicy( + parseProjectExecutionWorkspacePolicy(projectContext?.executionWorkspacePolicy), + isolatedWorkspacesEnabled, + ); const taskSession = taskKey ? await getTaskSession(agent.companyId, agent.id, agent.adapterType, taskKey) : null; @@ -2129,12 +2611,33 @@ export function heartbeatService(db: Db) { id: issueContext.id, identifier: issueContext.identifier, title: issueContext.title, + status: issueContext.status, + priority: issueContext.priority, projectId: issueContext.projectId, projectWorkspaceId: issueContext.projectWorkspaceId, executionWorkspaceId: issueContext.executionWorkspaceId, executionWorkspacePreference: issueContext.executionWorkspacePreference, } : null; + const paperclipWakePayload = await buildPaperclipWakePayload({ + db, + companyId: agent.companyId, + contextSnapshot: context, + issueSummary: issueRef + ? { + id: issueRef.id, + identifier: issueRef.identifier, + title: issueRef.title, + status: issueRef.status, + priority: issueRef.priority, + } + : null, + }); + if (paperclipWakePayload) { + context[PAPERCLIP_WAKE_PAYLOAD_KEY] = paperclipWakePayload; + } else { + delete context[PAPERCLIP_WAKE_PAYLOAD_KEY]; + } const existingExecutionWorkspace = issueRef?.executionWorkspaceId ? await executionWorkspacesSvc.getById(issueRef.executionWorkspaceId) : null; const shouldReuseExisting = @@ -2584,7 +3087,7 @@ export function heartbeatService(db: Db) { workspace: executionWorkspace, runtimeServices, }), - { agentId: agent.id }, + { agentId: agent.id, runId: run.id }, ); } catch (err) { await onLog( @@ -2674,7 +3177,7 @@ export function heartbeatService(db: Db) { workspace: executionWorkspace, runtimeServices: adapterManagedRuntimeServices, }), - { agentId: agent.id }, + { agentId: agent.id, runId: run.id }, ); } catch (err) { await onLog( @@ -2798,6 +3301,20 @@ export function heartbeatService(db: Db) { exitCode: adapterResult.exitCode, }, }); + if (issueId && outcome === "succeeded") { + try { + const issueComment = buildHeartbeatRunIssueComment(adapterResult.resultJson ?? null); + if (issueComment) { + await issuesSvc.addComment(issueId, issueComment, { agentId: agent.id, runId: finalizedRun.id }); + } + } catch (err) { + await onLog( + "stderr", + `[paperclip] Failed to post run summary comment: ${err instanceof Error ? err.message : String(err)}\n`, + ); + } + } + await finalizeIssueCommentPolicy(finalizedRun, agent); await releaseIssueExecutionAndPromote(finalizedRun); } @@ -2864,6 +3381,7 @@ export function heartbeatService(db: Db) { level: "error", message, }); + await finalizeIssueCommentPolicy(failedRun, agent); await releaseIssueExecutionAndPromote(failedRun); await updateRuntimeState(agent, failedRun, { @@ -2915,6 +3433,10 @@ export function heartbeatService(db: Db) { level: "error", message, }).catch(() => undefined); + const failedAgent = await getAgent(run.agentId).catch(() => null); + if (failedAgent) { + await finalizeIssueCommentPolicy(failedRun, failedAgent).catch(() => undefined); + } await releaseIssueExecutionAndPromote(failedRun).catch(() => undefined); } // Ensure the agent is not left stuck in "running" if the inner catch handler's @@ -3434,15 +3956,9 @@ export function heartbeatService(db: Db) { }) .where(eq(agentWakeupRequests.id, wakeupRequest.id)); - await tx - .update(issues) - .set({ - executionRunId: newRun.id, - executionAgentNameKey: agentNameKey, - executionLockedAt: new Date(), - updatedAt: new Date(), - }) - .where(eq(issues.id, issue.id)); + // executionRunId is NOT stamped here (enqueueWakeup queues the run but + // doesn't start it). It will be stamped in claimQueuedRun() once the run + // transitions to "running" — Fix A (lazy locking). return { kind: "queued" as const, run: newRun }; }); diff --git a/server/src/services/hire-hook.ts b/server/src/services/hire-hook.ts index 6b6e22ce963..79a38177ee1 100644 --- a/server/src/services/hire-hook.ts +++ b/server/src/services/hire-hook.ts @@ -2,7 +2,7 @@ import { and, eq } from "drizzle-orm"; import type { Db } from "@paperclipai/db"; import { agents } from "@paperclipai/db"; import type { HireApprovedPayload } from "@paperclipai/adapter-utils"; -import { findServerAdapter } from "../adapters/registry.js"; +import { findActiveServerAdapter } from "../adapters/registry.js"; import { logger } from "../middleware/logger.js"; import { logActivity } from "./activity-log.js"; @@ -40,7 +40,7 @@ export async function notifyHireApproved( } const adapterType = row.adapterType ?? "process"; - const adapter = findServerAdapter(adapterType); + const adapter = findActiveServerAdapter(adapterType); const onHireApproved = adapter?.onHireApproved; if (!onHireApproved) { return; diff --git a/server/src/services/inbox-dismissals.ts b/server/src/services/inbox-dismissals.ts new file mode 100644 index 00000000000..68032c69dc1 --- /dev/null +++ b/server/src/services/inbox-dismissals.ts @@ -0,0 +1,41 @@ +import { and, desc, eq } from "drizzle-orm"; +import type { Db } from "@paperclipai/db"; +import { inboxDismissals } from "@paperclipai/db"; + +export function inboxDismissalService(db: Db) { + return { + list: async (companyId: string, userId: string) => + db + .select() + .from(inboxDismissals) + .where(and(eq(inboxDismissals.companyId, companyId), eq(inboxDismissals.userId, userId))) + .orderBy(desc(inboxDismissals.updatedAt)), + + dismiss: async ( + companyId: string, + userId: string, + itemKey: string, + dismissedAt: Date = new Date(), + ) => { + const now = new Date(); + const [row] = await db + .insert(inboxDismissals) + .values({ + companyId, + userId, + itemKey, + dismissedAt, + updatedAt: now, + }) + .onConflictDoUpdate({ + target: [inboxDismissals.companyId, inboxDismissals.userId, inboxDismissals.itemKey], + set: { + dismissedAt, + updatedAt: now, + }, + }) + .returning(); + return row; + }, + }; +} diff --git a/server/src/services/index.ts b/server/src/services/index.ts index 241355b6e17..909fb05e10f 100644 --- a/server/src/services/index.ts +++ b/server/src/services/index.ts @@ -1,4 +1,5 @@ export { companyService } from "./companies.js"; +export { feedbackService } from "./feedback.js"; export { companySkillService } from "./company-skills.js"; export { agentService, deduplicateAgentName } from "./agents.js"; export { agentInstructionsService, syncInstructionsBundleConfigFromFilePath } from "./agent-instructions.js"; @@ -18,6 +19,7 @@ export { financeService } from "./finance.js"; export { heartbeatService } from "./heartbeat.js"; export { dashboardService } from "./dashboard.js"; export { sidebarBadgeService } from "./sidebar-badges.js"; +export { inboxDismissalService } from "./inbox-dismissals.js"; export { accessService } from "./access.js"; export { boardAuthService } from "./board-auth.js"; export { instanceSettingsService } from "./instance-settings.js"; diff --git a/server/src/services/instance-settings.ts b/server/src/services/instance-settings.ts index ccefea7c90d..7856591d689 100644 --- a/server/src/services/instance-settings.ts +++ b/server/src/services/instance-settings.ts @@ -1,6 +1,7 @@ import type { Db } from "@paperclipai/db"; import { companies, instanceSettings } from "@paperclipai/db"; import { + DEFAULT_FEEDBACK_DATA_SHARING_PREFERENCE, instanceGeneralSettingsSchema, type InstanceGeneralSettings, instanceExperimentalSettingsSchema, @@ -18,10 +19,15 @@ function normalizeGeneralSettings(raw: unknown): InstanceGeneralSettings { if (parsed.success) { return { censorUsernameInLogs: parsed.data.censorUsernameInLogs ?? false, + keyboardShortcuts: parsed.data.keyboardShortcuts ?? false, + feedbackDataSharingPreference: + parsed.data.feedbackDataSharingPreference ?? DEFAULT_FEEDBACK_DATA_SHARING_PREFERENCE, }; } return { censorUsernameInLogs: false, + keyboardShortcuts: false, + feedbackDataSharingPreference: DEFAULT_FEEDBACK_DATA_SHARING_PREFERENCE, }; } diff --git a/server/src/services/issue-execution-policy.ts b/server/src/services/issue-execution-policy.ts new file mode 100644 index 00000000000..6f4ba7b5557 --- /dev/null +++ b/server/src/services/issue-execution-policy.ts @@ -0,0 +1,458 @@ +import { randomUUID } from "node:crypto"; +import type { IssueExecutionDecision, IssueExecutionPolicy, IssueExecutionStage, IssueExecutionStagePrincipal, IssueExecutionState } from "@paperclipai/shared"; +import { issueExecutionPolicySchema, issueExecutionStateSchema } from "@paperclipai/shared"; +import { unprocessable } from "../errors.js"; + +type AssigneeLike = { + assigneeAgentId?: string | null; + assigneeUserId?: string | null; +}; + +type IssueLike = AssigneeLike & { + status: string; + executionPolicy?: IssueExecutionPolicy | Record | null; + executionState?: IssueExecutionState | Record | null; +}; + +type ActorLike = { + agentId?: string | null; + userId?: string | null; +}; + +type RequestedAssigneePatch = { + assigneeAgentId?: string | null; + assigneeUserId?: string | null; +}; + +type TransitionInput = { + issue: IssueLike; + policy: IssueExecutionPolicy | null; + requestedStatus?: string; + requestedAssigneePatch: RequestedAssigneePatch; + actor: ActorLike; + commentBody?: string | null; +}; + +type TransitionResult = { + patch: Record; + decision?: Pick; + workflowControlledAssignment?: boolean; +}; + +const COMPLETED_STATUS: IssueExecutionState["status"] = "completed"; +const PENDING_STATUS: IssueExecutionState["status"] = "pending"; +const CHANGES_REQUESTED_STATUS: IssueExecutionState["status"] = "changes_requested"; + +export function normalizeIssueExecutionPolicy(input: unknown): IssueExecutionPolicy | null { + if (input == null) return null; + const parsed = issueExecutionPolicySchema.safeParse(input); + if (!parsed.success) { + throw unprocessable("Invalid execution policy", parsed.error.flatten()); + } + + const stages = parsed.data.stages + .map((stage) => { + const participants: IssueExecutionStage["participants"] = stage.participants + .map((participant) => ({ + id: participant.id ?? randomUUID(), + type: participant.type, + agentId: participant.type === "agent" ? participant.agentId ?? null : null, + userId: participant.type === "user" ? participant.userId ?? null : null, + })) + .filter((participant) => (participant.type === "agent" ? Boolean(participant.agentId) : Boolean(participant.userId))); + + const dedupedParticipants: IssueExecutionStage["participants"] = []; + const seen = new Set(); + for (const participant of participants) { + const key = participant.type === "agent" ? `agent:${participant.agentId}` : `user:${participant.userId}`; + if (seen.has(key)) continue; + seen.add(key); + dedupedParticipants.push(participant); + } + + if (dedupedParticipants.length === 0) return null; + return { + id: stage.id ?? randomUUID(), + type: stage.type, + approvalsNeeded: 1 as const, + participants: dedupedParticipants, + }; + }) + .filter((stage): stage is NonNullable => stage !== null); + + if (stages.length === 0) return null; + + return { + mode: parsed.data.mode ?? "normal", + commentRequired: true, + stages, + }; +} + +export function parseIssueExecutionState(input: unknown): IssueExecutionState | null { + if (input == null) return null; + const parsed = issueExecutionStateSchema.safeParse(input); + if (!parsed.success) return null; + return parsed.data; +} + +export function assigneePrincipal(input: AssigneeLike): IssueExecutionStagePrincipal | null { + if (input.assigneeAgentId) { + return { type: "agent", agentId: input.assigneeAgentId, userId: null }; + } + if (input.assigneeUserId) { + return { type: "user", userId: input.assigneeUserId, agentId: null }; + } + return null; +} + +function actorPrincipal(actor: ActorLike): IssueExecutionStagePrincipal | null { + if (actor.agentId) return { type: "agent", agentId: actor.agentId, userId: null }; + if (actor.userId) return { type: "user", userId: actor.userId, agentId: null }; + return null; +} + +function principalsEqual(a: IssueExecutionStagePrincipal | null, b: IssueExecutionStagePrincipal | null): boolean { + if (!a || !b) return false; + if (a.type !== b.type) return false; + return a.type === "agent" ? a.agentId === b.agentId : a.userId === b.userId; +} + +function findStageById(policy: IssueExecutionPolicy, stageId: string | null | undefined) { + if (!stageId) return null; + return policy.stages.find((stage) => stage.id === stageId) ?? null; +} + +function nextPendingStage(policy: IssueExecutionPolicy, state: IssueExecutionState | null) { + const completed = new Set(state?.completedStageIds ?? []); + return policy.stages.find((stage) => !completed.has(stage.id)) ?? null; +} + +function selectStageParticipant( + stage: IssueExecutionStage, + opts?: { + preferred?: IssueExecutionStagePrincipal | null; + exclude?: IssueExecutionStagePrincipal | null; + }, +): IssueExecutionStagePrincipal | null { + const participants = stage.participants.filter((participant) => !principalsEqual(participant, opts?.exclude ?? null)); + if (participants.length === 0) return null; + if (opts?.preferred) { + const preferred = participants.find((participant) => principalsEqual(participant, opts.preferred ?? null)); + if (preferred) return preferred; + } + const first = participants[0]; + return first ? { type: first.type, agentId: first.agentId ?? null, userId: first.userId ?? null } : null; +} + +function stageHasParticipant(stage: IssueExecutionStage, participant: IssueExecutionStagePrincipal | null): boolean { + if (!participant) return false; + return stage.participants.some((candidate) => principalsEqual(candidate, participant)); +} + +function patchForPrincipal(principal: IssueExecutionStagePrincipal | null) { + if (!principal) { + return { assigneeAgentId: null, assigneeUserId: null }; + } + return principal.type === "agent" + ? { assigneeAgentId: principal.agentId ?? null, assigneeUserId: null } + : { assigneeAgentId: null, assigneeUserId: principal.userId ?? null }; +} + +function buildCompletedState(previous: IssueExecutionState | null, currentStage: IssueExecutionStage): IssueExecutionState { + const completedStageIds = Array.from(new Set([...(previous?.completedStageIds ?? []), currentStage.id])); + return { + status: COMPLETED_STATUS, + currentStageId: null, + currentStageIndex: null, + currentStageType: null, + currentParticipant: null, + returnAssignee: previous?.returnAssignee ?? null, + completedStageIds, + lastDecisionId: previous?.lastDecisionId ?? null, + lastDecisionOutcome: "approved", + }; +} + +function buildPendingState(input: { + previous: IssueExecutionState | null; + stage: IssueExecutionStage; + stageIndex: number; + participant: IssueExecutionStagePrincipal; + returnAssignee: IssueExecutionStagePrincipal | null; +}): IssueExecutionState { + return { + status: PENDING_STATUS, + currentStageId: input.stage.id, + currentStageIndex: input.stageIndex, + currentStageType: input.stage.type, + currentParticipant: input.participant, + returnAssignee: input.returnAssignee, + completedStageIds: input.previous?.completedStageIds ?? [], + lastDecisionId: input.previous?.lastDecisionId ?? null, + lastDecisionOutcome: input.previous?.lastDecisionOutcome ?? null, + }; +} + +function buildChangesRequestedState(previous: IssueExecutionState, currentStage: IssueExecutionStage): IssueExecutionState { + return { + ...previous, + status: CHANGES_REQUESTED_STATUS, + currentStageId: currentStage.id, + currentStageType: currentStage.type, + lastDecisionOutcome: "changes_requested", + }; +} + +function buildPendingStagePatch(input: { + patch: Record; + previous: IssueExecutionState | null; + policy: IssueExecutionPolicy; + stage: IssueExecutionStage; + participant: IssueExecutionStagePrincipal; + returnAssignee: IssueExecutionStagePrincipal | null; +}) { + input.patch.status = "in_review"; + Object.assign(input.patch, patchForPrincipal(input.participant)); + input.patch.executionState = buildPendingState({ + previous: input.previous, + stage: input.stage, + stageIndex: input.policy.stages.findIndex((candidate) => candidate.id === input.stage.id), + participant: input.participant, + returnAssignee: input.returnAssignee, + }); +} + +function clearExecutionStatePatch(input: { + patch: Record; + issueStatus: string; + requestedStatus?: string; + returnAssignee: IssueExecutionStagePrincipal | null; +}) { + input.patch.executionState = null; + if (input.requestedStatus === undefined && input.issueStatus === "in_review" && input.returnAssignee) { + input.patch.status = "in_progress"; + Object.assign(input.patch, patchForPrincipal(input.returnAssignee)); + } +} + +export function applyIssueExecutionPolicyTransition(input: TransitionInput): TransitionResult { + const patch: Record = {}; + const existingState = parseIssueExecutionState(input.issue.executionState); + const currentAssignee = assigneePrincipal(input.issue); + const actor = actorPrincipal(input.actor); + const requestedAssigneePatchProvided = + input.requestedAssigneePatch.assigneeAgentId !== undefined || input.requestedAssigneePatch.assigneeUserId !== undefined; + const explicitAssignee = assigneePrincipal(input.requestedAssigneePatch); + const currentStage = input.policy ? findStageById(input.policy, existingState?.currentStageId) : null; + const requestedStatus = input.requestedStatus; + const activeStage = currentStage && existingState?.status === PENDING_STATUS ? currentStage : null; + + if (!input.policy) { + if (existingState) { + patch.executionState = null; + if (input.issue.status === "in_review" && existingState.returnAssignee) { + patch.status = "in_progress"; + Object.assign(patch, patchForPrincipal(existingState.returnAssignee)); + } + } + return { patch }; + } + + if ( + (input.issue.status === "done" || input.issue.status === "cancelled") && + requestedStatus && + requestedStatus !== "done" && + requestedStatus !== "cancelled" + ) { + patch.executionState = null; + return { patch }; + } + + if (existingState?.currentStageId && !currentStage) { + clearExecutionStatePatch({ + patch, + issueStatus: input.issue.status, + requestedStatus, + returnAssignee: existingState.returnAssignee, + }); + return { patch }; + } + + if (activeStage) { + const currentParticipant = + existingState?.currentParticipant ?? + selectStageParticipant(activeStage, { + exclude: existingState?.returnAssignee ?? null, + }); + if (!currentParticipant) { + throw unprocessable(`No eligible ${activeStage.type} participant is configured for this issue`); + } + + if (!stageHasParticipant(activeStage, currentParticipant)) { + const participant = selectStageParticipant(activeStage, { + preferred: explicitAssignee ?? existingState?.currentParticipant ?? null, + exclude: existingState?.returnAssignee ?? null, + }); + if (!participant) { + clearExecutionStatePatch({ + patch, + issueStatus: input.issue.status, + requestedStatus, + returnAssignee: existingState?.returnAssignee ?? null, + }); + return { patch }; + } + + buildPendingStagePatch({ + patch, + previous: existingState, + policy: input.policy, + stage: activeStage, + participant, + returnAssignee: existingState?.returnAssignee ?? currentAssignee ?? actor, + }); + return { + patch, + workflowControlledAssignment: true, + }; + } + + if (principalsEqual(currentParticipant, actor)) { + if (requestedStatus === "done") { + if (!input.commentBody?.trim()) { + throw unprocessable("Approving a review or approval stage requires a comment"); + } + const approvedState = buildCompletedState(existingState, activeStage); + const nextStage = nextPendingStage( + input.policy, + { ...approvedState, completedStageIds: approvedState.completedStageIds }, + ); + + if (!nextStage) { + patch.executionState = approvedState; + return { + patch, + decision: { + stageId: activeStage.id, + stageType: activeStage.type, + outcome: "approved", + body: input.commentBody.trim(), + }, + }; + } + + const participant = selectStageParticipant(nextStage, { + preferred: explicitAssignee, + exclude: existingState?.returnAssignee ?? null, + }); + if (!participant) { + throw unprocessable(`No eligible ${nextStage.type} participant is configured for this issue`); + } + + buildPendingStagePatch({ + patch, + previous: approvedState, + policy: input.policy, + stage: nextStage, + participant, + returnAssignee: existingState?.returnAssignee ?? currentAssignee ?? actor, + }); + return { + patch, + decision: { + stageId: activeStage.id, + stageType: activeStage.type, + outcome: "approved", + body: input.commentBody.trim(), + }, + workflowControlledAssignment: true, + }; + } + + if (requestedStatus && requestedStatus !== "in_review") { + if (!input.commentBody?.trim()) { + throw unprocessable("Requesting changes requires a comment"); + } + if (!existingState?.returnAssignee) { + throw unprocessable("This execution stage has no return assignee"); + } + patch.status = "in_progress"; + Object.assign(patch, patchForPrincipal(existingState.returnAssignee)); + patch.executionState = buildChangesRequestedState(existingState, activeStage); + return { + patch, + decision: { + stageId: activeStage.id, + stageType: activeStage.type, + outcome: "changes_requested", + body: input.commentBody.trim(), + }, + workflowControlledAssignment: true, + }; + } + } + + if ( + input.issue.status !== "in_review" || + !principalsEqual(currentAssignee, currentParticipant) || + !principalsEqual(existingState?.currentParticipant ?? null, currentParticipant) || + (requestedStatus !== undefined && requestedStatus !== "in_review") || + (requestedAssigneePatchProvided && !principalsEqual(explicitAssignee, currentParticipant)) + ) { + buildPendingStagePatch({ + patch, + previous: existingState, + policy: input.policy, + stage: activeStage, + participant: currentParticipant, + returnAssignee: existingState?.returnAssignee ?? currentAssignee ?? actor, + }); + return { + patch, + workflowControlledAssignment: true, + }; + } + + return { patch }; + } + + const shouldStartWorkflow = + requestedStatus === "done" || + requestedStatus === "in_review"; + + if (!shouldStartWorkflow) { + return { patch }; + } + + const pendingStage = + existingState?.status === CHANGES_REQUESTED_STATUS && currentStage + ? currentStage + : nextPendingStage(input.policy, existingState); + if (!pendingStage) return { patch }; + + const returnAssignee = existingState?.returnAssignee ?? currentAssignee; + const participant = selectStageParticipant(pendingStage, { + preferred: + existingState?.status === CHANGES_REQUESTED_STATUS + ? explicitAssignee ?? existingState.currentParticipant ?? null + : explicitAssignee, + exclude: returnAssignee, + }); + if (!participant) { + throw unprocessable(`No eligible ${pendingStage.type} participant is configured for this issue`); + } + + buildPendingStagePatch({ + patch, + previous: existingState, + policy: input.policy, + stage: pendingStage, + participant, + returnAssignee, + }); + return { + patch, + workflowControlledAssignment: true, + }; +} diff --git a/server/src/services/issues.ts b/server/src/services/issues.ts index b6bdb0666cd..7f9030da85f 100644 --- a/server/src/services/issues.ts +++ b/server/src/services/issues.ts @@ -13,6 +13,7 @@ import { issueAttachments, issueInboxArchives, issueLabels, + issueRelations, issueComments, issueDocuments, issueReadStates, @@ -21,7 +22,8 @@ import { projectWorkspaces, projects, } from "@paperclipai/db"; -import { extractAgentMentionIds, extractProjectMentionIds } from "@paperclipai/shared"; +import type { IssueRelationIssueSummary } from "@paperclipai/shared"; +import { extractAgentMentionIds, extractProjectMentionIds, isUuidLike } from "@paperclipai/shared"; import { conflict, notFound, unprocessable } from "../errors.js"; import { defaultIssueExecutionWorkspaceSettingsForProject, @@ -78,6 +80,7 @@ export interface IssueFilters { originId?: string; includeRoutineExecutions?: boolean; q?: string; + limit?: number; } type IssueRow = typeof issues.$inferSelect; @@ -99,6 +102,11 @@ type IssueUserCommentStats = { myLastCommentAt: Date | null; lastExternalCommentAt: Date | null; }; +type IssueLastActivityStat = { + issueId: string; + latestCommentAt: Date | null; + latestLogAt: Date | null; +}; type IssueUserContextInput = { createdByUserId: string | null; assigneeUserId: string | null; @@ -262,8 +270,8 @@ function issueLastActivityAtExpr(companyId: string, userId: string) { const lastExternalCommentAt = lastExternalCommentAtExpr(companyId, userId); const myLastTouchAt = myLastTouchAtExpr(companyId, userId); return sql` - COALESCE( - ${lastExternalCommentAt}, + GREATEST( + COALESCE(${lastExternalCommentAt}, to_timestamp(0)), CASE WHEN ${issues.updatedAt} > COALESCE(${myLastTouchAt}, to_timestamp(0)) THEN ${issues.updatedAt} @@ -273,6 +281,52 @@ function issueLastActivityAtExpr(companyId: string, userId: string) { `; } +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` + ( + SELECT MAX(${issueComments.createdAt}) + FROM ${issueComments} + WHERE ${issueComments.issueId} = ${issues.id} + AND ${issueComments.companyId} = ${companyId} + ) + `; +} + +function issueLatestLogAtExpr(companyId: string) { + return sql` + ( + 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` + GREATEST( + ${issues.updatedAt}, + COALESCE(${latestCommentAt}, to_timestamp(0)), + COALESCE(${latestLogAt}, to_timestamp(0)) + ) + `; +} + function unreadForUserCondition(companyId: string, userId: string) { const touchedCondition = touchedByUserCondition(companyId, userId); const myLastTouchAt = myLastTouchAtExpr(companyId, userId); @@ -383,6 +437,19 @@ export function deriveIssueUserContext( }; } +function latestIssueActivityAt(...values: Array): 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; +} + async function labelMapForIssues(dbOrTx: any, issueIds: string[]): Promise> { const map = new Map(); if (issueIds.length === 0) return map; @@ -467,6 +534,28 @@ function withActiveRuns( export function issueService(db: Db) { const instanceSettings = instanceSettingsService(db); + 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; + } + function redactIssueComment(comment: T, censorUsernameInLogs: boolean): T { return { ...comment, @@ -589,6 +678,184 @@ export function issueService(db: Db) { ); } + async function getIssueRelationSummaryMap( + companyId: string, + issueIds: string[], + dbOrTx: DbReader = db, + ): Promise> { + const uniqueIssueIds = [...new Set(issueIds)]; + const empty = new Map(); + 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) { + empty.get(row.currentIssueId)?.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 row of blockingRows) { + empty.get(row.currentIssueId)?.blocks.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 relations of empty.values()) { + relations.blockedBy.sort((a, b) => a.title.localeCompare(b.title)); + 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(); + 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([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) { + 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`, + ); + 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, + })), + ); + } + async function isTerminalOrMissingHeartbeatRun(runId: string) { const run = await db .select({ status: heartbeatRuns.status }) @@ -640,6 +907,9 @@ export function issueService(db: Db) { return { list: async (companyId: string, filters?: IssueFilters) => { const conditions = [eq(issues.companyId, companyId)]; + const limit = typeof filters?.limit === "number" && Number.isFinite(filters.limit) + ? Math.max(1, Math.floor(filters.limit)) + : undefined; const touchedByUserId = filters?.touchedByUserId?.trim() || undefined; const inboxArchivedByUserId = filters?.inboxArchivedByUserId?.trim() || undefined; const unreadForUserId = filters?.unreadForUserId?.trim() || undefined; @@ -727,66 +997,159 @@ export function issueService(db: Db) { ELSE 6 END `; - const rows = await db + const canonicalLastActivityAt = issueCanonicalLastActivityAtExpr(companyId); + const baseQuery = db .select() .from(issues) .where(and(...conditions)) - .orderBy(hasSearch ? asc(searchOrder) : asc(priorityOrder), asc(priorityOrder), desc(issues.updatedAt)); + .orderBy( + hasSearch ? asc(searchOrder) : asc(priorityOrder), + asc(priorityOrder), + desc(canonicalLastActivityAt), + desc(issues.updatedAt), + ); + const rows = limit === undefined ? await baseQuery : await baseQuery.limit(limit); const withLabels = await withIssueLabels(db, rows); const runMap = await activeRunMapForIssues(db, withLabels); const withRuns = withActiveRuns(withLabels, runMap); - if (!contextUserId || withRuns.length === 0) { + if (withRuns.length === 0) { return withRuns; } const issueIds = withRuns.map((row) => row.id); - const statsRows = await db - .select({ - issueId: issueComments.issueId, - myLastCommentAt: sql` - MAX(CASE WHEN ${issueComments.authorUserId} = ${contextUserId} THEN ${issueComments.createdAt} END) - `, - lastExternalCommentAt: sql` - MAX( - CASE - WHEN ${issueComments.authorUserId} IS NULL OR ${issueComments.authorUserId} <> ${contextUserId} - THEN ${issueComments.createdAt} - END + const [statsRows, readRows, lastActivityRows] = await Promise.all([ + contextUserId + ? db + .select({ + issueId: issueComments.issueId, + myLastCommentAt: sql` + MAX(CASE WHEN ${issueComments.authorUserId} = ${contextUserId} THEN ${issueComments.createdAt} END) + `, + lastExternalCommentAt: sql` + MAX( + CASE + WHEN ${issueComments.authorUserId} IS NULL OR ${issueComments.authorUserId} <> ${contextUserId} + THEN ${issueComments.createdAt} + END + ) + `, + }) + .from(issueComments) + .where( + and( + eq(issueComments.companyId, companyId), + inArray(issueComments.issueId, issueIds), + ), ) - `, - }) - .from(issueComments) - .where( - and( - eq(issueComments.companyId, companyId), - inArray(issueComments.issueId, issueIds), - ), - ) - .groupBy(issueComments.issueId); - const readRows = await db - .select({ - issueId: issueReadStates.issueId, - myLastReadAt: issueReadStates.lastReadAt, - }) - .from(issueReadStates) - .where( - and( - eq(issueReadStates.companyId, companyId), - eq(issueReadStates.userId, contextUserId), - inArray(issueReadStates.issueId, issueIds), - ), - ); + .groupBy(issueComments.issueId) + : Promise.resolve([]), + contextUserId + ? db + .select({ + issueId: issueReadStates.issueId, + myLastReadAt: issueReadStates.lastReadAt, + }) + .from(issueReadStates) + .where( + and( + eq(issueReadStates.companyId, companyId), + eq(issueReadStates.userId, contextUserId), + inArray(issueReadStates.issueId, issueIds), + ), + ) + : Promise.resolve([]), + Promise.all([ + db + .select({ + issueId: issueComments.issueId, + latestCommentAt: sql`MAX(${issueComments.createdAt})`, + }) + .from(issueComments) + .where( + and( + eq(issueComments.companyId, companyId), + inArray(issueComments.issueId, issueIds), + ), + ) + .groupBy(issueComments.issueId), + db + .select({ + issueId: activityLog.entityId, + latestLogAt: sql`MAX(${activityLog.createdAt})`, + }) + .from(activityLog) + .where( + and( + eq(activityLog.companyId, companyId), + eq(activityLog.entityType, "issue"), + inArray(activityLog.entityId, issueIds), + sql`${activityLog.action} NOT IN (${sql.join( + ISSUE_LOCAL_INBOX_ACTIVITY_ACTIONS.map((action) => sql`${action}`), + sql`, `, + )})`, + ), + ) + .groupBy(activityLog.entityId), + ]).then(([commentRows, logRows]) => { + const byIssueId = new Map(); + 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()]; + }), + ]); const statsByIssueId = new Map(statsRows.map((row) => [row.issueId, row])); + const lastActivityByIssueId = new Map(lastActivityRows.map((row) => [row.issueId, row])); + + 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, + lastActivityAt, + }; + }); + } + const readByIssueId = new Map(readRows.map((row) => [row.issueId, row.myLastReadAt])); - return withRuns.map((row) => ({ - ...row, - ...deriveIssueUserContext(row, contextUserId, { - myLastCommentAt: statsByIssueId.get(row.id)?.myLastCommentAt ?? null, - myLastReadAt: readByIssueId.get(row.id) ?? null, - lastExternalCommentAt: statsByIssueId.get(row.id)?.lastExternalCommentAt ?? null, - }), - })); + 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, + lastActivityAt, + ...deriveIssueUserContext(row, contextUserId, { + myLastCommentAt: statsByIssueId.get(row.id)?.myLastCommentAt ?? null, + myLastReadAt: readByIssueId.get(row.id) ?? null, + lastExternalCommentAt: statsByIssueId.get(row.id)?.lastExternalCommentAt ?? null, + }), + }; + }); }, countUnreadTouchedByUser: async (companyId: string, userId: string, status?: string) => { @@ -883,26 +1246,128 @@ export function issueService(db: Db) { return row ?? null; }, - getById: async (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; + 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); }, getByIdentifier: async (identifier: string) => { - const row = await db - .select() + return getIssueByIdentifier(identifier); + }, + + getRelationSummaries: async (issueId: string) => { + const issue = await db + .select({ id: issues.id, companyId: issues.companyId }) .from(issues) - .where(eq(issues.identifier, identifier.toUpperCase())) + .where(eq(issues.id, issueId)) .then((rows) => rows[0] ?? null); - if (!row) return null; - const [enriched] = await withIssueLabels(db, [row]); - return enriched; + if (!issue) throw notFound("Issue not found"); + const relations = await getIssueRelationSummaryMap(issue.companyId, [issueId], db); + return relations.get(issueId) ?? { blockedBy: [], blocks: [] }; + }, + + 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>(); + 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 + .select({ id: issues.id, status: issues.status }) + .from(issues) + .where(and(eq(issues.companyId, parent.companyId), eq(issues.parentId, parentIssueId))); + if (children.length === 0) return null; + if (!children.every((child) => child.status === "done" || child.status === "cancelled")) { + return null; + } + + return { + id: parent.id, + assigneeAgentId: parent.assigneeAgentId, + childIssueIds: children.map((child) => child.id), + }; }, create: async ( @@ -1014,7 +1479,9 @@ export function issueService(db: Db) { } const [company] = await tx .update(companies) - .set({ issueCounter: sql`${companies.issueCounter} + 1` }) + .set({ + issueCounter: sql`greatest(${companies.issueCounter}, ${currentMax}) + 1`, + }) .where(eq(companies.id, companyId)) .returning({ issueCounter: companies.issueCounter, issuePrefix: companies.issuePrefix }); @@ -1052,20 +1519,47 @@ export function issueService(db: Db) { if (inputLabelIds) { await syncIssueLabels(issue.id, companyId, inputLabelIds, tx); } + if (blockedByIssueIds !== undefined) { + await syncBlockedByIssueIds( + issue.id, + companyId, + blockedByIssueIds, + { + agentId: issueData.createdByAgentId ?? null, + userId: issueData.createdByUserId ?? null, + }, + tx, + ); + } const [enriched] = await withIssueLabels(tx, [issue]); return enriched; }); }, - update: async (id: string, data: Partial & { labelIds?: string[] }) => { - const existing = await db + update: async ( + id: string, + data: Partial & { + labelIds?: string[]; + blockedByIssueIds?: string[]; + actorAgentId?: string | null; + actorUserId?: string | null; + }, + dbOrTx: any = db, + ) => { + const existing = await dbOrTx .select() .from(issues) .where(eq(issues.id, id)) - .then((rows) => rows[0] ?? null); + .then((rows: Array) => rows[0] ?? null); if (!existing) return null; - const { labelIds: nextLabelIds, ...issueData } = data; + const { + labelIds: nextLabelIds, + blockedByIssueIds, + actorAgentId, + actorUserId, + ...issueData + } = data; const isolatedWorkspacesEnabled = (await instanceSettings.getExperimental()).enableIsolatedWorkspaces; if (!isolatedWorkspacesEnabled) { delete issueData.executionWorkspaceId; @@ -1120,15 +1614,23 @@ export function issueService(db: Db) { } if (issueData.status && issueData.status !== "in_progress") { patch.checkoutRunId = null; + // Fix B: also clear the execution lock when leaving in_progress + patch.executionRunId = null; + patch.executionAgentNameKey = null; + patch.executionLockedAt = null; } if ( (issueData.assigneeAgentId !== undefined && issueData.assigneeAgentId !== existing.assigneeAgentId) || (issueData.assigneeUserId !== undefined && issueData.assigneeUserId !== existing.assigneeUserId) ) { patch.checkoutRunId = null; + // Fix B: clear execution lock on reassignment, matching checkoutRunId clear + patch.executionRunId = null; + patch.executionAgentNameKey = null; + patch.executionLockedAt = null; } - return db.transaction(async (tx) => { + const runUpdate = async (tx: any) => { const defaultCompanyGoal = await getDefaultCompanyGoal(tx, existing.companyId); const [currentProjectGoalId, nextProjectGoalId] = await Promise.all([ getProjectDefaultGoalId(tx, existing.companyId, existing.projectId), @@ -1152,14 +1654,28 @@ export function issueService(db: Db) { .set(patch) .where(eq(issues.id, id)) .returning() - .then((rows) => rows[0] ?? null); + .then((rows: Array) => rows[0] ?? null); if (!updated) return null; if (nextLabelIds !== undefined) { await syncIssueLabels(updated.id, existing.companyId, nextLabelIds, tx); } + if (blockedByIssueIds !== undefined) { + await syncBlockedByIssueIds( + updated.id, + existing.companyId, + blockedByIssueIds, + { + agentId: actorAgentId ?? null, + userId: actorUserId ?? null, + }, + tx, + ); + } const [enriched] = await withIssueLabels(tx, [updated]); return enriched; - }); + }; + + return dbOrTx === db ? db.transaction(runUpdate) : runUpdate(dbOrTx); }, remove: (id: string) => @@ -1206,6 +1722,40 @@ export function issueService(db: Db) { await assertAssignableAgent(issueCompany.companyId, agentId); const now = new Date(); + + // Fix C: staleness detection — if executionRunId references a run that is no + // longer queued or running, clear it before applying the execution lock condition + // so a dead lock can't produce a spurious 409. + // Wrapped in a transaction with SELECT FOR UPDATE to make the read + clear atomic, + // matching the existing pattern in enqueueWakeup(). + await db.transaction(async (tx) => { + await tx.execute( + sql`select id from issues where id = ${id} for update`, + ); + const preCheckRow = await tx + .select({ executionRunId: issues.executionRunId }) + .from(issues) + .where(eq(issues.id, id)) + .then((rows) => rows[0] ?? null); + if (!preCheckRow?.executionRunId) return; + const lockRun = await tx + .select({ id: heartbeatRuns.id, status: heartbeatRuns.status }) + .from(heartbeatRuns) + .where(eq(heartbeatRuns.id, preCheckRow.executionRunId)) + .then((rows) => rows[0] ?? null); + if (!lockRun || (lockRun.status !== "queued" && lockRun.status !== "running")) { + await tx + .update(issues) + .set({ executionRunId: null, executionAgentNameKey: null, executionLockedAt: null, updatedAt: now }) + .where( + and( + eq(issues.id, id), + eq(issues.executionRunId, preCheckRow.executionRunId), + ), + ); + } + }); + const sameRunAssigneeCondition = checkoutRunId ? and( eq(issues.assigneeAgentId, agentId), @@ -1298,7 +1848,8 @@ export function issueService(db: Db) { expectedCheckoutRunId: current.checkoutRunId, }); if (adopted) { - const row = await db.select().from(issues).where(eq(issues.id, id)).then((rows) => rows[0]!); + const row = await db.select().from(issues).where(eq(issues.id, id)).then((rows) => rows[0] ?? null); + if (!row) throw notFound("Issue not found"); const [enriched] = await withIssueLabels(db, [row]); return enriched; } @@ -1310,7 +1861,8 @@ export function issueService(db: Db) { current.status === "in_progress" && sameRunLock(current.checkoutRunId, checkoutRunId) ) { - const row = await db.select().from(issues).where(eq(issues.id, id)).then((rows) => rows[0]!); + const row = await db.select().from(issues).where(eq(issues.id, id)).then((rows) => rows[0] ?? null); + if (!row) throw notFound("Issue not found"); const [enriched] = await withIssueLabels(db, [row]); return enriched; } @@ -1542,7 +2094,11 @@ export function issueService(db: Db) { return comment ? redactIssueComment(comment, censorUsernameInLogs) : null; })), - addComment: async (issueId: string, body: string, actor: { agentId?: string; userId?: string }) => { + addComment: async ( + issueId: string, + body: string, + actor: { agentId?: string; userId?: string; runId?: string | null }, + ) => { const issue = await db .select({ companyId: issues.companyId }) .from(issues) @@ -1562,6 +2118,7 @@ export function issueService(db: Db) { issueId, authorAgentId: actor.agentId ?? null, authorUserId: actor.userId ?? null, + createdByRunId: actor.runId ?? null, body: redactedBody, }) .returning(); diff --git a/server/src/services/plugin-capability-validator.ts b/server/src/services/plugin-capability-validator.ts index 77e23231585..0d4bb2a739f 100644 --- a/server/src/services/plugin-capability-validator.ts +++ b/server/src/services/plugin-capability-validator.ts @@ -68,6 +68,7 @@ const OPERATION_CAPABILITIES: Record = { "issue.comments.create": ["issue.comments.create"], "activity.log": ["activity.log.write"], "metrics.write": ["metrics.write"], + "telemetry.track": ["telemetry.track"], // Plugin state operations "plugin.state.get": ["plugin.state.read"], diff --git a/server/src/services/plugin-host-services.ts b/server/src/services/plugin-host-services.ts index 9775a62d953..22ccb01708a 100644 --- a/server/src/services/plugin-host-services.ts +++ b/server/src/services/plugin-host-services.ts @@ -34,6 +34,7 @@ import { request as httpRequest } from "node:http"; import { request as httpsRequest } from "node:https"; import { isIP } from "node:net"; import { logger } from "../middleware/logger.js"; +import { getTelemetryClient } from "../telemetry.js"; // --------------------------------------------------------------------------- // SSRF protection for plugin HTTP fetch @@ -47,6 +48,7 @@ const DNS_LOOKUP_TIMEOUT_MS = 5_000; /** Only these protocols are allowed for plugin HTTP requests. */ const ALLOWED_PROTOCOLS = new Set(["http:", "https:"]); +const TELEMETRY_EVENT_NAME_REGEX = /^[a-z0-9][a-z0-9_-]*$/; /** * Check if an IP address is in a private/reserved range (RFC 1918, loopback, @@ -636,6 +638,20 @@ export function buildHostServices( }, }, + telemetry: { + async track(params) { + const eventName = String(params.eventName ?? "").trim(); + if (!TELEMETRY_EVENT_NAME_REGEX.test(eventName)) { + throw new Error( + 'Plugin telemetry event names must be lowercase slugs using letters, numbers, "_" or "-".', + ); + } + const telemetryClient = getTelemetryClient(); + if (!telemetryClient) return; + telemetryClient.track(`plugin.${pluginKey}.${eventName}`, params.dimensions); + }, + }, + logger: { async log(params) { const { level, meta } = params; @@ -791,7 +807,7 @@ export function buildHostServices( return (await issues.addComment( params.issueId, params.body, - {}, + { agentId: params.authorAgentId }, )) as IssueComment; }, }, diff --git a/server/src/services/projects.ts b/server/src/services/projects.ts index 7c7c4bb0a09..f653ea1a2f6 100644 --- a/server/src/services/projects.ts +++ b/server/src/services/projects.ts @@ -4,6 +4,7 @@ import { projects, projectGoals, goals, projectWorkspaces, workspaceRuntimeServi import { PROJECT_COLORS, deriveProjectUrlKey, + hasNonAsciiContent, isUuidLike, normalizeProjectUrlKey, type ProjectCodebase, @@ -13,7 +14,7 @@ import { type ProjectWorkspace, type WorkspaceRuntimeService, } from "@paperclipai/shared"; -import { listWorkspaceRuntimeServicesForProjectWorkspaces } from "./workspace-runtime.js"; +import { listCurrentRuntimeServicesForProjectWorkspaces } from "./workspace-runtime-read-model.js"; import { parseProjectExecutionWorkspacePolicy } from "./execution-workspace-policy.js"; import { mergeProjectWorkspaceRuntimeConfig, readProjectWorkspaceRuntimeConfig } from "./project-workspace-runtime-config.js"; import { resolveManagedProjectWorkspaceDir } from "../home-paths.js"; @@ -222,7 +223,7 @@ async function attachWorkspaces(db: Db, rows: ProjectWithGoals[]): Promise workspace.id), @@ -343,6 +344,8 @@ export function resolveProjectNameForUniqueShortname( ): string { const requestedShortname = normalizeProjectUrlKey(requestedName); if (!requestedShortname) return requestedName; + // Non-ASCII names get a UUID suffix in deriveProjectUrlKey, making slugs inherently unique. + if (hasNonAsciiContent(requestedName)) return requestedName; const usedShortnames = new Set( existingProjects @@ -538,7 +541,7 @@ export function projectService(db: Db) { .where(eq(projectWorkspaces.projectId, projectId)) .orderBy(desc(projectWorkspaces.isPrimary), asc(projectWorkspaces.createdAt), asc(projectWorkspaces.id)); if (rows.length === 0) return []; - const runtimeServicesByWorkspaceId = await listWorkspaceRuntimeServicesForProjectWorkspaces( + const runtimeServicesByWorkspaceId = await listCurrentRuntimeServicesForProjectWorkspaces( db, rows[0]!.companyId, rows.map((workspace) => workspace.id), diff --git a/server/src/services/routines.ts b/server/src/services/routines.ts index f6fdb26f480..db117653b07 100644 --- a/server/src/services/routines.ts +++ b/server/src/services/routines.ts @@ -21,12 +21,21 @@ import type { RoutineRunSummary, RoutineTrigger, RoutineTriggerSecretMaterial, + RoutineVariable, RunRoutine, UpdateRoutine, UpdateRoutineTrigger, } from "@paperclipai/shared"; +import { + getBuiltinRoutineVariableValues, + interpolateRoutineTemplate, + stringifyRoutineVariableValue, + syncRoutineVariablesWithTemplate, +} from "@paperclipai/shared"; +import { trackRoutineRun } from "@paperclipai/shared/telemetry"; import { conflict, forbidden, notFound, unauthorized, unprocessable } from "../errors.js"; import { logger } from "../middleware/logger.js"; +import { getTelemetryClient } from "../telemetry.js"; import { issueService } from "./issues.js"; import { secretService } from "./secrets.js"; import { parseCron, validateCron } from "./cron.js"; @@ -138,6 +147,168 @@ function normalizeWebhookTimestampMs(rawTimestamp: string) { return parsed > 1e12 ? parsed : parsed * 1000; } +function isPlainRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function parseBooleanVariableValue(name: string, raw: unknown) { + if (typeof raw === "boolean") return raw; + if (typeof raw === "number" && (raw === 0 || raw === 1)) return raw === 1; + if (typeof raw === "string") { + const normalized = raw.trim().toLowerCase(); + if (["true", "1", "yes", "y", "on"].includes(normalized)) return true; + if (["false", "0", "no", "n", "off"].includes(normalized)) return false; + } + throw unprocessable(`Variable "${name}" must be a boolean`); +} + +function parseNumberVariableValue(name: string, raw: unknown) { + if (typeof raw === "number" && Number.isFinite(raw)) return raw; + if (typeof raw === "string" && raw.trim().length > 0) { + const parsed = Number(raw); + if (Number.isFinite(parsed)) return parsed; + } + throw unprocessable(`Variable "${name}" must be a number`); +} + +function normalizeRoutineVariableValue(variable: RoutineVariable, raw: unknown): string | number | boolean | null { + if (raw == null) return null; + if (variable.type === "boolean") return parseBooleanVariableValue(variable.name, raw); + if (variable.type === "number") return parseNumberVariableValue(variable.name, raw); + + const normalized = stringifyRoutineVariableValue(raw); + if (variable.type === "select") { + if (!variable.options.includes(normalized)) { + throw unprocessable(`Variable "${variable.name}" must match one of: ${variable.options.join(", ")}`); + } + } + return normalized; +} + +function isMissingRoutineVariableValue(value: string | number | boolean | null) { + return value == null || (typeof value === "string" && value.trim().length === 0); +} + +function assertRoutineVariableDefinitions(variables: RoutineVariable[]) { + for (const variable of variables) { + if (variable.defaultValue != null) { + normalizeRoutineVariableValue(variable, variable.defaultValue); + } + if (variable.type === "select" && variable.options.length === 0) { + throw unprocessable(`Variable "${variable.name}" must define at least one option`); + } + } +} + +function sanitizeRoutineVariableInputs( + variables: Array & Pick> | null | undefined, +): RoutineVariable[] { + return (variables ?? []).map((variable) => ({ + name: variable.name, + label: variable.label ?? null, + type: variable.type ?? "text", + defaultValue: variable.defaultValue ?? null, + required: variable.required ?? true, + options: variable.options ?? [], + })); +} + +function assertScheduleCompatibleVariables(variables: RoutineVariable[]) { + const missingDefaults = variables + .filter((variable) => variable.required) + .filter((variable) => { + try { + return isMissingRoutineVariableValue(normalizeRoutineVariableValue(variable, variable.defaultValue)); + } catch { + return true; + } + }) + .map((variable) => variable.name); + if (missingDefaults.length > 0) { + throw unprocessable( + `Scheduled routines require defaults for required variables: ${missingDefaults.join(", ")}`, + ); + } +} + +function statusRequiresDefaultAgent(status: string) { + return status === "active"; +} + +function normalizeDraftRoutineStatus(status: string, assigneeAgentId: string | null | undefined) { + if (statusRequiresDefaultAgent(status) && !assigneeAgentId) { + return "paused"; + } + return status; +} + +function assertRoutineCanEnable(status: string, assigneeAgentId: string | null | undefined) { + if (statusRequiresDefaultAgent(status) && !assigneeAgentId) { + throw unprocessable("Default agent required"); + } +} + +function collectProvidedRoutineVariables( + source: "schedule" | "manual" | "api" | "webhook", + payload: Record | null | undefined, + variables: Record | null | undefined, +) { + const nestedVariables = isPlainRecord(payload) && isPlainRecord(payload.variables) ? payload.variables : {}; + const provided = { + ...(source === "webhook" && payload ? payload : {}), + ...nestedVariables, + ...(variables ?? {}), + }; + delete provided.variables; + return provided; +} + +function resolveRoutineVariableValues( + variables: RoutineVariable[], + input: { + source: "schedule" | "manual" | "api" | "webhook"; + payload?: Record | null; + variables?: Record | null; + }, +) { + if (variables.length === 0) return {} as Record; + const provided = collectProvidedRoutineVariables(input.source, input.payload, input.variables); + const resolved: Record = {}; + const missing: string[] = []; + + for (const variable of variables) { + const candidate = provided[variable.name] !== undefined ? provided[variable.name] : variable.defaultValue; + const normalized = normalizeRoutineVariableValue(variable, candidate); + if (normalized == null || (typeof normalized === "string" && normalized.trim().length === 0)) { + if (variable.required) missing.push(variable.name); + continue; + } + resolved[variable.name] = normalized; + } + + if (missing.length > 0) { + throw unprocessable(`Missing routine variables: ${missing.join(", ")}`); + } + + return resolved; +} + +function mergeRoutineRunPayload( + payload: Record | null | undefined, + variables: Record, +) { + if (Object.keys(variables).length === 0) return payload ?? null; + if (!payload) return { variables }; + const existingVariables = isPlainRecord(payload.variables) ? payload.variables : {}; + return { + ...payload, + variables: { + ...existingVariables, + ...variables, + }, + }; +} + export function routineService(db: Db, deps: { heartbeat?: IssueAssignmentWakeupDeps } = {}) { const issueSvc = issueService(db); const secretsSvc = secretService(db); @@ -166,7 +337,8 @@ export function routineService(db: Db, deps: { heartbeat?: IssueAssignmentWakeup return routine; } - async function assertAssignableAgent(companyId: string, agentId: string) { + async function assertAssignableAgent(companyId: string, agentId: string | null | undefined) { + if (!agentId) return; const agent = await db .select({ id: agents.id, companyId: agents.companyId, status: agents.status }) .from(agents) @@ -178,7 +350,8 @@ export function routineService(db: Db, deps: { heartbeat?: IssueAssignmentWakeup if (agent.status === "terminated") throw conflict("Cannot assign routines to terminated agents"); } - async function assertProject(companyId: string, projectId: string) { + async function assertProject(companyId: string, projectId: string | null | undefined) { + if (!projectId) return; const project = await db .select({ id: projects.id, companyId: projects.companyId }) .from(projects) @@ -515,8 +688,24 @@ export function routineService(db: Db, deps: { heartbeat?: IssueAssignmentWakeup trigger: typeof routineTriggers.$inferSelect | null; source: "schedule" | "manual" | "api" | "webhook"; payload?: Record | null; + variables?: Record | null; + projectId?: string | null; + assigneeAgentId?: string | null; idempotencyKey?: string | null; + executionWorkspaceId?: string | null; + executionWorkspacePreference?: string | null; + executionWorkspaceSettings?: Record | null; }) { + const projectId = input.projectId ?? input.routine.projectId ?? null; + const assigneeAgentId = input.assigneeAgentId ?? input.routine.assigneeAgentId ?? null; + if (!assigneeAgentId) { + throw unprocessable("Default agent required"); + } + const resolvedVariables = resolveRoutineVariableValues(input.routine.variables ?? [], input); + const allVariables = { ...getBuiltinRoutineVariableValues(), ...resolvedVariables }; + const title = interpolateRoutineTemplate(input.routine.title, allVariables) ?? input.routine.title; + const description = interpolateRoutineTemplate(input.routine.description, allVariables); + const triggerPayload = mergeRoutineRunPayload(input.payload, resolvedVariables); const run = await db.transaction(async (tx) => { const txDb = tx as unknown as Db; await tx.execute( @@ -553,7 +742,7 @@ export function routineService(db: Db, deps: { heartbeat?: IssueAssignmentWakeup status: "received", triggeredAt, idempotencyKey: input.idempotencyKey ?? null, - triggerPayload: input.payload ?? null, + triggerPayload, }) .returning(); @@ -585,17 +774,20 @@ export function routineService(db: Db, deps: { heartbeat?: IssueAssignmentWakeup try { createdIssue = await issueSvc.create(input.routine.companyId, { - projectId: input.routine.projectId, + projectId, goalId: input.routine.goalId, parentId: input.routine.parentIssueId, - title: input.routine.title, - description: input.routine.description, + title, + description, status: "todo", priority: input.routine.priority, - assigneeAgentId: input.routine.assigneeAgentId, + assigneeAgentId, originKind: "routine_execution", originId: input.routine.id, originRunId: createdRun.id, + executionWorkspaceId: input.executionWorkspaceId ?? null, + executionWorkspacePreference: input.executionWorkspacePreference ?? null, + executionWorkspaceSettings: input.executionWorkspaceSettings ?? null, }); } catch (error) { const isOpenExecutionConflict = @@ -695,6 +887,14 @@ export function routineService(db: Db, deps: { heartbeat?: IssueAssignmentWakeup } } + const telemetryClient = getTelemetryClient(); + if (telemetryClient) { + trackRoutineRun(telemetryClient, { + source: run.source, + status: run.status, + }); + } + return run; } @@ -734,8 +934,12 @@ export function routineService(db: Db, deps: { heartbeat?: IssueAssignmentWakeup const row = await getRoutineById(id); if (!row) return null; const [project, assignee, parentIssue, triggers, recentRuns, activeIssue] = await Promise.all([ - db.select().from(projects).where(eq(projects.id, row.projectId)).then((rows) => rows[0] ?? null), - db.select().from(agents).where(eq(agents.id, row.assigneeAgentId)).then((rows) => rows[0] ?? null), + row.projectId + ? db.select().from(projects).where(eq(projects.id, row.projectId)).then((rows) => rows[0] ?? null) + : null, + row.assigneeAgentId + ? db.select().from(agents).where(eq(agents.id, row.assigneeAgentId)).then((rows) => rows[0] ?? null) + : null, row.parentIssueId ? issueSvc.getById(row.parentIssueId) : null, db.select().from(routineTriggers).where(eq(routineTriggers.routineId, row.id)).orderBy(asc(routineTriggers.createdAt)), db @@ -820,24 +1024,31 @@ export function routineService(db: Db, deps: { heartbeat?: IssueAssignmentWakeup }, create: async (companyId: string, input: CreateRoutine, actor: Actor): Promise => { - await assertProject(companyId, input.projectId); - await assertAssignableAgent(companyId, input.assigneeAgentId); + await assertProject(companyId, input.projectId ?? null); + await assertAssignableAgent(companyId, input.assigneeAgentId ?? null); if (input.goalId) await assertGoal(companyId, input.goalId); if (input.parentIssueId) await assertParentIssue(companyId, input.parentIssueId); + const variables = syncRoutineVariablesWithTemplate( + [input.title, input.description], + sanitizeRoutineVariableInputs(input.variables), + ); + assertRoutineVariableDefinitions(variables); + const status = normalizeDraftRoutineStatus(input.status, input.assigneeAgentId); const [created] = await db .insert(routines) .values({ companyId, - projectId: input.projectId, + projectId: input.projectId ?? null, goalId: input.goalId ?? null, parentIssueId: input.parentIssueId ?? null, title: input.title, description: input.description ?? null, - assigneeAgentId: input.assigneeAgentId, + assigneeAgentId: input.assigneeAgentId ?? null, priority: input.priority, - status: input.status, + status, concurrencyPolicy: input.concurrencyPolicy, catchUpPolicy: input.catchUpPolicy, + variables, createdByAgentId: actor.agentId ?? null, createdByUserId: actor.userId ?? null, updatedByAgentId: actor.agentId ?? null, @@ -850,25 +1061,55 @@ export function routineService(db: Db, deps: { heartbeat?: IssueAssignmentWakeup update: async (id: string, patch: UpdateRoutine, actor: Actor): Promise => { const existing = await getRoutineById(id); if (!existing) return null; - const nextProjectId = patch.projectId ?? existing.projectId; - const nextAssigneeAgentId = patch.assigneeAgentId ?? existing.assigneeAgentId; - if (patch.projectId) await assertProject(existing.companyId, nextProjectId); - if (patch.assigneeAgentId) await assertAssignableAgent(existing.companyId, nextAssigneeAgentId); + const nextProjectId = patch.projectId === undefined ? existing.projectId : patch.projectId; + const nextAssigneeAgentId = patch.assigneeAgentId === undefined ? existing.assigneeAgentId : patch.assigneeAgentId; + const nextTitle = patch.title ?? existing.title; + const nextDescription = patch.description === undefined ? existing.description : patch.description; + const requestedStatus = patch.status ?? existing.status; + if (patch.status === "active") { + assertRoutineCanEnable(patch.status, nextAssigneeAgentId); + } + const nextStatus = patch.assigneeAgentId === undefined + ? requestedStatus + : normalizeDraftRoutineStatus(requestedStatus, nextAssigneeAgentId); + const nextVariables = syncRoutineVariablesWithTemplate( + [nextTitle, nextDescription], + patch.variables === undefined ? existing.variables : sanitizeRoutineVariableInputs(patch.variables), + ); + if (patch.projectId !== undefined) await assertProject(existing.companyId, nextProjectId); + if (patch.assigneeAgentId !== undefined) await assertAssignableAgent(existing.companyId, nextAssigneeAgentId); if (patch.goalId) await assertGoal(existing.companyId, patch.goalId); if (patch.parentIssueId) await assertParentIssue(existing.companyId, patch.parentIssueId); + assertRoutineVariableDefinitions(nextVariables); + const enabledScheduleTriggers = await db + .select({ id: routineTriggers.id }) + .from(routineTriggers) + .where( + and( + eq(routineTriggers.routineId, existing.id), + eq(routineTriggers.kind, "schedule"), + eq(routineTriggers.enabled, true), + ), + ) + .limit(1) + .then((rows) => rows.length > 0); + if (enabledScheduleTriggers) { + assertScheduleCompatibleVariables(nextVariables); + } const [updated] = await db .update(routines) .set({ projectId: nextProjectId, goalId: patch.goalId === undefined ? existing.goalId : patch.goalId, parentIssueId: patch.parentIssueId === undefined ? existing.parentIssueId : patch.parentIssueId, - title: patch.title ?? existing.title, - description: patch.description === undefined ? existing.description : patch.description, + title: nextTitle, + description: nextDescription, assigneeAgentId: nextAssigneeAgentId, priority: patch.priority ?? existing.priority, - status: patch.status ?? existing.status, + status: nextStatus, concurrencyPolicy: patch.concurrencyPolicy ?? existing.concurrencyPolicy, catchUpPolicy: patch.catchUpPolicy ?? existing.catchUpPolicy, + variables: nextVariables, updatedByAgentId: actor.agentId ?? null, updatedByUserId: actor.userId ?? null, updatedAt: new Date(), @@ -892,6 +1133,7 @@ export function routineService(db: Db, deps: { heartbeat?: IssueAssignmentWakeup let nextRunAt: Date | null = null; if (input.kind === "schedule") { + assertScheduleCompatibleVariables(routine.variables ?? []); const timeZone = input.timezone || "UTC"; assertTimeZone(timeZone); const error = validateCron(input.cronExpression); @@ -947,6 +1189,8 @@ export function routineService(db: Db, deps: { heartbeat?: IssueAssignmentWakeup let timezone = existing.timezone; if (existing.kind === "schedule") { + const routine = await getRoutineById(existing.routineId); + if (!routine) throw notFound("Routine not found"); if (patch.cronExpression !== undefined) { if (patch.cronExpression == null) throw unprocessable("Scheduled triggers require cronExpression"); const error = validateCron(patch.cronExpression); @@ -961,6 +1205,9 @@ export function routineService(db: Db, deps: { heartbeat?: IssueAssignmentWakeup if (cronExpression && timezone) { nextRunAt = nextCronTickInTimeZone(cronExpression, timezone, new Date()); } + if ((patch.enabled ?? existing.enabled) === true) { + assertScheduleCompatibleVariables(routine.variables ?? []); + } } const [updated] = await db @@ -1026,6 +1273,8 @@ export function routineService(db: Db, deps: { heartbeat?: IssueAssignmentWakeup const routine = await getRoutineById(id); if (!routine) throw notFound("Routine not found"); if (routine.status === "archived") throw conflict("Routine is archived"); + await assertProject(routine.companyId, input.projectId ?? null); + await assertAssignableAgent(routine.companyId, input.assigneeAgentId ?? null); const trigger = input.triggerId ? await getTriggerById(input.triggerId) : null; if (trigger && trigger.routineId !== routine.id) throw forbidden("Trigger does not belong to routine"); if (trigger && !trigger.enabled) throw conflict("Routine trigger is not active"); @@ -1034,13 +1283,21 @@ export function routineService(db: Db, deps: { heartbeat?: IssueAssignmentWakeup trigger, source: input.source, payload: input.payload as Record | null | undefined, + variables: input.variables as Record | null | undefined, + projectId: input.projectId ?? null, + assigneeAgentId: input.assigneeAgentId ?? null, idempotencyKey: input.idempotencyKey, + executionWorkspaceId: input.executionWorkspaceId ?? null, + executionWorkspacePreference: input.executionWorkspacePreference ?? null, + executionWorkspaceSettings: + (input.executionWorkspaceSettings as Record | null | undefined) ?? null, }); }, firePublicTrigger: async (publicId: string, input: { authorizationHeader?: string | null; signatureHeader?: string | null; + hubSignatureHeader?: string | null; timestampHeader?: string | null; idempotencyKey?: string | null; rawBody?: Buffer | null; @@ -1056,8 +1313,29 @@ export function routineService(db: Db, deps: { heartbeat?: IssueAssignmentWakeup if (!routine) throw notFound("Routine not found"); if (!trigger.enabled || routine.status !== "active") throw conflict("Routine trigger is not active"); - const secretValue = await resolveTriggerSecret(trigger, routine.companyId); - if (trigger.signingMode === "bearer") { + if (trigger.signingMode === "none") { + // No authentication — the publicId in the URL acts as a shared secret. + } else if (trigger.signingMode === "github_hmac") { + const secretValue = await resolveTriggerSecret(trigger, routine.companyId); + const rawBody = input.rawBody ?? Buffer.from(JSON.stringify(input.payload ?? {})); + // Accept X-Hub-Signature-256 (GitHub/Sentry) or fall back to the + // generic X-Paperclip-Signature header so operators can use github_hmac + // mode with either header convention. + const providedSignature = (input.hubSignatureHeader ?? input.signatureHeader)?.trim() ?? ""; + if (!providedSignature) throw unauthorized(); + const expectedHmac = crypto + .createHmac("sha256", secretValue) + .update(rawBody) + .digest("hex"); + const normalizedSignature = providedSignature.replace(/^sha256=/, ""); + const normalizedBuf = Buffer.from(normalizedSignature); + const expectedBuf = Buffer.from(expectedHmac); + const valid = + normalizedBuf.length === expectedBuf.length && + crypto.timingSafeEqual(normalizedBuf, expectedBuf); + if (!valid) throw unauthorized(); + } else if (trigger.signingMode === "bearer") { + const secretValue = await resolveTriggerSecret(trigger, routine.companyId); const expected = `Bearer ${secretValue}`; const provided = input.authorizationHeader?.trim() ?? ""; const expectedBuf = Buffer.from(expected); @@ -1070,6 +1348,7 @@ export function routineService(db: Db, deps: { heartbeat?: IssueAssignmentWakeup throw unauthorized(); } } else { + const secretValue = await resolveTriggerSecret(trigger, routine.companyId); const rawBody = input.rawBody ?? Buffer.from(JSON.stringify(input.payload ?? {})); const providedSignature = input.signatureHeader?.trim() ?? ""; const providedTimestamp = input.timestampHeader?.trim() ?? ""; @@ -1097,6 +1376,9 @@ export function routineService(db: Db, deps: { heartbeat?: IssueAssignmentWakeup trigger, source: "webhook", payload: input.payload, + variables: isPlainRecord(input.payload) && isPlainRecord(input.payload.variables) + ? input.payload.variables + : null, idempotencyKey: input.idempotencyKey, }); }, diff --git a/server/src/services/secrets.ts b/server/src/services/secrets.ts index 6317f0673d2..d288d4b3b46 100644 --- a/server/src/services/secrets.ts +++ b/server/src/services/secrets.ts @@ -39,6 +39,11 @@ function canonicalizeBinding(binding: EnvBinding): CanonicalEnvBinding { } export function secretService(db: Db) { + type NormalizeEnvOptions = { + strictMode?: boolean; + fieldPath?: string; + }; + async function getById(id: string) { return db .select() @@ -94,10 +99,10 @@ export function secretService(db: Db) { async function normalizeEnvConfig( companyId: string, envValue: unknown, - opts?: { strictMode?: boolean }, + opts?: NormalizeEnvOptions, ): Promise { const record = asRecord(envValue); - if (!record) throw unprocessable("adapterConfig.env must be an object"); + if (!record) throw unprocessable(`${opts?.fieldPath ?? "env"} must be an object`); const normalized: AgentEnvConfig = {}; for (const [key, rawBinding] of Object.entries(record)) { @@ -292,6 +297,12 @@ export function secretService(db: Db) { opts?: { strictMode?: boolean }, ) => normalizeAdapterConfigForPersistenceInternal(companyId, adapterConfig, opts), + normalizeEnvBindingsForPersistence: async ( + companyId: string, + envValue: unknown, + opts?: NormalizeEnvOptions, + ) => normalizeEnvConfig(companyId, envValue, opts), + normalizeHireApprovalPayloadForPersistence: async ( companyId: string, payload: Record, diff --git a/server/src/services/sidebar-badges.ts b/server/src/services/sidebar-badges.ts index cd39bf57ee0..5849f2ac6ff 100644 --- a/server/src/services/sidebar-badges.ts +++ b/server/src/services/sidebar-badges.ts @@ -1,4 +1,4 @@ -import { and, desc, eq, inArray, not, sql } from "drizzle-orm"; +import { and, desc, eq, inArray, not } from "drizzle-orm"; import type { Db } from "@paperclipai/db"; import { agents, approvals, heartbeatRuns } from "@paperclipai/db"; import type { SidebarBadges } from "@paperclipai/shared"; @@ -6,14 +6,34 @@ import type { SidebarBadges } from "@paperclipai/shared"; const ACTIONABLE_APPROVAL_STATUSES = ["pending", "revision_requested"]; const FAILED_HEARTBEAT_STATUSES = ["failed", "timed_out"]; +function normalizeTimestamp(value: Date | string | null | undefined): number { + if (!value) return 0; + const timestamp = new Date(value).getTime(); + return Number.isFinite(timestamp) ? timestamp : 0; +} + +function isDismissed( + dismissedAtByKey: ReadonlyMap, + itemKey: string, + activityAt: Date | string | null | undefined, +) { + const dismissedAt = dismissedAtByKey.get(itemKey); + if (dismissedAt == null) return false; + return dismissedAt >= normalizeTimestamp(activityAt); +} + export function sidebarBadgeService(db: Db) { return { get: async ( companyId: string, - extra?: { joinRequests?: number; unreadTouchedIssues?: number }, + extra?: { + dismissals?: ReadonlyMap; + joinRequests?: Array<{ id: string; updatedAt: Date | string | null; createdAt: Date | string }>; + unreadTouchedIssues?: number; + }, ): Promise => { const actionableApprovals = await db - .select({ count: sql`count(*)` }) + .select({ id: approvals.id, updatedAt: approvals.updatedAt }) .from(approvals) .where( and( @@ -21,11 +41,15 @@ export function sidebarBadgeService(db: Db) { inArray(approvals.status, ACTIONABLE_APPROVAL_STATUSES), ), ) - .then((rows) => Number(rows[0]?.count ?? 0)); + .then((rows) => + rows.filter((row) => !isDismissed(extra?.dismissals ?? new Map(), `approval:${row.id}`, row.updatedAt)).length + ); const latestRunByAgent = await db .selectDistinctOn([heartbeatRuns.agentId], { + id: heartbeatRuns.id, runStatus: heartbeatRuns.status, + createdAt: heartbeatRuns.createdAt, }) .from(heartbeatRuns) .innerJoin(agents, eq(heartbeatRuns.agentId, agents.id)) @@ -39,10 +63,17 @@ export function sidebarBadgeService(db: Db) { .orderBy(heartbeatRuns.agentId, desc(heartbeatRuns.createdAt)); const failedRuns = latestRunByAgent.filter((row) => - FAILED_HEARTBEAT_STATUSES.includes(row.runStatus), + FAILED_HEARTBEAT_STATUSES.includes(row.runStatus) + && !isDismissed(extra?.dismissals ?? new Map(), `run:${row.id}`, row.createdAt), ).length; - const joinRequests = extra?.joinRequests ?? 0; + const joinRequests = (extra?.joinRequests ?? []).filter((row) => + !isDismissed( + extra?.dismissals ?? new Map(), + `join:${row.id}`, + row.updatedAt ?? row.createdAt, + ) + ).length; const unreadTouchedIssues = extra?.unreadTouchedIssues ?? 0; return { inbox: actionableApprovals + failedRuns + joinRequests + unreadTouchedIssues, diff --git a/server/src/services/workspace-runtime-read-model.ts b/server/src/services/workspace-runtime-read-model.ts new file mode 100644 index 00000000000..dba6190d485 --- /dev/null +++ b/server/src/services/workspace-runtime-read-model.ts @@ -0,0 +1,96 @@ +import type { Db } from "@paperclipai/db"; +import { workspaceRuntimeServices } from "@paperclipai/db"; +import { and, desc, eq, inArray } from "drizzle-orm"; + +type WorkspaceRuntimeServiceRow = typeof workspaceRuntimeServices.$inferSelect; + +function runtimeServiceIdentityKey(row: WorkspaceRuntimeServiceRow) { + if (row.reuseKey) return row.reuseKey; + return [ + row.scopeType, + row.scopeId ?? "", + row.projectWorkspaceId ?? "", + row.executionWorkspaceId ?? "", + row.serviceName, + row.command ?? "", + row.cwd ?? "", + ].join(":"); +} + +export function selectCurrentRuntimeServiceRows(rows: WorkspaceRuntimeServiceRow[]) { + const current = new Map(); + for (const row of rows) { + const identity = runtimeServiceIdentityKey(row); + if (!current.has(identity)) current.set(identity, row); + } + return [...current.values()]; +} + +export async function listCurrentRuntimeServicesForProjectWorkspaces( + db: Db, + companyId: string, + projectWorkspaceIds: string[], +) { + if (projectWorkspaceIds.length === 0) return new Map(); + + const rows = await db + .select() + .from(workspaceRuntimeServices) + .where( + and( + eq(workspaceRuntimeServices.companyId, companyId), + inArray(workspaceRuntimeServices.projectWorkspaceId, projectWorkspaceIds), + eq(workspaceRuntimeServices.scopeType, "project_workspace"), + ), + ) + .orderBy(desc(workspaceRuntimeServices.updatedAt), desc(workspaceRuntimeServices.createdAt)); + + const grouped = new Map(); + for (const row of rows) { + if (!row.projectWorkspaceId) continue; + const existing = grouped.get(row.projectWorkspaceId) ?? []; + existing.push(row); + grouped.set(row.projectWorkspaceId, existing); + } + + return new Map( + Array.from(grouped.entries()).map(([workspaceId, workspaceRows]) => [ + workspaceId, + selectCurrentRuntimeServiceRows(workspaceRows), + ]), + ); +} + +export async function listCurrentRuntimeServicesForExecutionWorkspaces( + db: Db, + companyId: string, + executionWorkspaceIds: string[], +) { + if (executionWorkspaceIds.length === 0) return new Map(); + + const rows = await db + .select() + .from(workspaceRuntimeServices) + .where( + and( + eq(workspaceRuntimeServices.companyId, companyId), + inArray(workspaceRuntimeServices.executionWorkspaceId, executionWorkspaceIds), + ), + ) + .orderBy(desc(workspaceRuntimeServices.updatedAt), desc(workspaceRuntimeServices.createdAt)); + + const grouped = new Map(); + for (const row of rows) { + if (!row.executionWorkspaceId) continue; + const existing = grouped.get(row.executionWorkspaceId) ?? []; + existing.push(row); + grouped.set(row.executionWorkspaceId, existing); + } + + return new Map( + Array.from(grouped.entries()).map(([workspaceId, workspaceRows]) => [ + workspaceId, + selectCurrentRuntimeServiceRows(workspaceRows), + ]), + ); +} diff --git a/server/src/services/workspace-runtime.ts b/server/src/services/workspace-runtime.ts index bafadba5f62..b1971ce3cc3 100644 --- a/server/src/services/workspace-runtime.ts +++ b/server/src/services/workspace-runtime.ts @@ -1,4 +1,5 @@ import { spawn, type ChildProcess } from "node:child_process"; +import { existsSync, lstatSync, readdirSync, readFileSync, realpathSync } from "node:fs"; import fs from "node:fs/promises"; import net from "node:net"; import { createHash, randomUUID } from "node:crypto"; @@ -97,6 +98,27 @@ interface RuntimeServiceRecord extends RuntimeServiceRef { const runtimeServicesById = new Map(); const runtimeServicesByReuseKey = new Map(); const runtimeServiceLeasesByRun = new Map(); +const DEFAULT_EXECUTE_PROCESS_OUTPUT_BYTES = 256 * 1024; + +type ProcessOutputCapture = { + text: string; + truncated: boolean; + totalBytes: number; +}; + +type ProcessOutputAccumulator = { + append(chunk: string): void; + finish(): ProcessOutputCapture; +}; + +export async function resetRuntimeServicesForTests() { + for (const record of runtimeServicesById.values()) { + clearIdleTimer(record); + } + runtimeServicesById.clear(); + runtimeServicesByReuseKey.clear(); + runtimeServiceLeasesByRun.clear(); +} export async function resetRuntimeServicesForTests() { for (const record of runtimeServicesById.values()) { @@ -118,6 +140,139 @@ function stableStringify(value: unknown): string { return JSON.stringify(value); } +type WorkspaceLinkMismatch = { + packageName: string; + expectedPath: string; + actualPath: string | null; +}; + +function readJsonFile(filePath: string): Record { + return JSON.parse(readFileSync(filePath, "utf8")) as Record; +} + +function findWorkspaceRoot(startCwd: string) { + let current = path.resolve(startCwd); + while (true) { + if (existsSync(path.join(current, "pnpm-workspace.yaml"))) { + return current; + } + const parent = path.dirname(current); + if (parent === current) return null; + current = parent; + } +} + +function isLinkedGitWorktreeCheckout(rootDir: string) { + const gitMetadataPath = path.join(rootDir, ".git"); + if (!existsSync(gitMetadataPath)) return false; + + const stat = lstatSync(gitMetadataPath); + if (!stat.isFile()) return false; + + return readFileSync(gitMetadataPath, "utf8").trimStart().startsWith("gitdir:"); +} + +function discoverWorkspacePackagePaths(rootDir: string): Map { + const packagePaths = new Map(); + const ignoredDirNames = new Set([".git", ".paperclip", "dist", "node_modules"]); + + function visit(dirPath: string) { + if (!existsSync(dirPath)) return; + + const packageJsonPath = path.join(dirPath, "package.json"); + if (existsSync(packageJsonPath)) { + const packageJson = readJsonFile(packageJsonPath); + if (typeof packageJson.name === "string" && packageJson.name.length > 0) { + packagePaths.set(packageJson.name, dirPath); + } + } + + for (const entry of readdirSync(dirPath, { withFileTypes: true })) { + if (!entry.isDirectory()) continue; + if (ignoredDirNames.has(entry.name)) continue; + visit(path.join(dirPath, entry.name)); + } + } + + visit(path.join(rootDir, "packages")); + visit(path.join(rootDir, "server")); + visit(path.join(rootDir, "ui")); + visit(path.join(rootDir, "cli")); + + return packagePaths; +} + +function findServerWorkspaceLinkMismatches(rootDir: string): WorkspaceLinkMismatch[] { + const serverPackageJsonPath = path.join(rootDir, "server", "package.json"); + if (!existsSync(serverPackageJsonPath)) return []; + + const serverPackageJson = readJsonFile(serverPackageJsonPath); + const dependencies = { + ...(serverPackageJson.dependencies as Record | undefined), + ...(serverPackageJson.devDependencies as Record | undefined), + }; + const workspacePackagePaths = discoverWorkspacePackagePaths(rootDir); + const mismatches: WorkspaceLinkMismatch[] = []; + + for (const [packageName, version] of Object.entries(dependencies)) { + if (typeof version !== "string" || !version.startsWith("workspace:")) continue; + + const expectedPath = workspacePackagePaths.get(packageName); + if (!expectedPath) continue; + const normalizedExpectedPath = existsSync(expectedPath) ? path.resolve(realpathSync(expectedPath)) : path.resolve(expectedPath); + + const linkPath = path.join(rootDir, "server", "node_modules", ...packageName.split("/")); + const actualPath = existsSync(linkPath) ? path.resolve(realpathSync(linkPath)) : null; + if (actualPath === normalizedExpectedPath) continue; + + mismatches.push({ + packageName, + expectedPath: normalizedExpectedPath, + actualPath, + }); + } + + return mismatches; +} + +export async function ensureServerWorkspaceLinksCurrent( + startCwd: string, + opts?: { + onLog?: (stream: "stdout" | "stderr", chunk: string) => Promise; + }, +) { + const workspaceRoot = findWorkspaceRoot(startCwd); + if (!workspaceRoot) return; + if (!isLinkedGitWorktreeCheckout(workspaceRoot)) return; + + const mismatches = findServerWorkspaceLinkMismatches(workspaceRoot); + if (mismatches.length === 0) return; + + if (opts?.onLog) { + await opts.onLog("stdout", "[runtime] detected stale workspace package links for server; relinking dependencies...\n"); + for (const mismatch of mismatches) { + await opts.onLog( + "stdout", + `[runtime] ${mismatch.packageName}: ${mismatch.actualPath ?? "missing"} -> ${mismatch.expectedPath}\n`, + ); + } + } + + for (const mismatch of mismatches) { + const linkPath = path.join(workspaceRoot, "server", "node_modules", ...mismatch.packageName.split("/")); + await fs.mkdir(path.dirname(linkPath), { recursive: true }); + await fs.rm(linkPath, { recursive: true, force: true }); + await fs.symlink(mismatch.expectedPath, linkPath); + } + + const remainingMismatches = findServerWorkspaceLinkMismatches(workspaceRoot); + if (remainingMismatches.length === 0) return; + + throw new Error( + `Workspace relink did not repair all server package links: ${remainingMismatches.map((item) => item.packageName).join(", ")}`, + ); +} + export function sanitizeRuntimeServiceBaseEnv(baseEnv: NodeJS.ProcessEnv): NodeJS.ProcessEnv { const env: NodeJS.ProcessEnv = { ...baseEnv }; for (const key of Object.keys(env)) { @@ -254,30 +409,96 @@ function formatCommandForDisplay(command: string, args: string[]) { .join(" "); } +function createProcessOutputCapture(maxBytes: number): ProcessOutputAccumulator { + const limit = Math.max(1, Math.trunc(maxBytes)); + let chunks: string[] = []; + let truncated = false; + let totalBytes = 0; + + return { + append(chunk: string) { + if (!chunk) return; + chunks.push(chunk); + totalBytes += Buffer.byteLength(chunk, "utf8"); + + let currentBytes = chunks.reduce((sum, value) => sum + Buffer.byteLength(value, "utf8"), 0); + if (currentBytes <= limit) return; + + const combined = Buffer.from(chunks.join(""), "utf8"); + const tail = combined.subarray(Math.max(0, combined.length - limit)).toString("utf8"); + chunks = [tail]; + truncated = true; + currentBytes = Buffer.byteLength(tail, "utf8"); + if (currentBytes > limit) { + chunks = [Buffer.from(tail, "utf8").subarray(Math.max(0, currentBytes - limit)).toString("utf8")]; + } + }, + finish(): ProcessOutputCapture { + const text = chunks.join(""); + if (!truncated) { + return { + text, + truncated: false, + totalBytes, + }; + } + return { + text: `[output truncated to last ${limit} bytes; total ${totalBytes} bytes]\n${text}`, + truncated: true, + totalBytes, + }; + }, + }; +} + async function executeProcess(input: { command: string; args: string[]; cwd: string; env?: NodeJS.ProcessEnv; -}): Promise<{ stdout: string; stderr: string; code: number | null }> { - const proc = await new Promise<{ stdout: string; stderr: string; code: number | null }>((resolve, reject) => { + maxStdoutBytes?: number; + maxStderrBytes?: number; +}): Promise<{ + stdout: string; + stderr: string; + code: number | null; + stdoutTruncated: boolean; + stderrTruncated: boolean; + stdoutBytes: number; + stderrBytes: number; +}> { + const proc = await new Promise<{ + stdout: ProcessOutputAccumulator; + stderr: ProcessOutputAccumulator; + code: number | null; + }>((resolve, reject) => { const child = spawn(input.command, input.args, { cwd: input.cwd, stdio: ["ignore", "pipe", "pipe"], env: input.env ?? process.env, }); - let stdout = ""; - let stderr = ""; + const stdout = createProcessOutputCapture(input.maxStdoutBytes ?? DEFAULT_EXECUTE_PROCESS_OUTPUT_BYTES); + const stderr = createProcessOutputCapture(input.maxStderrBytes ?? DEFAULT_EXECUTE_PROCESS_OUTPUT_BYTES); child.stdout?.on("data", (chunk) => { - stdout += String(chunk); + stdout.append(String(chunk)); }); child.stderr?.on("data", (chunk) => { - stderr += String(chunk); + stderr.append(String(chunk)); }); child.on("error", reject); child.on("close", (code) => resolve({ stdout, stderr, code })); }); - return proc; + const stdout = proc.stdout.finish(); + const stderr = proc.stderr.finish(); + return { + stdout: stdout.text, + stderr: stderr.text, + code: proc.code, + stdoutTruncated: stdout.truncated, + stderrTruncated: stderr.truncated, + stdoutBytes: stdout.totalBytes, + stderrBytes: stderr.totalBytes, + }; } async function runGit(args: string[], cwd: string): Promise { @@ -297,6 +518,32 @@ function gitErrorIncludes(error: unknown, needle: string) { return message.toLowerCase().includes(needle.toLowerCase()); } +async function detectDefaultBranch(repoRoot: string): Promise { + // Try the explicit remote HEAD first (set by git clone or git remote set-head) + try { + const remoteHead = await runGit( + ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"], + repoRoot, + ); + const branch = remoteHead?.startsWith("origin/") ? remoteHead.slice("origin/".length) : remoteHead; + if (branch) return branch; + } catch { + // Not set — fall through to heuristic + } + + // Fallback: check for common default branch names on the remote + for (const candidate of ["main", "master"]) { + try { + await runGit(["rev-parse", "--verify", `refs/remotes/origin/${candidate}`], repoRoot); + return candidate; + } catch { + // Not found — try next + } + } + + return null; +} + async function directoryExists(value: string) { return fs.stat(value).then((stats) => stats.isDirectory()).catch(() => false); } @@ -347,16 +594,43 @@ function buildWorkspaceCommandEnv(input: { return env; } +function quoteShellArg(value: string) { + return `'${value.replace(/'/g, `'\\''`)}'`; +} + +function resolveRepoManagedWorkspaceCommand(command: string, repoRoot: string) { + const patterns = [ + /^(?(?:bash|sh|zsh)\s+)(?["']?)(?\.\/[^"'\s]+)\k(?(?:\s.*)?)$/s, + /^(?["']?)(?\.\/[^"'\s]+)\k(?(?:\s.*)?)$/s, + ]; + + for (const pattern of patterns) { + const match = command.match(pattern); + if (!match?.groups) continue; + + const relativePath = match.groups.relative; + const repoManagedPath = path.join(repoRoot, relativePath.slice(2)); + if (!existsSync(repoManagedPath)) continue; + + const prefix = match.groups.prefix ?? ""; + const suffix = match.groups.suffix ?? ""; + return `${prefix}${quoteShellArg(repoManagedPath)}${suffix}`; + } + + return command; +} + async function runWorkspaceCommand(input: { command: string; + resolvedCommand?: string; cwd: string; env: NodeJS.ProcessEnv; label: string; }) { - const shell = process.env.SHELL?.trim() || "/bin/sh"; + const shell = resolveShell(); const proc = await executeProcess({ command: shell, - args: ["-c", input.command], + args: ["-c", input.resolvedCommand ?? input.command], cwd: input.cwd, env: input.env, }); @@ -408,6 +682,15 @@ async function recordGitOperation( stdout: result.stdout, stderr: result.stderr, system: result.code === 0 ? input.successMessage ?? null : null, + metadata: + result.stdoutTruncated || result.stderrTruncated + ? { + stdoutTruncated: result.stdoutTruncated, + stderrTruncated: result.stderrTruncated, + stdoutBytes: result.stdoutBytes, + stderrBytes: result.stderrBytes, + } + : null, }; }, }); @@ -428,6 +711,7 @@ async function recordWorkspaceCommandOperation( input: { phase: "workspace_provision" | "workspace_teardown"; command: string; + resolvedCommand?: string; cwd: string; env: NodeJS.ProcessEnv; label: string; @@ -449,10 +733,10 @@ async function recordWorkspaceCommandOperation( cwd: input.cwd, metadata: input.metadata ?? null, run: async () => { - const shell = process.env.SHELL?.trim() || "/bin/sh"; + const shell = resolveShell(); const result = await executeProcess({ command: shell, - args: ["-c", input.command], + args: ["-c", input.resolvedCommand ?? input.command], cwd: input.cwd, env: input.env, }); @@ -465,6 +749,15 @@ async function recordWorkspaceCommandOperation( stdout: result.stdout, stderr: result.stderr, system: result.code === 0 ? input.successMessage ?? null : null, + metadata: + result.stdoutTruncated || result.stderrTruncated + ? { + stdoutTruncated: result.stdoutTruncated, + stderrTruncated: result.stderrTruncated, + stdoutBytes: result.stdoutBytes, + stderrBytes: result.stderrBytes, + } + : null, }; }, }); @@ -492,10 +785,12 @@ async function provisionExecutionWorktree(input: { }) { const provisionCommand = asString(input.strategy.provisionCommand, "").trim(); if (!provisionCommand) return; + const resolvedProvisionCommand = resolveRepoManagedWorkspaceCommand(provisionCommand, input.repoRoot); await recordWorkspaceCommandOperation(input.recorder, { phase: "workspace_provision", command: provisionCommand, + resolvedCommand: resolvedProvisionCommand, cwd: input.worktreePath, env: buildWorkspaceCommandEnv({ base: input.base, @@ -512,6 +807,7 @@ async function provisionExecutionWorktree(input: { worktreePath: input.worktreePath, branchName: input.branchName, created: input.created, + resolvedCommand: resolvedProvisionCommand === provisionCommand ? null : resolvedProvisionCommand, }, successMessage: `Provisioned workspace at ${input.worktreePath}\n`, }); @@ -601,7 +897,12 @@ export async function realizeExecutionWorkspace(input: { ? resolveConfiguredPath(configuredParentDir, repoRoot) : path.join(repoRoot, ".paperclip", "worktrees"); const worktreePath = path.join(worktreeParentDir, branchName); - const baseRef = asString(rawStrategy.baseRef, input.base.repoRef ?? "HEAD"); + const configuredBaseRef = typeof rawStrategy.baseRef === "string" && rawStrategy.baseRef.length > 0 + ? rawStrategy.baseRef + : input.base.repoRef ?? null; + const baseRef = configuredBaseRef + ?? await detectDefaultBranch(repoRoot) + ?? "HEAD"; await fs.mkdir(worktreeParentDir, { recursive: true }); @@ -734,6 +1035,12 @@ export async function cleanupExecutionWorkspaceArtifacts(input: { }) { const warnings: string[] = []; const workspacePath = input.workspace.providerRef ?? input.workspace.cwd; + const repoRoot = input.workspace.providerType === "git_worktree" && workspacePath + ? await resolveGitRepoRootForWorkspaceCleanup( + workspacePath, + input.projectWorkspace?.cwd ?? null, + ) + : null; const cleanupEnv = buildExecutionWorkspaceCleanupEnv({ workspace: input.workspace, projectWorkspaceCwd: input.projectWorkspace?.cwd ?? null, @@ -749,9 +1056,13 @@ export async function cleanupExecutionWorkspaceArtifacts(input: { for (const command of cleanupCommands) { try { + const resolvedCommand = repoRoot + ? resolveRepoManagedWorkspaceCommand(command, repoRoot) + : command; await recordWorkspaceCommandOperation(input.recorder, { phase: "workspace_teardown", command, + resolvedCommand, cwd: workspacePath ?? input.projectWorkspace?.cwd ?? process.cwd(), env: cleanupEnv, label: `Execution workspace cleanup command "${command}"`, @@ -760,6 +1071,7 @@ export async function cleanupExecutionWorkspaceArtifacts(input: { workspacePath, branchName: input.workspace.branchName, providerType: input.workspace.providerType, + resolvedCommand: resolvedCommand === command ? null : resolvedCommand, }, successMessage: `Completed cleanup command "${command}"\n`, }); @@ -769,10 +1081,6 @@ export async function cleanupExecutionWorkspaceArtifacts(input: { } if (input.workspace.providerType === "git_worktree" && workspacePath) { - const repoRoot = await resolveGitRepoRootForWorkspaceCleanup( - workspacePath, - input.projectWorkspace?.cwd ?? null, - ); const worktreeExists = await directoryExists(workspacePath); if (worktreeExists) { if (!repoRoot) { @@ -1046,6 +1354,16 @@ async function waitForReadiness(input: { throw new Error(`Readiness check failed for ${input.url}: ${lastError}`); } +async function isRuntimeServiceUrlHealthy(url: string | null) { + if (!url) return true; + try { + const response = await fetch(url, { signal: AbortSignal.timeout(2_000) }); + return response.ok; + } catch { + return false; + } +} + function toPersistedWorkspaceRuntimeService(record: RuntimeServiceRecord): typeof workspaceRuntimeServices.$inferInsert { return { id: record.id, @@ -1192,6 +1510,8 @@ export function normalizeAdapterManagedRuntimeServices(input: { async function startLocalRuntimeService(input: { db?: Db; runId: string; + leaseRunId?: string | null; + startedByRunId?: string | null; agent: ExecutionWorkspaceAgentRef; issue: ExecutionWorkspaceIssueRef | null; workspace: RealizedExecutionWorkspace; @@ -1382,7 +1702,7 @@ async function startLocalRuntimeService(input: { reused: false, db: input.db, child, - leaseRunIds: new Set([input.runId]), + leaseRunIds: leaseRunId ? new Set([leaseRunId]) : new Set(), idleTimer: null, envFingerprint, serviceKey, diff --git a/server/src/telemetry.ts b/server/src/telemetry.ts new file mode 100644 index 00000000000..eb2b28749e6 --- /dev/null +++ b/server/src/telemetry.ts @@ -0,0 +1,30 @@ +import path from "node:path"; +import { + TelemetryClient, + resolveTelemetryConfig, + loadOrCreateState, +} from "@paperclipai/shared/telemetry"; +import { resolvePaperclipInstanceRoot } from "./home-paths.js"; +import { serverVersion } from "./version.js"; + +let client: TelemetryClient | null = null; + +export function initTelemetry(fileConfig?: { enabled?: boolean }): TelemetryClient | null { + if (client) return client; + + const config = resolveTelemetryConfig(fileConfig); + if (!config.enabled) return null; + + const stateDir = path.join(resolvePaperclipInstanceRoot(), "telemetry"); + client = new TelemetryClient( + config, + () => loadOrCreateState(stateDir, serverVersion), + serverVersion, + ); + client.startPeriodicFlush(60_000); + return client; +} + +export function getTelemetryClient(): TelemetryClient | null { + return client; +} diff --git a/skills/paperclip/SKILL.md b/skills/paperclip/SKILL.md index 1d319ad3562..c8e3ad5f861 100644 --- a/skills/paperclip/SKILL.md +++ b/skills/paperclip/SKILL.md @@ -3,9 +3,10 @@ name: paperclip description: > Interact with the Paperclip control plane API to manage tasks, coordinate with other agents, and follow company governance. Use when you need to check - assignments, update task status, delegate work, post comments, or call any - Paperclip API endpoint. Do NOT use for the actual domain work itself (writing - code, research, etc.) — only for Paperclip coordination. + assignments, update task status, delegate work, post comments, set up or manage + routines (recurring scheduled tasks), or call any Paperclip API endpoint. Do NOT + use for the actual domain work itself (writing code, research, etc.) — only for + Paperclip coordination. --- # Paperclip Skill @@ -16,6 +17,8 @@ You run in **heartbeats** — short execution windows triggered by Paperclip. Ea Env vars auto-injected: `PAPERCLIP_AGENT_ID`, `PAPERCLIP_COMPANY_ID`, `PAPERCLIP_API_URL`, `PAPERCLIP_RUN_ID`. Optional wake-context vars may also be present: `PAPERCLIP_TASK_ID` (issue/task that triggered this wake), `PAPERCLIP_WAKE_REASON` (why this run was triggered), `PAPERCLIP_WAKE_COMMENT_ID` (specific comment that triggered this wake), `PAPERCLIP_APPROVAL_ID`, `PAPERCLIP_APPROVAL_STATUS`, and `PAPERCLIP_LINKED_ISSUE_IDS` (comma-separated). For local adapters, `PAPERCLIP_API_KEY` is auto-injected as a short-lived run JWT. For non-local adapters, your operator should set `PAPERCLIP_API_KEY` in adapter config. All requests use `Authorization: Bearer $PAPERCLIP_API_KEY`. All endpoints under `/api`, all JSON. Never hard-code the API URL. +Some adapters also inject `PAPERCLIP_WAKE_PAYLOAD_JSON` on comment-driven wakes. When present, it contains the compact issue summary and the ordered batch of new comment payloads for this wake. Use it first. For comment wakes, treat that batch as the highest-priority new context in the heartbeat: in your first task update or response, acknowledge the latest comment and say how it changes your next action before broad repo exploration or generic wake boilerplate. Only fetch the thread/comments API immediately when `fallbackFetchNeeded` is true or you need broader context than the inline batch provides. + Manual local CLI mode (outside heartbeat runs): use `paperclipai agent local-cli --company-id ` to install Paperclip skills for Claude/Codex and print/export the required `PAPERCLIP_*` environment variables for that agent identity. **Run audit trail:** You MUST include `-H 'X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID'` on ALL API requests that modify issues (checkout, update, comment, create subtask, release). This links your actions to the current heartbeat run for traceability. @@ -24,6 +27,8 @@ Manual local CLI mode (outside heartbeat runs): use `paperclipai agent local-cli Follow these steps every time you wake up: +**Scoped-wake fast path.** If the user message includes a **"Paperclip Resume Delta"** or **"Paperclip Wake Payload"** section that names a specific issue, **skip Steps 1–4 entirely**. Go straight to **Step 5 (Checkout)** for that issue, then continue with Steps 6–9. The scoped wake already tells you which issue to work on — do NOT call `/api/agents/me`, do NOT fetch your inbox, do NOT pick work. Just checkout, read the wake context, do the work, and update. + **Step 1 — Identity.** If not already in context, `GET /api/agents/me` to get your id, companyId, role, chainOfCommand, and budget. **Step 2 — Approval follow-up (when triggered).** If `PAPERCLIP_APPROVAL_ID` is set (or wake reason indicates approval resolution), review the approval first: @@ -35,12 +40,13 @@ Follow these steps every time you wake up: - add a markdown comment explaining why it remains open and what happens next. Always include links to the approval and issue in that comment. -**Step 3 — Get assignments.** Prefer `GET /api/agents/me/inbox-lite` for the normal heartbeat inbox. It returns the compact assignment list you need for prioritization. Fall back to `GET /api/companies/{companyId}/issues?assigneeAgentId={your-agent-id}&status=todo,in_progress,blocked` only when you need the full issue objects. +**Step 3 — Get assignments.** Prefer `GET /api/agents/me/inbox-lite` for the normal heartbeat inbox. It returns the compact assignment list you need for prioritization. Fall back to `GET /api/companies/{companyId}/issues?assigneeAgentId={your-agent-id}&status=todo,in_progress,in_review,blocked` only when you need the full issue objects. -**Step 4 — Pick work (with mention exception).** Work on `in_progress` first, then `todo`. Skip `blocked` unless you can unblock it. +**Step 4 — Pick work (with mention exception).** Work on `in_progress` first, then `in_review` (if you were woken by a comment on it — check `PAPERCLIP_WAKE_COMMENT_ID`), then `todo`. Skip `blocked` unless you can unblock it. **Blocked-task dedup:** Before working on a `blocked` task, fetch its comment thread. If your most recent comment was a blocked-status update AND no new comments from other agents or users have been posted since, skip the task entirely — do not checkout, do not post another comment. Exit the heartbeat (or move to the next task) instead. Only re-engage with a blocked task when new context exists (a new comment, status change, or event-based wake like `PAPERCLIP_WAKE_COMMENT_ID`). If `PAPERCLIP_TASK_ID` is set and that task is assigned to you, prioritize it first for this heartbeat. -If this run was triggered by a comment mention (`PAPERCLIP_WAKE_COMMENT_ID` set; typically `PAPERCLIP_WAKE_REASON=issue_comment_mentioned`), you MUST read that comment thread first, even if the task is not currently assigned to you. +If this run was triggered by a comment on a task you own (`PAPERCLIP_WAKE_COMMENT_ID` set; `PAPERCLIP_WAKE_REASON=issue_commented`), you MUST read that comment, then checkout and address the feedback. This includes `in_review` tasks — if someone comments with feedback, re-checkout the task to address it. +If this run was triggered by a comment mention (`PAPERCLIP_WAKE_COMMENT_ID` set; `PAPERCLIP_WAKE_REASON=issue_comment_mentioned`), you MUST read that comment thread first, even if the task is not currently assigned to you. If that mentioned comment explicitly asks you to take the task, you may self-assign by checking out `PAPERCLIP_TASK_ID` as yourself, then proceed normally. If the comment asks for input/review but not ownership, respond in comments if useful, then continue with assigned work. If the comment does not direct you to take ownership, do not self-assign. @@ -51,13 +57,15 @@ If nothing is assigned and there is no valid mention-based ownership handoff, ex ``` POST /api/issues/{issueId}/checkout Headers: Authorization: Bearer $PAPERCLIP_API_KEY, X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID -{ "agentId": "{your-agent-id}", "expectedStatuses": ["todo", "backlog", "blocked"] } +{ "agentId": "{your-agent-id}", "expectedStatuses": ["todo", "backlog", "blocked", "in_review"] } ``` If already checked out by you, returns normally. If owned by another agent: `409 Conflict` — stop, pick a different task. **Never retry a 409.** **Step 6 — Understand context.** Prefer `GET /api/issues/{issueId}/heartbeat-context` first. It gives you compact issue state, ancestor summaries, goal/project info, and comment cursor metadata without forcing a full thread replay. +If `PAPERCLIP_WAKE_PAYLOAD_JSON` is present, inspect that payload before calling the API. It is the fastest path for comment wakes and may already include the exact new comments that triggered this run. For comment-driven wakes, explicitly reflect the new comment context first, then fetch broader history only if needed. + Use comments incrementally: - if `PAPERCLIP_WAKE_COMMENT_ID` is set, fetch that exact comment first with `GET /api/issues/{issueId}/comments/{commentId}` @@ -66,6 +74,35 @@ Use comments incrementally: Read enough ancestor/comment context to understand _why_ the task exists and what changed. Do not reflexively reload the whole thread on every heartbeat. +**Execution-policy review/approval wakes.** If the issue is in `in_review` and includes `executionState`, inspect these fields immediately: + +- `executionState.currentStageType` tells you whether you are in a `review` or `approval` stage +- `executionState.currentParticipant` tells you who is currently allowed to act +- `executionState.returnAssignee` tells you who receives the task back if changes are requested +- `executionState.lastDecisionOutcome` tells you the latest review/approval outcome + +If `currentParticipant` matches you, you are the active reviewer/approver for this heartbeat. There is **no separate execution-decision endpoint**. Submit your decision through the normal issue update route: + +```json +PATCH /api/issues/{issueId} +Headers: X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID +{ "status": "done", "comment": "Approved: what you reviewed and why it passes." } +``` + +That approves the current stage. If more stages remain, Paperclip keeps the issue in `in_review`, reassigns it to the next participant, and records the decision automatically. + +To request changes, send a non-`done` status with a required comment. Prefer `in_progress`: + +```json +PATCH /api/issues/{issueId} +Headers: X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID +{ "status": "in_progress", "comment": "Changes requested: exactly what must be fixed." } +``` + +Paperclip converts that into a changes-requested decision, reassigns the issue to `returnAssignee`, and routes the task back through the same stage after the executor resubmits. + +If `currentParticipant` does **not** match you, do not try to advance the stage. Only the active reviewer/approver can do that, and Paperclip will reject other actors with `422`. + **Step 7 — Do the work.** Use your tools and capabilities. **Step 8 — Update status and communicate.** Always include the run ID header. @@ -83,7 +120,9 @@ Headers: X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID { "status": "blocked", "comment": "What is blocked, why, and who needs to unblock it." } ``` -Status values: `backlog`, `todo`, `in_progress`, `in_review`, `done`, `blocked`, `cancelled`. Priority values: `critical`, `high`, `medium`, `low`. Other updatable fields: `title`, `description`, `priority`, `assigneeAgentId`, `projectId`, `goalId`, `parentId`, `billingCode`. +Status values: `backlog`, `todo`, `in_progress`, `in_review`, `done`, `blocked`, `cancelled`. Priority values: `critical`, `high`, `medium`, `low`. Other updatable fields: `title`, `description`, `priority`, `assigneeAgentId`, `projectId`, `goalId`, `parentId`, `billingCode`, `blockedByIssueIds`. + +**Step 9 — Delegate if needed.** Create subtasks with `POST /api/companies/{companyId}/issues`. Always set `parentId` and `goalId`. When a follow-up issue needs to stay on the same code change but is not a true child task, set `inheritExecutionWorkspaceFromIssueId` to the source issue. Set `billingCode` for cross-team work. **Step 9 — Delegate if needed.** Create subtasks with `POST /api/companies/{companyId}/issues`. Always set `parentId` and `goalId`. When a follow-up issue needs to stay on the same code change but is not a true child task, set `inheritExecutionWorkspaceFromIssueId` to the source issue. Set `billingCode` for cross-team work. @@ -137,6 +176,17 @@ Authorized managers can install company skills independently of hiring, then ass If you are asked to install a skill for the company or an agent you MUST read: `skills/paperclip/references/company-skills.md` +## Routines + +Routines are recurring tasks. Each time a routine fires it creates an execution issue assigned to the routine's agent — the agent picks it up in the normal heartbeat flow. + +- Create and manage routines with the routines API — agents can only manage routines assigned to themselves. +- Add triggers per routine: `schedule` (cron), `webhook`, or `api` (manual). +- Control concurrency and catch-up behaviour with `concurrencyPolicy` and `catchUpPolicy`. + +If you are asked to create or manage routines you MUST read: +`skills/paperclip/references/routines.md` + ## Critical Rules - **Always checkout** before working. Never PATCH to `in_progress` manually. @@ -150,11 +200,12 @@ If you are asked to install a skill for the company or an agent you MUST read: - **Preserve workspace continuity for follow-ups.** Child issues inherit execution workspace linkage server-side from `parentId`. For non-child follow-ups tied to the same checkout/worktree, send `inheritExecutionWorkspaceFromIssueId` explicitly instead of relying on free-text references or memory. - **Never cancel cross-team tasks.** Reassign to your manager with a comment. - **Always update blocked issues explicitly.** If blocked, PATCH status to `blocked` with a blocker comment before exiting, then escalate. On subsequent heartbeats, do NOT repeat the same blocked comment — see blocked-task dedup in Step 4. +- **Use first-class blockers** when a task depends on other tasks. Set `blockedByIssueIds` on the dependent issue so Paperclip automatically wakes the assignee when all blockers are done. Prefer this over ad-hoc "blocked by X" comments. - **@-mentions** (`@AgentName` in comments) trigger heartbeats — use sparingly, they cost budget. - **Budget**: auto-paused at 100%. Above 80%, focus on critical tasks only. - **Escalate** via `chainOfCommand` when stuck. Reassign to manager or create a task for them. - **Hiring**: use `paperclip-create-agent` skill for new agent creation workflows. -- **Commit Co-author**: if you make a git commit you MUST add `Co-Authored-By: Paperclip ` to the end of each commit message +- **Commit Co-author**: if you make a git commit you MUST add EXACTLY `Co-Authored-By: Paperclip ` to the end of each commit message. Do not put in your agent name, put `Co-Authored-By: Paperclip ` ## Comment Style (Required) @@ -277,20 +328,32 @@ PATCH /api/agents/{agentId}/instructions-path | Set instructions path | `PATCH /api/agents/:agentId/instructions-path` | | Release task | `POST /api/issues/:issueId/release` | | List agents | `GET /api/companies/:companyId/agents` | +| Create approval | `POST /api/companies/:companyId/approvals` | | List company skills | `GET /api/companies/:companyId/skills` | | Import company skills | `POST /api/companies/:companyId/skills/import` | | Scan project workspaces for skills | `POST /api/companies/:companyId/skills/scan-projects` | | Sync agent desired skills | `POST /api/agents/:agentId/skills/sync` | -| Preview CEO-safe company import | `POST /api/companies/:companyId/imports/preview` | -| Apply CEO-safe company import | `POST /api/companies/:companyId/imports/apply` | -| Preview company export | `POST /api/companies/:companyId/exports/preview` | -| Build company export | `POST /api/companies/:companyId/exports` | +| Preview CEO-safe company import | `POST /api/companies/:companyId/imports/preview` | +| Apply CEO-safe company import | `POST /api/companies/:companyId/imports/apply` | +| Preview company export | `POST /api/companies/:companyId/exports/preview` | +| Build company export | `POST /api/companies/:companyId/exports` | | Dashboard | `GET /api/companies/:companyId/dashboard` | | Search issues | `GET /api/companies/:companyId/issues?q=search+term` | | Upload attachment (multipart, field=file) | `POST /api/companies/:companyId/issues/:issueId/attachments` | | List issue attachments | `GET /api/issues/:issueId/attachments` | | Get attachment content | `GET /api/attachments/:attachmentId/content` | | Delete attachment | `DELETE /api/attachments/:attachmentId` | +| List routines | `GET /api/companies/:companyId/routines` | +| Get routine | `GET /api/routines/:routineId` | +| Create routine | `POST /api/companies/:companyId/routines` | +| Update routine | `PATCH /api/routines/:routineId` | +| Add trigger | `POST /api/routines/:routineId/triggers` | +| Update trigger | `PATCH /api/routine-triggers/:triggerId` | +| Delete trigger | `DELETE /api/routine-triggers/:triggerId` | +| Rotate webhook secret | `POST /api/routine-triggers/:triggerId/rotate-secret` | +| Manual run | `POST /api/routines/:routineId/run` | +| Fire webhook (external) | `POST /api/routine-triggers/public/:publicId/fire` | +| List runs | `GET /api/routines/:routineId/runs` | ## Company Import / Export diff --git a/skills/paperclip/references/api-reference.md b/skills/paperclip/references/api-reference.md index aea4250ce02..1b34624d0fc 100644 --- a/skills/paperclip/references/api-reference.md +++ b/skills/paperclip/references/api-reference.md @@ -109,6 +109,8 @@ POST /api/companies/company-1/exports Includes the issue's `project` and `goal` (with descriptions), plus each ancestor's resolved `project` and `goal`. This gives agents full context about where the task sits in the project/goal hierarchy. +The response also includes `blockedBy` and `blocks` arrays showing first-class dependency relationships: + ```json { "id": "issue-99", @@ -116,6 +118,10 @@ Includes the issue's `project` and `goal` (with descriptions), plus each ancesto "parentId": "issue-50", "projectId": "proj-1", "goalId": null, + "blockedBy": [ + { "id": "issue-80", "identifier": "PAP-80", "title": "Design auth schema", "status": "in_progress", "priority": "high", "assigneeAgentId": "agent-55", "assigneeUserId": null } + ], + "blocks": [], "project": { "id": "proj-1", "name": "Auth System", @@ -183,6 +189,60 @@ Includes the issue's `project` and `goal` (with descriptions), plus each ancesto } ``` +Blocker wake semantics are strict: `issue_blockers_resolved` only fires when every blocker reaches `done`. A blocker moved to `cancelled` still requires manual re-triage or relation cleanup. + +### Execution Policy Fields On An Issue + +When an issue has review or approval gates, `GET /api/issues/:issueId` can also include `executionPolicy` and `executionState`: + +```json +{ + "status": "in_review", + "executionPolicy": { + "mode": "normal", + "commentRequired": true, + "stages": [ + { + "id": "stage-review", + "type": "review", + "approvalsNeeded": 1, + "participants": [ + { "id": "participant-qa", "type": "agent", "agentId": "qa-agent-id" } + ] + }, + { + "id": "stage-approval", + "type": "approval", + "approvalsNeeded": 1, + "participants": [ + { "id": "participant-cto", "type": "user", "userId": "cto-user-id" } + ] + } + ] + }, + "executionState": { + "status": "pending", + "currentStageId": "stage-review", + "currentStageIndex": 0, + "currentStageType": "review", + "currentParticipant": { "type": "agent", "agentId": "qa-agent-id" }, + "returnAssignee": { "type": "agent", "agentId": "coder-agent-id" }, + "completedStageIds": [], + "lastDecisionId": null, + "lastDecisionOutcome": null + } +} +``` + +Interpretation: + +- `currentStageType` tells you whether the active gate is `review` or `approval` +- `currentParticipant` is the only actor allowed to advance the stage +- `returnAssignee` is who gets the task back when changes are requested +- `lastDecisionOutcome` shows the latest gate decision + +There is **no separate execution-decision endpoint**. Review and approval decisions are submitted through `PATCH /api/issues/:issueId`, and Paperclip records the decision row automatically. + --- ## Worked Example: IC Heartbeat @@ -195,7 +255,7 @@ GET /api/agents/me -> { id: "agent-42", companyId: "company-1", ... } # 2. Check inbox -GET /api/companies/company-1/issues?assigneeAgentId=agent-42&status=todo,in_progress,blocked +GET /api/companies/company-1/issues?assigneeAgentId=agent-42&status=todo,in_progress,in_review,blocked -> [ { id: "issue-101", title: "Fix rate limiter bug", status: "in_progress", priority: "high" }, { id: "issue-99", title: "Implement login API", status: "todo", priority: "medium" } @@ -216,7 +276,7 @@ PATCH /api/issues/issue-101 # 6. Still have time. Checkout the next task. POST /api/issues/issue-99/checkout -{ "agentId": "agent-42", "expectedStatuses": ["todo"] } +{ "agentId": "agent-42", "expectedStatuses": ["todo", "backlog", "blocked", "in_review"] } GET /api/issues/issue-99 -> { ..., ancestors: [{ title: "Build auth system", ... }] } @@ -283,14 +343,15 @@ GET /api/companies/company-1/issues?assigneeAgentId=mgr-1&status=todo,in_progres -> [ { id: "issue-30", title: "Break down Q2 roadmap into tasks", status: "todo" } ] POST /api/issues/issue-30/checkout -{ "agentId": "mgr-1", "expectedStatuses": ["todo"] } +{ "agentId": "mgr-1", "expectedStatuses": ["todo", "backlog", "blocked", "in_review"] } # 6. Create subtasks and delegate. POST /api/companies/company-1/issues { "title": "Implement caching layer", "assigneeAgentId": "agent-42", "parentId": "issue-30", "status": "todo", "priority": "high", "goalId": "goal-1" } POST /api/companies/company-1/issues -{ "title": "Write load test suite", "assigneeAgentId": "agent-55", "parentId": "issue-30", "status": "todo", "priority": "medium", "goalId": "goal-1" } +{ "title": "Write load test suite", "assigneeAgentId": "agent-55", "parentId": "issue-30", "status": "blocked", "priority": "medium", "goalId": "goal-1", "blockedByIssueIds": [""] } +# ^ Load tests depend on caching layer being done first. Paperclip will auto-wake agent-55 when the blocker resolves. PATCH /api/issues/issue-30 { "status": "done", "comment": "Broke down into subtasks for caching layer and load testing." } @@ -597,7 +658,15 @@ Terminal states: `done`, `cancelled` | GET | `/api/agents/me/inbox/mine?userId=:userId` | Mine-tab issue list for a specific board user | | GET | `/api/agents/:agentId` | Agent details + chain of command | | GET | `/api/companies/:companyId/agents` | List all agents in company | +| POST | `/api/companies/:companyId/agents` | Create agent directly (no approval) | +| PATCH | `/api/agents/:agentId` | Update agent config or budget | +| POST | `/api/agents/:agentId/pause` | Temporarily stop heartbeats | +| POST | `/api/agents/:agentId/resume` | Resume a paused agent | +| POST | `/api/agents/:agentId/terminate` | Permanently deactivate agent (irreversible) | +| POST | `/api/agents/:agentId/keys` | Create long-lived API key (full value shown once) | +| POST | `/api/agents/:agentId/heartbeat/invoke` | Manually trigger a heartbeat | | GET | `/api/companies/:companyId/org` | Org chart tree | +| GET | `/api/companies/:companyId/adapters/:adapterType/models` | List selectable models for an adapter type | | PATCH | `/api/agents/:agentId/instructions-path` | Set/clear instructions path (`AGENTS.md`) | | GET | `/api/agents/:agentId/config-revisions` | List config revisions | | POST | `/api/agents/:agentId/config-revisions/:revisionId/rollback` | Roll back config | @@ -608,15 +677,21 @@ Terminal states: `done`, `cancelled` | ------ | ---------------------------------- | ---------------------------------------------------------------------------------------- | | GET | `/api/companies/:companyId/issues` | List issues, sorted by priority. Filters: `?status=`, `?assigneeAgentId=`, `?assigneeUserId=`, `?projectId=`, `?labelId=`, `?q=` (full-text search across title, identifier, description, comments) | | GET | `/api/issues/:issueId` | Issue details + ancestors | -| POST | `/api/companies/:companyId/issues` | Create issue | -| PATCH | `/api/issues/:issueId` | Update issue (optional `comment` field adds a comment in same call) | +| GET | `/api/issues/:issueId/heartbeat-context` | Compact context for heartbeat: issue state, ancestor summaries, comment cursor | +| POST | `/api/companies/:companyId/issues` | Create issue (supports `blockedByIssueIds: string[]` for dependencies) | +| PATCH | `/api/issues/:issueId` | Update issue (optional `comment` field; `blockedByIssueIds` replaces blocker set) | | POST | `/api/issues/:issueId/checkout` | Atomic checkout (claim + start). Idempotent if you already own it. | | POST | `/api/issues/:issueId/release` | Release task ownership | | GET | `/api/issues/:issueId/comments` | List comments | | GET | `/api/issues/:issueId/comments/:commentId` | Get a specific comment by ID | | POST | `/api/issues/:issueId/comments` | Add comment (@-mentions trigger wakeups) | +| GET | `/api/issues/:issueId/documents` | List issue documents | +| GET | `/api/issues/:issueId/documents/:key` | Get issue document by key | +| PUT | `/api/issues/:issueId/documents/:key` | Create or update issue document (send `baseRevisionId` when updating) | +| GET | `/api/issues/:issueId/documents/:key/revisions` | Document revision history | +| DELETE | `/api/issues/:issueId/documents/:key` | Delete document (board-only) | | GET | `/api/issues/:issueId/approvals` | List approvals linked to issue | -| POST | `/api/issues/:issueId/approvals` | Link approval to issue | +| POST | `/api/issues/:issueId/approvals` | Link approval to issue | | DELETE | `/api/issues/:issueId/approvals/:approvalId` | Unlink approval from issue | ### Companies, Projects, Goals @@ -624,7 +699,11 @@ Terminal states: `done`, `cancelled` | Method | Path | Description | | ------ | ------------------------------------ | ------------------ | | GET | `/api/companies` | List all companies | +| POST | `/api/companies` | Create company | | GET | `/api/companies/:companyId` | Company details | +| PATCH | `/api/companies/:companyId` | Update company fields | +| POST | `/api/companies/:companyId/logo` | Upload company logo (multipart) | +| POST | `/api/companies/:companyId/archive` | Archive company | | GET | `/api/companies/:companyId/projects` | List projects | | GET | `/api/projects/:projectId` | Project details | | POST | `/api/companies/:companyId/projects` | Create project (optional inline `workspace`) | @@ -639,6 +718,22 @@ Terminal states: `done`, `cancelled` | PATCH | `/api/goals/:goalId` | Update goal | | POST | `/api/companies/:companyId/openclaw/invite-prompt` | Generate OpenClaw invite prompt (CEO/board only) | +### Routines + +| Method | Path | Description | +| ------ | ---- | ----------- | +| GET | `/api/companies/:companyId/routines` | List all routines in company | +| GET | `/api/routines/:routineId` | Routine details including triggers | +| POST | `/api/companies/:companyId/routines` | Create routine (`assigneeAgentId` + `projectId` required; agents: own only) | +| PATCH | `/api/routines/:routineId` | Update routine (agents: own only, cannot reassign) | +| POST | `/api/routines/:routineId/triggers` | Add trigger (`schedule`, `webhook`, or `api` kind) | +| PATCH | `/api/routine-triggers/:triggerId` | Update trigger (e.g. disable, change cron) | +| DELETE | `/api/routine-triggers/:triggerId` | Delete trigger | +| POST | `/api/routine-triggers/:triggerId/rotate-secret` | Rotate webhook signing secret (previous secret immediately invalidated) | +| POST | `/api/routines/:routineId/run` | Manual run (bypasses schedule; concurrency policy still applies) | +| POST | `/api/routine-triggers/public/:publicId/fire` | Fire webhook trigger from external system | +| GET | `/api/routines/:routineId/runs` | Run history (default 50) | + ### Approvals, Costs, Activity, Dashboard | Method | Path | Description | @@ -650,14 +745,25 @@ Terminal states: `done`, `cancelled` | GET | `/api/approvals/:approvalId/issues` | Issues linked to approval | | GET | `/api/approvals/:approvalId/comments` | Approval comments | | POST | `/api/approvals/:approvalId/comments` | Add approval comment | +| POST | `/api/approvals/:approvalId/approve` | Approve approval request | +| POST | `/api/approvals/:approvalId/reject` | Reject approval request | | POST | `/api/approvals/:approvalId/request-revision`| Board asks for revision | | POST | `/api/approvals/:approvalId/resubmit` | Resubmit revised approval | +| POST | `/api/companies/:companyId/cost-events` | Report cost event | | GET | `/api/companies/:companyId/costs/summary` | Company cost summary | | GET | `/api/companies/:companyId/costs/by-agent` | Costs by agent | | GET | `/api/companies/:companyId/costs/by-project` | Costs by project | | GET | `/api/companies/:companyId/activity` | Activity log | | GET | `/api/companies/:companyId/dashboard` | Company health summary | +### Secrets + +| Method | Path | Description | +| ------ | ---- | ----------- | +| GET | `/api/companies/:companyId/secrets` | List secrets (metadata only) | +| POST | `/api/companies/:companyId/secrets` | Create secret | +| PATCH | `/api/secrets/:secretId` | Update secret value (creates new version) | + --- ## Common Mistakes @@ -674,3 +780,4 @@ Terminal states: `done`, `cancelled` | @-mention agents for no reason | Each mention triggers a budget-consuming heartbeat | Only mention agents who need to act | | Sit silently on blocked work | Nobody knows you're stuck; the task rots | Comment the blocker and escalate immediately | | Leave tasks in ambiguous states | Others can't tell if work is progressing | Always update status: `blocked`, `in_review`, or `done` | +| Block on another task without `blockedByIssueIds` | No automatic wake when blocker resolves; manual follow-up needed | Set `blockedByIssueIds` so Paperclip auto-wakes the assignee when all blockers are done | diff --git a/skills/paperclip/references/routines.md b/skills/paperclip/references/routines.md new file mode 100644 index 00000000000..1d1987fbee7 --- /dev/null +++ b/skills/paperclip/references/routines.md @@ -0,0 +1,187 @@ +# Paperclip Routines + +Routines are recurring tasks. Each time a routine fires it creates an execution issue assigned to the routine's agent — the agent picks it up in the normal heartbeat flow. + +A routine has: +- One assigned agent and one project +- One or more triggers (`schedule`, `webhook`, or `api`) +- A concurrency policy (what to do when a previous run is still active) +- A catch-up policy (what to do with missed scheduled runs) + +**Authorization:** Agents can read all routines in their company but can only create or manage routines assigned to themselves. Board operators have full access, including reassignment. + +--- + +## Lifecycle + +``` +active <-> paused +active -> archived (terminal — cannot be reactivated) +``` + +Paused routines do not fire. Archived routines do not fire and cannot be unarchived. + +--- + +## Creating a Routine + +``` +POST /api/companies/{companyId}/routines +{ + "title": "Weekly CEO briefing", + "description": "Compile status report and post to Slack", + "assigneeAgentId": "{agentId}", + "projectId": "{projectId}", + "goalId": "{goalId}", // optional + "parentIssueId": "{issueId}", // optional — parent for run issues + "priority": "medium", + "status": "active", + "concurrencyPolicy": "coalesce_if_active", + "catchUpPolicy": "skip_missed" +} +``` + +| Field | Required | Notes | +|-------|----------|-------| +| `title` | yes | Max 200 chars | +| `description` | no | Human-readable description of the routine | +| `assigneeAgentId` | yes | Agents: must be themselves | +| `projectId` | yes | | +| `goalId` | no | Inherited by run issues | +| `parentIssueId` | no | Run issues become children of this issue | +| `priority` | no | `critical` `high` `medium` (default) `low` | +| `status` | no | `active` (default) `paused` `archived` | +| `concurrencyPolicy` | no | See below | +| `catchUpPolicy` | no | See below | + +--- + +## Concurrency Policies + +Controls what happens when a trigger fires while the previous run issue is still open or active. + +| Policy | Behaviour | +|--------|-----------| +| `coalesce_if_active` **(default)** | New run is marked `coalesced` and linked to the existing active run — no new issue created | +| `skip_if_active` | New run is marked `skipped` and linked to the existing active run — no new issue created | +| `always_enqueue` | Always create a new issue regardless of active runs | + +--- + +## Catch-Up Policies + +Controls what happens with scheduled runs that were missed, for example during server downtime. + +| Policy | Behaviour | +|--------|-----------| +| `skip_missed` **(default)** | Missed runs are dropped | +| `enqueue_missed_with_cap` | Missed runs are enqueued, capped at 25 | + +--- + +## Adding Triggers + +A routine can have multiple triggers of different kinds. + +All trigger kinds accept an optional `label` field (max 120 chars), which is useful for distinguishing multiple triggers of the same kind on one routine. + +``` +POST /api/routines/{routineId}/triggers +``` + +### Schedule (cron) + +```json +{ + "kind": "schedule", + "cronExpression": "0 9 * * 1", + "timezone": "Europe/Amsterdam" +} +``` + +- `cronExpression`: standard 5-field cron syntax +- `timezone`: IANA timezone string (for example `UTC` or `America/New_York`) +- The server computes `nextRunAt` automatically + +### Webhook + +```json +{ + "kind": "webhook", + "signingMode": "hmac_sha256", + "replayWindowSec": 300 +} +``` + +- `signingMode`: `bearer` (default) or `hmac_sha256` +- `replayWindowSec`: 30-86400 (default 300) +- Response includes the webhook URL (`publicId`-based) and the signing secret +- Fire externally: `POST /api/routine-triggers/public/{publicId}/fire` + - Bearer: `Authorization: Bearer ` + - HMAC: `X-Paperclip-Signature` + `X-Paperclip-Timestamp` headers + +### API (manual only) + +```json +{ + "kind": "api" +} +``` + +No configuration. Fire via the manual run endpoint. + +--- + +## Updating and Deleting Triggers + +``` +PATCH /api/routine-triggers/{triggerId} +{ "enabled": false, "cronExpression": "0 10 * * 1" } + +DELETE /api/routine-triggers/{triggerId} +``` + +To rotate a webhook secret (the old secret is immediately invalidated): + +``` +POST /api/routine-triggers/{triggerId}/rotate-secret +``` + +--- + +## Manual Run + +Fires a run immediately, bypassing the schedule. Concurrency policy still applies. + +``` +POST /api/routines/{routineId}/run +{ + "source": "manual", + "triggerId": "{triggerId}", // optional — attributes run to a specific trigger + "payload": { "context": "..." }, // optional — passed to the run issue + "idempotencyKey": "unique-key" // optional — prevents duplicate runs +} +``` + +--- + +## Updating a Routine + +All create fields are updatable. Agents cannot reassign a routine to another agent. + +``` +PATCH /api/routines/{routineId} +{ "status": "paused", "title": "New title" } +``` + +--- + +## Reading Routines and Runs + +``` +GET /api/companies/{companyId}/routines +GET /api/routines/{routineId} +GET /api/routines/{routineId}/runs?limit=50 +``` + +Use the generic API endpoint tables in `skills/paperclip/references/api-reference.md` when you need a full cross-domain reference. Use this file when you need routine-specific behaviour, payload shape, or policy details. diff --git a/tests/e2e/onboarding.spec.ts b/tests/e2e/onboarding.spec.ts index a89fe11492e..c5ab59d87c0 100644 --- a/tests/e2e/onboarding.spec.ts +++ b/tests/e2e/onboarding.spec.ts @@ -22,18 +22,9 @@ const TASK_TITLE = "E2E test task"; test.describe("Onboarding wizard", () => { test("completes full wizard flow", async ({ page }) => { - await page.goto("/"); + await page.goto("/onboarding"); const wizardHeading = page.locator("h3", { hasText: "Name your company" }); - const newCompanyBtn = page.getByRole("button", { name: "New Company" }); - - await expect( - wizardHeading.or(newCompanyBtn) - ).toBeVisible({ timeout: 15_000 }); - - if (await newCompanyBtn.isVisible()) { - await newCompanyBtn.click(); - } await expect(wizardHeading).toBeVisible({ timeout: 5_000 }); @@ -45,7 +36,7 @@ test.describe("Onboarding wizard", () => { await expect( page.locator("h3", { hasText: "Create your first agent" }) - ).toBeVisible({ timeout: 10_000 }); + ).toBeVisible({ timeout: 30_000 }); const agentNameInput = page.locator('input[placeholder="CEO"]'); await expect(agentNameInput).toHaveValue(AGENT_NAME); @@ -61,7 +52,7 @@ test.describe("Onboarding wizard", () => { await expect( page.locator("h3", { hasText: "Give it something to do" }) - ).toBeVisible({ timeout: 10_000 }); + ).toBeVisible({ timeout: 30_000 }); const taskTitleInput = page.locator( 'input[placeholder="e.g. Research competitor pricing"]' @@ -73,7 +64,7 @@ test.describe("Onboarding wizard", () => { await expect( page.locator("h3", { hasText: "Ready to launch" }) - ).toBeVisible({ timeout: 10_000 }); + ).toBeVisible({ timeout: 30_000 }); await expect(page.locator("text=" + COMPANY_NAME)).toBeVisible(); await expect(page.locator("text=" + AGENT_NAME)).toBeVisible(); @@ -81,7 +72,7 @@ test.describe("Onboarding wizard", () => { await page.getByRole("button", { name: "Create & Open Issue" }).click(); - await expect(page).toHaveURL(/\/issues\//, { timeout: 10_000 }); + await expect(page).toHaveURL(/\/issues\//, { timeout: 30_000 }); const baseUrl = page.url().split("/").slice(0, 3).join("/"); diff --git a/tests/e2e/playwright.config.ts b/tests/e2e/playwright.config.ts index 33022502c79..572b012a3a7 100644 --- a/tests/e2e/playwright.config.ts +++ b/tests/e2e/playwright.config.ts @@ -1,6 +1,8 @@ import { defineConfig } from "@playwright/test"; -const PORT = Number(process.env.PAPERCLIP_E2E_PORT ?? 3100); +// Use a dedicated port so e2e tests always start their own server in local_trusted mode, +// even when the dev server is running on :3100 in authenticated mode. +const PORT = Number(process.env.PAPERCLIP_E2E_PORT ?? 3199); const BASE_URL = `http://127.0.0.1:${PORT}`; export default defineConfig({ @@ -29,6 +31,11 @@ export default defineConfig({ timeout: 120_000, stdout: "pipe", stderr: "pipe", + env: { + ...process.env, + PORT: String(PORT), + PAPERCLIP_DEPLOYMENT_MODE: "local_trusted", + }, }, outputDir: "./test-results", reporter: [["list"], ["html", { open: "never", outputFolder: "./playwright-report" }]], diff --git a/tests/e2e/signoff-policy.spec.ts b/tests/e2e/signoff-policy.spec.ts new file mode 100644 index 00000000000..934eeaf9f15 --- /dev/null +++ b/tests/e2e/signoff-policy.spec.ts @@ -0,0 +1,408 @@ +import { test, expect, request as pwRequest, type APIRequestContext } from "@playwright/test"; + +/** + * E2E: Signoff execution policy flow. + * + * Validates the full signoff lifecycle through the API and UI: + * 1. Create a company with executor + reviewer + approver agents + * 2. Create an issue with a two-stage execution policy (review → approval) + * 3. Executor marks done → issue routes to reviewer (in_review) + * 4. Reviewer approves → issue routes to approver + * 5. Approver approves → execution completes, issue marked done + * 6. Verify "changes requested" flow returns to executor + * + * Requires local_trusted deployment mode (set in playwright.config.ts webServer env). + * + * Agent auth flow: + * - Board request (local_trusted auto-auth) handles setup/teardown. + * - Agent-specific actions use API keys + heartbeat run IDs. + * - Reviewers/approvers invoke heartbeat runs (gets run IDs) then PATCH + * directly without checkout (checkout would force in_progress, breaking + * the in_review state the signoff policy requires). + */ + +const PORT = Number(process.env.PAPERCLIP_E2E_PORT ?? 3199); +const BASE_URL = `http://127.0.0.1:${PORT}`; +const COMPANY_NAME = `E2E-Signoff-${Date.now()}`; + +interface AgentAuth { + agentId: string; + token: string; + keyId: string; + request: APIRequestContext; +} + +interface TestContext { + companyId: string; + companyPrefix: string; + executor: AgentAuth; + reviewer: AgentAuth; + approver: AgentAuth; + boardRequest: APIRequestContext; + issueIds: string[]; +} + +/** Create an authenticated APIRequestContext for an agent (token set, no run ID yet). */ +async function createAgentRequest(token: string): Promise { + return pwRequest.newContext({ + baseURL: BASE_URL, + extraHTTPHeaders: { Authorization: `Bearer ${token}` }, + }); +} + +/** Invoke a heartbeat run for an agent, returning the run ID. */ +async function invokeHeartbeat(board: APIRequestContext, agentId: string): Promise { + const res = await board.post(`${BASE_URL}/api/agents/${agentId}/heartbeat/invoke`); + expect(res.ok()).toBe(true); + const run = await res.json(); + return run.id; +} + +/** PATCH an issue as an agent with a fresh heartbeat run ID. */ +async function agentPatch( + board: APIRequestContext, + agent: AgentAuth, + issueId: string, + data: Record, +) { + const runId = await invokeHeartbeat(board, agent.agentId); + const res = await agent.request.patch(`${BASE_URL}/api/issues/${issueId}`, { + headers: { "X-Paperclip-Run-Id": runId }, + data, + }); + return res; +} + +/** Checkout an issue as an agent, then PATCH it. Used for executor mark-done. */ +async function agentCheckoutAndPatch( + board: APIRequestContext, + agent: AgentAuth, + issueId: string, + expectedStatuses: string[], + patchData: Record, +) { + const runId = await invokeHeartbeat(board, agent.agentId); + // Checkout (sets executionRunId so PATCH is allowed) + const checkoutRes = await agent.request.post(`${BASE_URL}/api/issues/${issueId}/checkout`, { + headers: { "X-Paperclip-Run-Id": runId }, + data: { agentId: agent.agentId, expectedStatuses }, + }); + if (!checkoutRes.ok()) { + // If agent checkout fails (e.g. run expired), fall back to board checkout + // then PATCH with the agent's identity + const boardCheckout = await board.post(`${BASE_URL}/api/issues/${issueId}/checkout`, { + data: { agentId: agent.agentId, expectedStatuses }, + }); + if (!boardCheckout.ok()) { + throw new Error(`Board checkout failed: ${await boardCheckout.text()}`); + } + // Board PATCH (executor mark-done triggers signoff regardless of actor) + const res = await board.patch(`${BASE_URL}/api/issues/${issueId}`, { + data: patchData, + }); + return res; + } + // PATCH with agent identity + const res = await agent.request.patch(`${BASE_URL}/api/issues/${issueId}`, { + headers: { "X-Paperclip-Run-Id": runId }, + data: patchData, + }); + return res; +} + +async function setupCompany(boardRequest: APIRequestContext): Promise { + // Verify server is in local_trusted mode + const healthRes = await boardRequest.get(`${BASE_URL}/api/health`); + expect(healthRes.ok()).toBe(true); + const health = await healthRes.json(); + if (health.deploymentMode !== "local_trusted") { + throw new Error( + `Signoff e2e tests require local_trusted deployment mode, ` + + `but server is in "${health.deploymentMode}" mode. ` + + `Set PAPERCLIP_DEPLOYMENT_MODE=local_trusted or use the webServer config.`, + ); + } + + // Create company + const companyRes = await boardRequest.post(`${BASE_URL}/api/companies`, { + data: { name: COMPANY_NAME }, + }); + if (!companyRes.ok()) { + const errBody = await companyRes.text(); + throw new Error(`POST /api/companies → ${companyRes.status()}: ${errBody}`); + } + const company = await companyRes.json(); + const companyId = company.id; + const companyPrefix = company.issuePrefix ?? company.prefix ?? company.urlKey ?? "E2E"; + + // Helper: create agent + API key + request context + async function createAgent(name: string, role: string, title: string): Promise { + const agentRes = await boardRequest.post(`${BASE_URL}/api/companies/${companyId}/agents`, { + data: { + name, + role, + title, + adapterType: "process", + adapterConfig: { + command: process.execPath, + args: ["-e", "process.stdout.write('done\\n')"], + }, + }, + }); + expect(agentRes.ok()).toBe(true); + const agent = await agentRes.json(); + + const keyRes = await boardRequest.post(`${BASE_URL}/api/agents/${agent.id}/keys`, { + data: { name: `e2e-${name.toLowerCase()}` }, + }); + expect(keyRes.ok()).toBe(true); + const keyData = await keyRes.json(); + + return { + agentId: agent.id, + token: keyData.token, + keyId: keyData.id, + request: await createAgentRequest(keyData.token), + }; + } + + const executor = await createAgent("Executor", "engineer", "Software Engineer"); + const reviewer = await createAgent("Reviewer", "qa", "QA Engineer"); + const approver = await createAgent("Approver", "cto", "CTO"); + + return { + companyId, + companyPrefix, + executor, + reviewer, + approver, + boardRequest, + issueIds: [], + }; +} + +async function createIssueWithPolicy(ctx: TestContext, title: string, stages?: unknown[]) { + const defaultStages = [ + { type: "review", participants: [{ type: "agent", agentId: ctx.reviewer.agentId }] }, + { type: "approval", participants: [{ type: "agent", agentId: ctx.approver.agentId }] }, + ]; + const res = await ctx.boardRequest.post(`${BASE_URL}/api/companies/${ctx.companyId}/issues`, { + data: { + title, + status: "in_progress", + assigneeAgentId: ctx.executor.agentId, + executionPolicy: { stages: stages ?? defaultStages }, + }, + }); + expect(res.ok()).toBe(true); + const issue = await res.json(); + ctx.issueIds.push(issue.id); + return issue; +} + +test.describe("Signoff execution policy", () => { + let ctx: TestContext; + + test.beforeAll(async () => { + const boardRequest = await pwRequest.newContext({ baseURL: BASE_URL }); + ctx = await setupCompany(boardRequest); + }); + + test.afterAll(async () => { + if (!ctx) return; + const board = ctx.boardRequest; + + // Dispose agent request contexts + for (const agent of [ctx.executor, ctx.reviewer, ctx.approver]) { + await agent.request.dispose(); + } + + // Clean up issues, keys, agents, company (best-effort) + for (const issueId of ctx.issueIds) { + await board.patch(`${BASE_URL}/api/issues/${issueId}`, { + data: { status: "cancelled", comment: "E2E test cleanup." }, + }).catch(() => {}); + } + for (const agent of [ctx.executor, ctx.reviewer, ctx.approver]) { + await board.delete(`${BASE_URL}/api/agents/${agent.agentId}/keys/${agent.keyId}`).catch(() => {}); + await board.delete(`${BASE_URL}/api/agents/${agent.agentId}`).catch(() => {}); + } + await board.delete(`${BASE_URL}/api/companies/${ctx.companyId}`).catch(() => {}); + await board.dispose(); + }); + + test("happy path: executor → review → approval → done", async ({ page }) => { + const issue = await createIssueWithPolicy(ctx, "Signoff happy path"); + const issueId = issue.id; + + // Verify policy was saved + expect(issue.executionPolicy).toBeTruthy(); + expect(issue.executionPolicy.stages).toHaveLength(2); + expect(issue.executionPolicy.stages[0].type).toBe("review"); + expect(issue.executionPolicy.stages[1].type).toBe("approval"); + + // Step 1: Executor marks done → should route to reviewer + const step1Res = await agentCheckoutAndPatch( + ctx.boardRequest, ctx.executor, issueId, ["in_progress"], + { status: "done", comment: "Implemented the feature, ready for review." }, + ); + expect(step1Res.ok()).toBe(true); + const step1Issue = await step1Res.json(); + + expect(step1Issue.status).toBe("in_review"); + expect(step1Issue.assigneeAgentId).toBe(ctx.reviewer.agentId); + expect(step1Issue.executionState).toBeTruthy(); + expect(step1Issue.executionState.status).toBe("pending"); + expect(step1Issue.executionState.currentStageType).toBe("review"); + expect(step1Issue.executionState.returnAssignee).toMatchObject({ + type: "agent", + agentId: ctx.executor.agentId, + }); + + // Step 2: Navigate to issue in UI and verify execution label + await page.goto(`/${ctx.companyPrefix}/issues/${issue.identifier}`); + await expect(page.locator("text=Review pending")).toBeVisible({ timeout: 10_000 }); + + // Step 3: Reviewer approves → should route to approver + const step3Res = await agentPatch( + ctx.boardRequest, ctx.reviewer, issueId, + { status: "done", comment: "QA signoff complete. Looks good." }, + ); + expect(step3Res.ok()).toBe(true); + const step3Issue = await step3Res.json(); + + expect(step3Issue.status).toBe("in_review"); + expect(step3Issue.assigneeAgentId).toBe(ctx.approver.agentId); + expect(step3Issue.executionState.status).toBe("pending"); + expect(step3Issue.executionState.currentStageType).toBe("approval"); + expect(step3Issue.executionState.completedStageIds).toHaveLength(1); + + // Step 4: Verify UI shows approval pending + await page.reload(); + await expect(page.locator("text=Approval pending")).toBeVisible({ timeout: 10_000 }); + + // Step 5: Approver approves → should complete + const step5Res = await agentPatch( + ctx.boardRequest, ctx.approver, issueId, + { status: "done", comment: "Approved. Ship it." }, + ); + expect(step5Res.ok()).toBe(true); + const step5Issue = await step5Res.json(); + + expect(step5Issue.status).toBe("done"); + expect(step5Issue.executionState.status).toBe("completed"); + expect(step5Issue.executionState.completedStageIds).toHaveLength(2); + expect(step5Issue.executionState.lastDecisionOutcome).toBe("approved"); + }); + + test("changes requested: reviewer bounces back to executor", async () => { + const issue = await createIssueWithPolicy(ctx, "Signoff changes requested"); + const issueId = issue.id; + + // Executor marks done → routes to reviewer + const doneRes = await agentCheckoutAndPatch( + ctx.boardRequest, ctx.executor, issueId, ["in_progress"], + { status: "done", comment: "Ready for review." }, + ); + expect(doneRes.ok()).toBe(true); + expect((await doneRes.json()).status).toBe("in_review"); + + // Reviewer requests changes → returns to executor + const changesRes = await agentPatch( + ctx.boardRequest, ctx.reviewer, issueId, + { status: "in_progress", comment: "Needs another pass on edge cases." }, + ); + expect(changesRes.ok()).toBe(true); + const changesIssue = await changesRes.json(); + + expect(changesIssue.status).toBe("in_progress"); + expect(changesIssue.assigneeAgentId).toBe(ctx.executor.agentId); + expect(changesIssue.executionState.status).toBe("changes_requested"); + expect(changesIssue.executionState.lastDecisionOutcome).toBe("changes_requested"); + + // Executor re-submits → goes back to reviewer (same stage) + const resubmitRes = await agentCheckoutAndPatch( + ctx.boardRequest, ctx.executor, issueId, ["in_progress"], + { status: "done", comment: "Fixed the edge cases." }, + ); + expect(resubmitRes.ok()).toBe(true); + const resubmitIssue = await resubmitRes.json(); + + expect(resubmitIssue.status).toBe("in_review"); + expect(resubmitIssue.assigneeAgentId).toBe(ctx.reviewer.agentId); + expect(resubmitIssue.executionState.status).toBe("pending"); + expect(resubmitIssue.executionState.currentStageType).toBe("review"); + }); + + test("comment required: approval without comment fails", async () => { + const issue = await createIssueWithPolicy(ctx, "Signoff comment required"); + const issueId = issue.id; + + // Executor marks done → routes to reviewer + await agentCheckoutAndPatch( + ctx.boardRequest, ctx.executor, issueId, ["in_progress"], + { status: "done", comment: "Done." }, + ); + + // Reviewer tries to approve without comment → should fail + const noCommentRes = await agentPatch( + ctx.boardRequest, ctx.reviewer, issueId, + { status: "done" }, + ); + expect(noCommentRes.ok()).toBe(false); + const errorBody = await noCommentRes.json(); + expect(JSON.stringify(errorBody)).toContain("comment"); + }); + + test("non-participant cannot advance stage", async () => { + const issue = await createIssueWithPolicy(ctx, "Signoff access control"); + const issueId = issue.id; + + // Executor marks done → routes to reviewer + const doneRes = await agentCheckoutAndPatch( + ctx.boardRequest, ctx.executor, issueId, ["in_progress"], + { status: "done", comment: "Done." }, + ); + expect(doneRes.ok()).toBe(true); + + // Verify issue is in_review with reviewer + const issueRes = await ctx.boardRequest.get(`${BASE_URL}/api/issues/${issueId}`); + const inReviewIssue = await issueRes.json(); + expect(inReviewIssue.status).toBe("in_review"); + expect(inReviewIssue.assigneeAgentId).toBe(ctx.reviewer.agentId); + expect(inReviewIssue.executionState.currentStageType).toBe("review"); + + // Non-participant (approver at this stage) tries to advance → should be rejected + const advanceRes = await agentPatch( + ctx.boardRequest, ctx.approver, issueId, + { status: "done", comment: "I'm the approver, not the reviewer." }, + ); + expect(advanceRes.ok()).toBe(false); + expect(advanceRes.status()).toBeGreaterThanOrEqual(400); + }); + + test("review-only policy: reviewer approval completes execution", async () => { + const issue = await createIssueWithPolicy(ctx, "Signoff review-only", [ + { type: "review", participants: [{ type: "agent", agentId: ctx.reviewer.agentId }] }, + ]); + + // Executor marks done → routes to reviewer + const doneRes = await agentCheckoutAndPatch( + ctx.boardRequest, ctx.executor, issue.id, ["in_progress"], + { status: "done", comment: "Ready for review." }, + ); + expect(doneRes.ok()).toBe(true); + expect((await doneRes.json()).status).toBe("in_review"); + + // Reviewer approves → should complete immediately (no approval stage) + const approveRes = await agentPatch( + ctx.boardRequest, ctx.reviewer, issue.id, + { status: "done", comment: "LGTM." }, + ); + expect(approveRes.ok()).toBe(true); + const doneIssue = await approveRes.json(); + expect(doneIssue.status).toBe("done"); + expect(doneIssue.executionState.status).toBe("completed"); + expect(doneIssue.executionState.completedStageIds).toHaveLength(1); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 3a989f389d1..e597c331112 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,11 +3,13 @@ "files": [], "references": [ { "path": "./packages/adapter-utils" }, + { "path": "./packages/mcp-server" }, { "path": "./packages/shared" }, { "path": "./packages/db" }, { "path": "./packages/adapters/claude-local" }, { "path": "./packages/adapters/codex-local" }, { "path": "./packages/adapters/cursor-local" }, + { "path": "./packages/adapters/droid-local" }, { "path": "./packages/adapters/openclaw-gateway" }, { "path": "./packages/adapters/opencode-local" }, { "path": "./packages/adapters/pi-local" }, diff --git a/ui/index.html b/ui/index.html index 1bb9152e1e3..70a8550ead5 100644 --- a/ui/index.html +++ b/ui/index.html @@ -4,6 +4,7 @@ + diff --git a/ui/package.json b/ui/package.json index d344f67aeaa..020e2ec2f8a 100644 --- a/ui/package.json +++ b/ui/package.json @@ -26,11 +26,11 @@ "access": "public" }, "dependencies": { + "@assistant-ui/react": "0.12.23", "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", "@lexical/link": "0.35.0", - "lexical": "0.35.0", "@mdxeditor/editor": "^3.52.4", "@paperclipai/adapter-claude-local": "workspace:*", "@paperclipai/adapter-codex-local": "workspace:*", @@ -41,13 +41,14 @@ "@paperclipai/adapter-pi-local": "workspace:*", "@paperclipai/adapter-utils": "workspace:*", "@paperclipai/shared": "workspace:*", - "hermes-paperclip-adapter": "^0.2.0", "@radix-ui/react-slot": "^1.2.4", "@tailwindcss/typography": "^0.5.19", "@tanstack/react-query": "^5.90.21", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", + "hermes-paperclip-adapter": "^0.2.0", + "lexical": "0.35.0", "lucide-react": "^0.574.0", "mermaid": "^11.12.0", "radix-ui": "^1.4.3", diff --git a/ui/src/App.tsx b/ui/src/App.tsx index f240defcf7d..bb4a31e38c0 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -34,7 +34,9 @@ import { InstanceSettings } from "./pages/InstanceSettings"; import { InstanceExperimentalSettings } from "./pages/InstanceExperimentalSettings"; import { PluginManager } from "./pages/PluginManager"; import { PluginSettings } from "./pages/PluginSettings"; +import { AdapterManager } from "./pages/AdapterManager"; import { PluginPage } from "./pages/PluginPage"; +import { IssueChatUxLab } from "./pages/IssueChatUxLab"; import { RunTranscriptUxLab } from "./pages/RunTranscriptUxLab"; import { OrgChart } from "./pages/OrgChart"; import { NewAgent } from "./pages/NewAgent"; @@ -159,6 +161,8 @@ function boardRoutes() { } /> } /> } /> + } /> + } /> } /> } /> } /> @@ -174,7 +178,9 @@ function boardRoutes() { } /> } /> } /> + } /> } /> + } /> } /> } /> @@ -321,6 +327,7 @@ export function App() { } /> } /> } /> + } /> } /> } /> diff --git a/ui/src/adapters/adapter-display-registry.ts b/ui/src/adapters/adapter-display-registry.ts new file mode 100644 index 00000000000..fe809273962 --- /dev/null +++ b/ui/src/adapters/adapter-display-registry.ts @@ -0,0 +1,157 @@ +/** + * Single source of truth for adapter display metadata. + * + * Built-in adapters have entries in `adapterDisplayMap`. External (plugin) + * adapters get sensible defaults derived from their type string via + * `getAdapterDisplay()`. + */ +import type { ComponentType } from "react"; +import { + Bot, + Code, + Gem, + MousePointer2, + Sparkles, + Terminal, + Cpu, +} from "lucide-react"; +import { OpenCodeLogoIcon } from "@/components/OpenCodeLogoIcon"; +import { HermesIcon } from "@/components/HermesIcon"; + +// --------------------------------------------------------------------------- +// Type suffix parsing +// --------------------------------------------------------------------------- + +const TYPE_SUFFIXES: Record = { + _local: "local", + _gateway: "gateway", +}; + +function getTypeSuffix(type: string): string | null { + for (const [suffix, mode] of Object.entries(TYPE_SUFFIXES)) { + if (type.endsWith(suffix)) return mode; + } + return null; +} + +function withSuffix(label: string, suffix: string | null): string { + return suffix ? `${label} (${suffix})` : label; +} + +// --------------------------------------------------------------------------- +// Display metadata per adapter type +// --------------------------------------------------------------------------- + +export interface AdapterDisplayInfo { + label: string; + description: string; + icon: ComponentType<{ className?: string }>; + recommended?: boolean; + comingSoon?: boolean; + disabledLabel?: string; +} + +const adapterDisplayMap: Record = { + claude_local: { + label: "Claude Code", + description: "Local Claude agent", + icon: Sparkles, + recommended: true, + }, + codex_local: { + label: "Codex", + description: "Local Codex agent", + icon: Code, + recommended: true, + }, + gemini_local: { + label: "Gemini CLI", + description: "Local Gemini agent", + icon: Gem, + }, + opencode_local: { + label: "OpenCode", + description: "Local multi-provider agent", + icon: OpenCodeLogoIcon, + }, + hermes_local: { + label: "Hermes Agent", + description: "Local Hermes CLI agent", + icon: HermesIcon, + }, + pi_local: { + label: "Pi", + description: "Local Pi agent", + icon: Terminal, + }, + cursor: { + label: "Cursor", + description: "Local Cursor agent", + icon: MousePointer2, + }, + openclaw_gateway: { + label: "OpenClaw Gateway", + description: "Invoke OpenClaw via gateway protocol", + icon: Bot, + comingSoon: true, + disabledLabel: "Configure OpenClaw within the App", + }, + process: { + label: "Process", + description: "Internal process adapter", + icon: Cpu, + comingSoon: true, + }, + http: { + label: "HTTP", + description: "Internal HTTP adapter", + icon: Cpu, + comingSoon: true, + }, +}; + +// --------------------------------------------------------------------------- +// Public API +// --------------------------------------------------------------------------- + +function humanizeType(type: string): string { + // Strip known type suffixes so "droid_local" → "Droid", not "Droid Local" + let base = type; + for (const suffix of Object.keys(TYPE_SUFFIXES)) { + if (base.endsWith(suffix)) { + base = base.slice(0, -suffix.length); + break; + } + } + return base.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()); +} + +export function getAdapterLabel(type: string): string { + const base = adapterDisplayMap[type]?.label ?? humanizeType(type); + return withSuffix(base, getTypeSuffix(type)); +} + +export function getAdapterLabels(): Record { + const suffixed: Record = {}; + for (const [type, info] of Object.entries(adapterDisplayMap)) { + suffixed[type] = withSuffix(info.label, getTypeSuffix(type)); + } + return suffixed; +} + +export function getAdapterDisplay(type: string): AdapterDisplayInfo { + const known = adapterDisplayMap[type]; + if (known) return known; + + const suffix = getTypeSuffix(type); + const label = withSuffix(humanizeType(type), suffix); + return { + label, + description: suffix ? `External ${suffix} adapter` : "External adapter", + icon: Cpu, + }; +} + +export function isKnownAdapterType(type: string): boolean { + return type in adapterDisplayMap; +} diff --git a/ui/src/adapters/claude-local/config-fields.tsx b/ui/src/adapters/claude-local/config-fields.tsx index 972c378e585..fd46352beb0 100644 --- a/ui/src/adapters/claude-local/config-fields.tsx +++ b/ui/src/adapters/claude-local/config-fields.tsx @@ -125,9 +125,9 @@ export function ClaudeLocalAdvancedFields({ value={eff( "adapterConfig", "maxTurnsPerRun", - Number(config.maxTurnsPerRun ?? 300), + Number(config.maxTurnsPerRun ?? 1000), )} - onCommit={(v) => mark("adapterConfig", "maxTurnsPerRun", v || 300)} + onCommit={(v) => mark("adapterConfig", "maxTurnsPerRun", v || 1000)} immediate className={inputClass} /> diff --git a/ui/src/adapters/disabled-store.ts b/ui/src/adapters/disabled-store.ts new file mode 100644 index 00000000000..66de3e71201 --- /dev/null +++ b/ui/src/adapters/disabled-store.ts @@ -0,0 +1,33 @@ +/** + * Client-side store for disabled adapter types. + * + * Hydrated from the server's GET /api/adapters response. + * Provides synchronous reads so module-level constants can filter against it. + * Falls back to "nothing disabled" before the first hydration. + * + * Usage in components: + * useQuery + adaptersApi.list() populates the store automatically. + * + * Usage in non-React code: + * import { isAdapterTypeHidden } from "@/adapters/disabled-store"; + */ + +let disabledTypes = new Set(); + +/** Check if an adapter type is hidden from menus (sync read). */ +export function isAdapterTypeHidden(type: string): boolean { + return disabledTypes.has(type); +} + +/** Get all hidden adapter types (sync read). */ +export function getHiddenAdapterTypes(): Set { + return disabledTypes; +} + +/** + * Hydrate the store from a server response. + * Called by components that fetch the adapters list. + */ +export function setDisabledAdapterTypes(types: string[]): void { + disabledTypes = new Set(types); +} diff --git a/ui/src/adapters/dynamic-loader.ts b/ui/src/adapters/dynamic-loader.ts new file mode 100644 index 00000000000..aec2efa84c7 --- /dev/null +++ b/ui/src/adapters/dynamic-loader.ts @@ -0,0 +1,122 @@ +/** + * Dynamic UI parser loading for external adapters. + * + * When the Paperclip UI encounters an adapter type that doesn't have a + * built-in parser (e.g., an external adapter loaded via the plugin system), + * it fetches the parser JS from `/api/adapters/:type/ui-parser.js` and + * evaluates it to create a `parseStdoutLine` function. + * + * The parser module must export: + * - `parseStdoutLine(line: string, ts: string): TranscriptEntry[]` + * - optionally `createStdoutParser(): { parseLine, reset }` for stateful parsers + * + * This is the bridge between the server-side plugin system and the client-side + * UI rendering. Adapter developers ship a `dist/ui-parser.js` with zero + * runtime dependencies, and Paperclip's UI loads it on demand. + */ + +import type { TranscriptEntry } from "@paperclipai/adapter-utils"; +import type { StatefulStdoutParser, StdoutLineParser, StdoutParserFactory } from "./types"; + +interface DynamicParserModule { + parseStdoutLine: StdoutLineParser; + createStdoutParser?: StdoutParserFactory; +} + +// Cache of dynamically loaded parsers by adapter type. +// Once loaded, the parser is reused for all runs of that adapter type. +const dynamicParserCache = new Map(); + +// Track which types we've already attempted to load (to avoid repeat 404s). +const failedLoads = new Set(); + +/** + * Dynamically load a UI parser for an adapter type from the server API. + * + * Fetches `/api/adapters/:type/ui-parser.js`, evaluates the module source + * in a scoped context, and extracts the `parseStdoutLine` export. + * + * @returns A StdoutLineParser function, or null if unavailable. + */ +export async function loadDynamicParser(adapterType: string): Promise { + // Return cached parser if already loaded + const cached = dynamicParserCache.get(adapterType); + if (cached) return cached; + + // Don't retry types that previously 404'd + if (failedLoads.has(adapterType)) return null; + + try { + const response = await fetch(`/api/adapters/${encodeURIComponent(adapterType)}/ui-parser.js`); + if (!response.ok) { + failedLoads.add(adapterType); + return null; + } + + const source = await response.text(); + + // Evaluate the module source using URL.createObjectURL + dynamic import(). + // This properly supports ESM modules with `export` statements. + // (new Function("exports", source) would fail with SyntaxError on `export` keywords.) + const blob = new Blob([source], { type: "application/javascript" }); + const blobUrl = URL.createObjectURL(blob); + + let parserModule: DynamicParserModule; + + try { + const mod = await import(/* @vite-ignore */ blobUrl); + + // Prefer the factory function (stateful parser) if available, + // fall back to the static parseStdoutLine function. + if (typeof mod.createStdoutParser === "function") { + const createStdoutParser = mod.createStdoutParser as StdoutParserFactory; + parserModule = { + createStdoutParser, + // Fallback for callers that only know about parseStdoutLine. + parseStdoutLine: + typeof mod.parseStdoutLine === "function" + ? (mod.parseStdoutLine as StdoutLineParser) + : ((line: string, ts: string) => { + const parser = createStdoutParser() as StatefulStdoutParser; + const entries = parser.parseLine(line, ts); + parser.reset(); + return entries; + }), + }; + } else if (typeof mod.parseStdoutLine === "function") { + parserModule = { + parseStdoutLine: mod.parseStdoutLine as StdoutLineParser, + }; + } else { + console.warn(`[adapter-ui-loader] Module for "${adapterType}" exports neither parseStdoutLine nor createStdoutParser`); + failedLoads.add(adapterType); + return null; + } + } finally { + URL.revokeObjectURL(blobUrl); + } + + // Cache for reuse + dynamicParserCache.set(adapterType, parserModule); + console.info(`[adapter-ui-loader] Loaded dynamic UI parser for "${adapterType}"`); + return parserModule; + } catch (err) { + console.warn(`[adapter-ui-loader] Failed to load UI parser for "${adapterType}":`, err); + failedLoads.add(adapterType); + return null; + } +} + +/** + * Invalidate a cached dynamic parser, removing it from both the parser cache + * and the failed-loads set so that the next load attempt will try again. + */ +export function invalidateDynamicParser(adapterType: string): boolean { + const wasCached = dynamicParserCache.has(adapterType); + dynamicParserCache.delete(adapterType); + failedLoads.delete(adapterType); + if (wasCached) { + console.info(`[adapter-ui-loader] Invalidated dynamic UI parser for "${adapterType}"`); + } + return wasCached; +} diff --git a/ui/src/adapters/hermes-local/config-fields.tsx b/ui/src/adapters/hermes-local/config-fields.tsx index 62b85fea885..4b807043652 100644 --- a/ui/src/adapters/hermes-local/config-fields.tsx +++ b/ui/src/adapters/hermes-local/config-fields.tsx @@ -1,49 +1,49 @@ -import type { AdapterConfigFieldsProps } from "../types"; -import { - Field, - DraftInput, -} from "../../components/agent-config-primitives"; -import { ChoosePathButton } from "../../components/PathInstructionsModal"; - -const inputClass = - "w-full rounded-md border border-border px-2.5 py-1.5 bg-transparent outline-none text-sm font-mono placeholder:text-muted-foreground/40"; -const instructionsFileHint = - "Absolute path to a markdown file (e.g. AGENTS.md) that defines this agent's behavior. Injected into the system prompt at runtime."; - -export function HermesLocalConfigFields({ - isCreate, - values, - set, - config, - eff, - mark, - hideInstructionsFile, -}: AdapterConfigFieldsProps) { - if (hideInstructionsFile) return null; - return ( - -
- - isCreate - ? set!({ instructionsFilePath: v }) - : mark("adapterConfig", "instructionsFilePath", v || undefined) - } - immediate - className={inputClass} - placeholder="/absolute/path/to/AGENTS.md" - /> - -
-
- ); -} +import type { AdapterConfigFieldsProps } from "../types"; +import { + Field, + DraftInput, +} from "../../components/agent-config-primitives"; +import { ChoosePathButton } from "../../components/PathInstructionsModal"; + +const inputClass = + "w-full rounded-md border border-border px-2.5 py-1.5 bg-transparent outline-none text-sm font-mono placeholder:text-muted-foreground/40"; +const instructionsFileHint = + "Absolute path to a markdown file (e.g. AGENTS.md) that defines this agent's behavior. Injected into the system prompt at runtime."; + +export function HermesLocalConfigFields({ + isCreate, + values, + set, + config, + eff, + mark, + hideInstructionsFile, +}: AdapterConfigFieldsProps) { + if (hideInstructionsFile) return null; + return ( + +
+ + isCreate + ? set!({ instructionsFilePath: v }) + : mark("adapterConfig", "instructionsFilePath", v || undefined) + } + immediate + className={inputClass} + placeholder="/absolute/path/to/AGENTS.md" + /> + +
+
+ ); +} diff --git a/ui/src/adapters/hermes-local/index.ts b/ui/src/adapters/hermes-local/index.ts index 97c064f8d61..c037a747dc0 100644 --- a/ui/src/adapters/hermes-local/index.ts +++ b/ui/src/adapters/hermes-local/index.ts @@ -1,12 +1,12 @@ -import type { UIAdapterModule } from "../types"; -import { parseHermesStdoutLine } from "hermes-paperclip-adapter/ui"; -import { HermesLocalConfigFields } from "./config-fields"; -import { buildHermesConfig } from "hermes-paperclip-adapter/ui"; - -export const hermesLocalUIAdapter: UIAdapterModule = { - type: "hermes_local", - label: "Hermes Agent", - parseStdoutLine: parseHermesStdoutLine, - ConfigFields: HermesLocalConfigFields, - buildAdapterConfig: buildHermesConfig, -}; +import type { UIAdapterModule } from "../types"; +import { parseHermesStdoutLine } from "hermes-paperclip-adapter/ui"; +import { SchemaConfigFields, buildSchemaAdapterConfig } from "../schema-config-fields"; +import { buildHermesConfig } from "hermes-paperclip-adapter/ui"; + +export const hermesLocalUIAdapter: UIAdapterModule = { + type: "hermes_local", + label: "Hermes Agent", + parseStdoutLine: parseHermesStdoutLine, + ConfigFields: SchemaConfigFields, + buildAdapterConfig: buildSchemaAdapterConfig, +}; diff --git a/ui/src/adapters/index.ts b/ui/src/adapters/index.ts index feb04511abc..5623cf8a858 100644 --- a/ui/src/adapters/index.ts +++ b/ui/src/adapters/index.ts @@ -1,4 +1,12 @@ -export { getUIAdapter, listUIAdapters } from "./registry"; +export { + getUIAdapter, + listUIAdapters, + findUIAdapter, + registerUIAdapter, + unregisterUIAdapter, + syncExternalAdapters, + onAdapterChange, +} from "./registry"; export { buildTranscript } from "./transcript"; export type { TranscriptEntry, diff --git a/ui/src/adapters/metadata.test.ts b/ui/src/adapters/metadata.test.ts new file mode 100644 index 00000000000..70b7ef3c96b --- /dev/null +++ b/ui/src/adapters/metadata.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, it } from "vitest"; +import { isEnabledAdapterType, listAdapterOptions } from "./metadata"; +import type { UIAdapterModule } from "./types"; + +const externalAdapter: UIAdapterModule = { + type: "external_test", + label: "External Test", + parseStdoutLine: () => [], + ConfigFields: () => null, + buildAdapterConfig: () => ({}), +}; + +describe("adapter metadata", () => { + it("treats registered external adapters as enabled by default", () => { + expect(isEnabledAdapterType("external_test")).toBe(true); + + expect( + listAdapterOptions((type) => type, [externalAdapter]), + ).toEqual([ + { + value: "external_test", + label: "external_test", + comingSoon: false, + hidden: false, + }, + ]); + }); + + it("keeps intentionally withheld built-in adapters marked as coming soon", () => { + expect(isEnabledAdapterType("process")).toBe(false); + expect(isEnabledAdapterType("http")).toBe(false); + }); +}); \ No newline at end of file diff --git a/ui/src/adapters/metadata.ts b/ui/src/adapters/metadata.ts new file mode 100644 index 00000000000..297a7237c32 --- /dev/null +++ b/ui/src/adapters/metadata.ts @@ -0,0 +1,75 @@ +/** + * Adapter metadata utilities — built on top of the display registry and UI adapter list. + * + * This module bridges the static display metadata with the dynamic adapter registry. + * "Coming soon" status is derived from the display registry's `comingSoon` flag. + * "Hidden" status comes from the disabled-adapter store (server-side toggle). + */ +import type { UIAdapterModule } from "./types"; +import { listUIAdapters } from "./registry"; +import { isAdapterTypeHidden } from "./disabled-store"; +import { getAdapterLabel, getAdapterDisplay } from "./adapter-display-registry"; + +export interface AdapterOptionMetadata { + value: string; + label: string; + comingSoon: boolean; + hidden: boolean; +} + +export function listKnownAdapterTypes(): string[] { + return listUIAdapters().map((adapter) => adapter.type); +} + +/** + * Check whether an adapter type is enabled (not "coming soon"). + * Unknown types (external adapters) are always considered enabled. + */ +export function isEnabledAdapterType(type: string): boolean { + // Check display registry first — built-in adapters like process/http are + // intentionally withheld even though they're registered as UI adapters. + if (getAdapterDisplay(type).comingSoon) return false; + // All other types (registered or external) are enabled. + return true; +} + +/** + * Check whether an adapter type is a valid choice for new agent creation. + * Includes all registered UI adapters (built-in + external) and + * any non-"coming soon" adapter from the display registry. + */ +export function isValidAdapterType(type: string): boolean { + if (getAdapterDisplay(type).comingSoon) return false; + return true; +} + +/** + * Build option metadata for a list of adapters (for dropdowns). + * `labelFor` callback allows callers to override labels; defaults to display registry. + */ +export function listAdapterOptions( + labelFor?: (type: string) => string, + adapters: UIAdapterModule[] = listUIAdapters(), +): AdapterOptionMetadata[] { + const getLabel = labelFor ?? getAdapterLabel; + return adapters.map((adapter) => ({ + value: adapter.type, + label: getLabel(adapter.type), + comingSoon: !!getAdapterDisplay(adapter.type).comingSoon, + hidden: isAdapterTypeHidden(adapter.type), + })); +} + +/** + * List UI adapters excluding those hidden via the Adapters settings page. + */ +export function listVisibleUIAdapters(): UIAdapterModule[] { + return listUIAdapters().filter((a) => !isAdapterTypeHidden(a.type)); +} + +/** + * List visible adapter types (for non-React contexts like module-level constants). + */ +export function listVisibleAdapterTypes(): string[] { + return listVisibleUIAdapters().map((a) => a.type); +} diff --git a/ui/src/adapters/openclaw-gateway/config-fields.tsx b/ui/src/adapters/openclaw-gateway/config-fields.tsx index 19780d94e66..82bff3e29c8 100644 --- a/ui/src/adapters/openclaw-gateway/config-fields.tsx +++ b/ui/src/adapters/openclaw-gateway/config-fields.tsx @@ -150,6 +150,16 @@ export function OpenClawGatewayConfigFields({ /> + + mark("adapterConfig", "claimedApiKeyPath", v || undefined)} + immediate + className={inputClass} + placeholder="~/.openclaw/workspace/paperclip-claimed-api-key.json" + /> + + { + setFilter(e.target.value); + if (!open) setOpen(true); + }} + onFocus={() => { + if (!open) setOpen(true); + }} + onBlur={() => { + // Delay close to allow click on option to register + setTimeout(() => setOpen(false), 150); + }} + onKeyDown={handleKeyDown} + /> + 0} onOpenChange={setOpen}> + + + + e.preventDefault()} + > + {Array.from(grouped.entries()).map(([group, opts]) => ( +
+ {group && ( +
+ {group} +
+ )} + {opts.map((opt) => ( + + ))} +
+ ))} + {filter && filtered.length === 0 && ( +
+ Use "{filter}" as custom value (press Enter) +
+ )} +
+
+ + + ); +} + +// --------------------------------------------------------------------------- +// SchemaConfigFields component +// --------------------------------------------------------------------------- + +const schemaCache = new Map(); +const schemaFetchInflight = new Map>(); +const failedSchemaTypes = new Set(); + +async function fetchConfigSchema(adapterType: string): Promise { + const cached = schemaCache.get(adapterType); + if (cached !== undefined) return cached; + if (failedSchemaTypes.has(adapterType)) return null; + + const inflight = schemaFetchInflight.get(adapterType); + if (inflight) return inflight; + + const promise = (async () => { + try { + const res = await fetch(`/api/adapters/${encodeURIComponent(adapterType)}/config-schema`); + if (!res.ok) { + failedSchemaTypes.add(adapterType); + return null; + } + const schema = (await res.json()) as AdapterConfigSchema; + schemaCache.set(adapterType, schema); + return schema; + } catch { + failedSchemaTypes.add(adapterType); + return null; + } finally { + schemaFetchInflight.delete(adapterType); + } + })(); + + schemaFetchInflight.set(adapterType, promise); + return promise; +} + +export function invalidateConfigSchemaCache(adapterType: string): void { + schemaCache.delete(adapterType); + failedSchemaTypes.delete(adapterType); +} + +// --------------------------------------------------------------------------- +// Hook +// --------------------------------------------------------------------------- + +function useConfigSchema(adapterType: string): AdapterConfigSchema | null { + const [schema, setSchema] = useState( + schemaCache.get(adapterType) ?? null, + ); + + useEffect(() => { + let cancelled = false; + fetchConfigSchema(adapterType).then((s) => { + if (!cancelled) setSchema(s); + }); + return () => { + cancelled = true; + }; + }, [adapterType]); + + return schema; +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +function getDefaultValue(field: ConfigFieldSchema): unknown { + if (field.default !== undefined) return field.default; + switch (field.type) { + case "toggle": + return false; + case "number": + return 0; + case "text": + case "textarea": + return ""; + case "select": + return field.options?.[0]?.value ?? ""; + } +} + +// --------------------------------------------------------------------------- +// Component +// --------------------------------------------------------------------------- + +export function SchemaConfigFields({ + adapterType, + isCreate, + values, + set, + config, + eff, + mark, +}: AdapterConfigFieldsProps) { + const schema = useConfigSchema(adapterType); + + const [defaultsApplied, setDefaultsApplied] = useState(false); + useEffect(() => { + if (!schema || !isCreate || defaultsApplied) return; + const defaults: Record = {}; + for (const field of schema.fields) { + const def = getDefaultValue(field); + if (def !== undefined && def !== "") { + defaults[field.key] = def; + } + } + if (Object.keys(defaults).length > 0) { + set?.({ + adapterSchemaValues: { ...values?.adapterSchemaValues, ...defaults }, + }); + } + setDefaultsApplied(true); + }, [schema, isCreate, defaultsApplied, set, values?.adapterSchemaValues]); + + if (!schema || schema.fields.length === 0) return null; + + function readValue(field: ConfigFieldSchema): unknown { + if (isCreate) { + return values?.adapterSchemaValues?.[field.key] ?? getDefaultValue(field); + } + const stored = config[field.key]; + return eff("adapterConfig", field.key, (stored ?? getDefaultValue(field)) as string); + } + + function writeValue(field: ConfigFieldSchema, value: unknown): void { + if (isCreate) { + const next = { + adapterSchemaValues: { + ...values?.adapterSchemaValues, + [field.key]: value, + }, + }; + + // When provider changes, auto-clear model if it's not in the new provider's list + if (field.key === "provider" && schema) { + const modelField = schema.fields.find((f) => f.key === "model"); + if (modelField?.meta?.providerModels) { + const modelsByProvider = modelField.meta.providerModels as Record; + const providerModels = modelsByProvider[String(value)] ?? []; + const currentModel = values?.adapterSchemaValues?.model; + if (currentModel && String(value) !== "auto" && !providerModels.includes(String(currentModel))) { + next.adapterSchemaValues.model = ""; + } + } + } + + set?.(next); + } else { + mark("adapterConfig", field.key, value); + + // Same logic for edit mode + if (field.key === "provider" && schema) { + const modelField = schema.fields.find((f) => f.key === "model"); + if (modelField?.meta?.providerModels) { + const modelsByProvider = modelField.meta.providerModels as Record; + const providerModels = modelsByProvider[String(value)] ?? []; + const currentModel = eff("adapterConfig", "model", ""); + if (currentModel && String(value) !== "auto" && !providerModels.includes(String(currentModel))) { + mark("adapterConfig", "model", ""); + } + } + } + } + } + + return ( + <> + {schema.fields.map((field) => { + switch (field.type) { + case "select": { + const currentVal = String(readValue(field) ?? ""); + return ( + + writeValue(field, v)} + /> + + ); + } + + case "toggle": + return ( + writeValue(field, v)} + /> + ); + + case "number": + return ( + + writeValue(field, v)} + immediate + className={inputClass} + /> + + ); + + case "textarea": + return ( + + writeValue(field, v || undefined)} + immediate + /> + + ); + + case "combobox": { + const currentVal = String(readValue(field) ?? ""); + // Dynamic options: if meta.providerModels exists, compute options + // based on the current provider value + let comboboxOptions = field.options ?? []; + if (field.meta?.providerModels) { + const providerVal = String(readValue(schema.fields.find((f) => f.key === "provider")!) ?? "auto"); + const modelsByProvider = field.meta.providerModels as Record; + if (providerVal === "auto") { + // Auto: show all models from all providers, grouped by provider + const providerLabel = schema.fields.find((f) => f.key === "provider"); + const providerOptions = providerLabel?.options ?? []; + comboboxOptions = Object.entries(modelsByProvider).flatMap(([prov, models]) => + models.map((m) => ({ + label: m, + value: m, + group: providerOptions.find((p) => p.value === prov)?.label ?? prov, + })), + ); + } else { + const providerModels = modelsByProvider[providerVal] ?? []; + const providerLabel = schema.fields.find((f) => f.key === "provider"); + const provName = providerLabel?.options?.find((p) => p.value === providerVal)?.label ?? providerVal; + comboboxOptions = providerModels.map((m) => ({ + label: m, + value: m, + group: provName, + })); + } + } + return ( + + writeValue(field, v || undefined)} + placeholder={field.hint} + /> + + ); + } + + case "text": + default: + return ( + + writeValue(field, v || undefined)} + immediate + className={inputClass} + /> + + ); + } + })} + + ); +} + +// --------------------------------------------------------------------------- +// Build adapter config from schema values + standard CreateConfigValues fields +// --------------------------------------------------------------------------- + +export function buildSchemaAdapterConfig( + values: CreateConfigValues, +): Record { + const ac: Record = {}; + + if (values.model?.trim()) ac.model = values.model.trim(); + if (values.cwd) ac.cwd = values.cwd; + if (values.command) ac.command = values.command; + if (values.instructionsFilePath) ac.instructionsFilePath = values.instructionsFilePath; + if (values.thinkingEffort) ac.thinkingEffort = values.thinkingEffort; + + if (values.extraArgs) { + ac.extraArgs = values.extraArgs + .split(/\s+/) + .filter(Boolean); + } + + if (values.adapterSchemaValues) { + Object.assign(ac, values.adapterSchemaValues); + } + + return ac; +} diff --git a/ui/src/adapters/transcript.test.ts b/ui/src/adapters/transcript.test.ts index 8b56163e78d..c33c900836e 100644 --- a/ui/src/adapters/transcript.test.ts +++ b/ui/src/adapters/transcript.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "vitest"; import { buildTranscript, type RunLogChunk } from "./transcript"; +import type { UIAdapterModule } from "./types"; describe("buildTranscript", () => { const ts = "2026-03-20T13:00:00.000Z"; @@ -27,4 +28,46 @@ describe("buildTranscript", () => { { kind: "stderr", ts, text: "stderr /Users/d****/project" }, ]); }); + + it("creates a fresh stateful parser for each transcript build", () => { + const statefulAdapter: UIAdapterModule = { + type: "stateful_test", + label: "Stateful Test", + parseStdoutLine: (line, entryTs) => [{ kind: "stdout", ts: entryTs, text: line }], + createStdoutParser: () => { + let pending: string | null = null; + return { + parseLine: (line, entryTs) => { + if (line.startsWith("begin:")) { + pending = line.slice("begin:".length); + return []; + } + if (line === "finish" && pending) { + const text = `completed:${pending}`; + pending = null; + return [{ kind: "stdout", ts: entryTs, text }]; + } + return [{ kind: "stdout", ts: entryTs, text: `literal:${line}` }]; + }, + reset: () => { + pending = null; + }, + }; + }, + ConfigFields: () => null, + buildAdapterConfig: () => ({}), + }; + + const first = buildTranscript( + [{ ts, stream: "stdout", chunk: "begin:task-a\n" }], + statefulAdapter, + ); + const second = buildTranscript( + [{ ts, stream: "stdout", chunk: "finish\n" }], + statefulAdapter, + ); + + expect(first).toEqual([]); + expect(second).toEqual([{ kind: "stdout", ts, text: "literal:finish" }]); + }); }); diff --git a/ui/src/adapters/transcript.ts b/ui/src/adapters/transcript.ts index 98b19454558..307aa5ae44b 100644 --- a/ui/src/adapters/transcript.ts +++ b/ui/src/adapters/transcript.ts @@ -1,9 +1,20 @@ import { redactHomePathUserSegments, redactTranscriptEntryPaths } from "@paperclipai/adapter-utils"; -import type { TranscriptEntry, StdoutLineParser } from "./types"; +import type { TranscriptEntry, StdoutLineParser, TranscriptParserSource } from "./types"; export type RunLogChunk = { ts: string; stream: "stdout" | "stderr" | "system"; chunk: string }; type TranscriptBuildOptions = { censorUsernameInLogs?: boolean }; +function resolveStdoutParser(source: StdoutLineParser | TranscriptParserSource) { + if (typeof source === "function") { + return { parseLine: source, reset: null as (() => void) | null }; + } + if (source.createStdoutParser) { + const parser = source.createStdoutParser(); + return { parseLine: parser.parseLine, reset: parser.reset }; + } + return { parseLine: source.parseStdoutLine, reset: null as (() => void) | null }; +} + export function appendTranscriptEntry(entries: TranscriptEntry[], entry: TranscriptEntry) { if ((entry.kind === "thinking" || entry.kind === "assistant") && entry.delta) { const last = entries[entries.length - 1]; @@ -24,12 +35,13 @@ export function appendTranscriptEntries(entries: TranscriptEntry[], incoming: Tr export function buildTranscript( chunks: RunLogChunk[], - parser: StdoutLineParser, + parserSource: StdoutLineParser | TranscriptParserSource, opts?: TranscriptBuildOptions, ): TranscriptEntry[] { const entries: TranscriptEntry[] = []; let stdoutBuffer = ""; const redactionOptions = { enabled: opts?.censorUsernameInLogs ?? false }; + const { parseLine, reset } = resolveStdoutParser(parserSource); for (const chunk of chunks) { if (chunk.stream === "stderr") { @@ -47,15 +59,17 @@ export function buildTranscript( for (const line of lines) { const trimmed = line.trim(); if (!trimmed) continue; - appendTranscriptEntries(entries, parser(trimmed, chunk.ts).map((entry) => redactTranscriptEntryPaths(entry, redactionOptions))); + appendTranscriptEntries(entries, parseLine(trimmed, chunk.ts).map((entry) => redactTranscriptEntryPaths(entry, redactionOptions))); } } const trailing = stdoutBuffer.trim(); if (trailing) { const ts = chunks.length > 0 ? chunks[chunks.length - 1]!.ts : new Date().toISOString(); - appendTranscriptEntries(entries, parser(trailing, ts).map((entry) => redactTranscriptEntryPaths(entry, redactionOptions))); + appendTranscriptEntries(entries, parseLine(trailing, ts).map((entry) => redactTranscriptEntryPaths(entry, redactionOptions))); } + reset?.(); + return entries; } diff --git a/ui/src/adapters/types.ts b/ui/src/adapters/types.ts index 6a7ae48a32d..74bd48e5380 100644 --- a/ui/src/adapters/types.ts +++ b/ui/src/adapters/types.ts @@ -4,6 +4,18 @@ import type { CreateConfigValues } from "@paperclipai/adapter-utils"; // Re-export shared types so local consumers don't need to change imports export type { TranscriptEntry, StdoutLineParser, CreateConfigValues } from "@paperclipai/adapter-utils"; +export interface StatefulStdoutParser { + parseLine: (line: string, ts: string) => import("@paperclipai/adapter-utils").TranscriptEntry[]; + reset: () => void; +} + +export type StdoutParserFactory = () => StatefulStdoutParser; + +export interface TranscriptParserSource { + parseStdoutLine: (line: string, ts: string) => import("@paperclipai/adapter-utils").TranscriptEntry[]; + createStdoutParser?: StdoutParserFactory; +} + export interface AdapterConfigFieldsProps { mode: "create" | "edit"; isCreate: boolean; @@ -24,10 +36,9 @@ export interface AdapterConfigFieldsProps { hideInstructionsFile?: boolean; } -export interface UIAdapterModule { +export interface UIAdapterModule extends TranscriptParserSource { type: string; label: string; - parseStdoutLine: (line: string, ts: string) => import("@paperclipai/adapter-utils").TranscriptEntry[]; ConfigFields: ComponentType; buildAdapterConfig: (values: CreateConfigValues) => Record; } diff --git a/ui/src/adapters/use-disabled-adapters.ts b/ui/src/adapters/use-disabled-adapters.ts new file mode 100644 index 00000000000..ebc63946cda --- /dev/null +++ b/ui/src/adapters/use-disabled-adapters.ts @@ -0,0 +1,54 @@ +import { useEffect, useMemo } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { adaptersApi } from "@/api/adapters"; +import { setDisabledAdapterTypes } from "@/adapters/disabled-store"; +import { syncExternalAdapters } from "@/adapters/registry"; +import { queryKeys } from "@/lib/queryKeys"; + +/** + * Fetch adapters and keep the disabled-adapter store + UI adapter registry + * in sync with the server. + * + * - Registers external adapter types in the UI registry so they appear in + * dropdowns (done eagerly during render — idempotent, no React state). + * - Syncs the disabled-adapter store for non-React consumers (useEffect). + * + * Returns a reactive Set of disabled types for use as useMemo dependencies. + * Call this at the top of any component that renders adapter menus. + */ +export function useDisabledAdaptersSync(): Set { + const { data: adapters } = useQuery({ + queryKey: queryKeys.adapters.all, + queryFn: () => adaptersApi.list(), + staleTime: 5 * 60 * 1000, + }); + + // Eagerly register external adapter types in the UI registry so that + // consumers calling listUIAdapters() in the same render cycle see them. + // This is idempotent — already-registered types are skipped. + if (adapters) { + syncExternalAdapters( + adapters + .filter((a) => a.source === "external") + .map((a) => ({ + type: a.type, + label: a.label, + disabled: a.disabled, + overrideDisabled: a.overridePaused, + })), + ); + } + + // Sync the disabled set to the global store for non-React code + useEffect(() => { + if (!adapters) return; + setDisabledAdapterTypes( + adapters.filter((a) => a.disabled).map((a) => a.type), + ); + }, [adapters]); + + return useMemo( + () => new Set(adapters?.filter((a) => a.disabled).map((a) => a.type) ?? []), + [adapters], + ); +} diff --git a/ui/src/api/adapters.ts b/ui/src/api/adapters.ts new file mode 100644 index 00000000000..86705bd4e75 --- /dev/null +++ b/ui/src/api/adapters.ts @@ -0,0 +1,59 @@ +/** + * @fileoverview Frontend API client for external adapter management. + */ + +import { api } from "./client"; + +export interface AdapterInfo { + type: string; + label: string; + source: "builtin" | "external"; + modelsCount: number; + loaded: boolean; + disabled: boolean; + /** Installed version (for external npm adapters) */ + version?: string; + /** Package name (for external adapters) */ + packageName?: string; + /** Whether the adapter was installed from a local path (vs npm). */ + isLocalPath?: boolean; + /** True when an external plugin has replaced a built-in adapter of the same type. */ + overriddenBuiltin?: boolean; + /** True when the external override for a builtin type is currently paused. */ + overridePaused?: boolean; +} + +export interface AdapterInstallResult { + type: string; + packageName: string; + version?: string; + installedAt: string; +} + +export const adaptersApi = { + /** List all registered adapters (built-in + external). */ + list: () => api.get("/adapters"), + + /** Install an external adapter from npm or a local path. */ + install: (params: { packageName: string; version?: string; isLocalPath?: boolean }) => + api.post("/adapters/install", params), + + /** Remove an external adapter by type. */ + remove: (type: string) => api.delete<{ type: string; removed: boolean }>(`/adapters/${type}`), + + /** Enable or disable an adapter (disabled adapters hidden from agent menus). */ + setDisabled: (type: string, disabled: boolean) => + api.patch<{ type: string; disabled: boolean; changed: boolean }>(`/adapters/${type}`, { disabled }), + + /** Pause or resume an external override of a builtin type. */ + setOverridePaused: (type: string, paused: boolean) => + api.patch<{ type: string; paused: boolean; changed: boolean }>(`/adapters/${type}/override`, { paused }), + + /** Reload an external adapter (bust server + client caches). */ + reload: (type: string) => + api.post<{ type: string; version?: string; reloaded: boolean }>(`/adapters/${type}/reload`, {}), + + /** Reinstall an npm-sourced adapter (pulls latest from registry, then reloads). */ + reinstall: (type: string) => + api.post<{ type: string; version?: string; reinstalled: boolean }>(`/adapters/${type}/reinstall`, {}), +}; diff --git a/ui/src/api/agents.ts b/ui/src/api/agents.ts index ec090b43103..fcd38604209 100644 --- a/ui/src/api/agents.ts +++ b/ui/src/api/agents.ts @@ -8,6 +8,7 @@ import type { AgentKeyCreated, AgentRuntimeState, AgentTaskSession, + AgentWakeupResponse, HeartbeatRun, Approval, AgentConfigRevision, @@ -31,6 +32,7 @@ export interface DetectedAdapterModel { model: string; provider: string; source: string; + candidates?: string[]; } export interface ClaudeLoginResult { @@ -189,7 +191,7 @@ export const agentsApi = { idempotencyKey?: string | null; }, companyId?: string, - ) => api.post(agentPath(id, companyId, "/wakeup"), data), + ) => api.post(agentPath(id, companyId, "/wakeup"), data), loginWithClaude: (id: string, companyId?: string) => api.post(agentPath(id, companyId, "/claude-login"), {}), availableSkills: () => diff --git a/ui/src/api/companies.ts b/ui/src/api/companies.ts index 82d2e54e8ba..9529e8976b3 100644 --- a/ui/src/api/companies.ts +++ b/ui/src/api/companies.ts @@ -28,7 +28,14 @@ export const companiesApi = { data: Partial< Pick< Company, - "name" | "description" | "status" | "budgetMonthlyCents" | "requireBoardApprovalForNewAgents" | "brandColor" | "logoAssetId" + | "name" + | "description" + | "status" + | "budgetMonthlyCents" + | "requireBoardApprovalForNewAgents" + | "feedbackDataSharingEnabled" + | "brandColor" + | "logoAssetId" > >, ) => api.patch(`/companies/${companyId}`, data), diff --git a/ui/src/api/companySkills.ts b/ui/src/api/companySkills.ts index adbc21171c6..7377b2fa509 100644 --- a/ui/src/api/companySkills.ts +++ b/ui/src/api/companySkills.ts @@ -51,4 +51,8 @@ export const companySkillsApi = { `/companies/${encodeURIComponent(companyId)}/skills/${encodeURIComponent(skillId)}/install-update`, {}, ), + delete: (companyId: string, skillId: string) => + api.delete( + `/companies/${encodeURIComponent(companyId)}/skills/${encodeURIComponent(skillId)}`, + ), }; diff --git a/ui/src/api/inboxDismissals.ts b/ui/src/api/inboxDismissals.ts new file mode 100644 index 00000000000..f80d3aef966 --- /dev/null +++ b/ui/src/api/inboxDismissals.ts @@ -0,0 +1,8 @@ +import type { InboxDismissal } from "@paperclipai/shared"; +import { api } from "./client"; + +export const inboxDismissalsApi = { + list: (companyId: string) => api.get(`/companies/${companyId}/inbox-dismissals`), + dismiss: (companyId: string, itemKey: string) => + api.post(`/companies/${companyId}/inbox-dismissals`, { itemKey }), +}; diff --git a/ui/src/api/index.ts b/ui/src/api/index.ts index 84b58cda409..13c72f1f99b 100644 --- a/ui/src/api/index.ts +++ b/ui/src/api/index.ts @@ -15,4 +15,5 @@ export { dashboardApi } from "./dashboard"; export { heartbeatsApi } from "./heartbeats"; export { instanceSettingsApi } from "./instanceSettings"; export { sidebarBadgesApi } from "./sidebarBadges"; +export { inboxDismissalsApi } from "./inboxDismissals"; export { companySkillsApi } from "./companySkills"; diff --git a/ui/src/api/issues.test.ts b/ui/src/api/issues.test.ts new file mode 100644 index 00000000000..d0b3fab06f5 --- /dev/null +++ b/ui/src/api/issues.test.ts @@ -0,0 +1,26 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const mockApi = vi.hoisted(() => ({ + get: vi.fn(), +})); + +vi.mock("./client", () => ({ + api: mockApi, +})); + +import { issuesApi } from "./issues"; + +describe("issuesApi.list", () => { + beforeEach(() => { + mockApi.get.mockReset(); + mockApi.get.mockResolvedValue([]); + }); + + it("passes parentId through to the company issues endpoint", async () => { + await issuesApi.list("company-1", { parentId: "issue-parent-1", limit: 25 }); + + expect(mockApi.get).toHaveBeenCalledWith( + "/companies/company-1/issues?parentId=issue-parent-1&limit=25", + ); + }); +}); diff --git a/ui/src/api/issues.ts b/ui/src/api/issues.ts index 8d4834c3beb..bd604af9cdf 100644 --- a/ui/src/api/issues.ts +++ b/ui/src/api/issues.ts @@ -1,6 +1,9 @@ import type { Approval, DocumentRevision, + FeedbackTargetType, + FeedbackTrace, + FeedbackVote, Issue, IssueAttachment, IssueComment, @@ -21,6 +24,7 @@ export const issuesApi = { filters?: { status?: string; projectId?: string; + parentId?: string; assigneeAgentId?: string; participantAgentId?: string; assigneeUserId?: string; @@ -33,11 +37,13 @@ export const issuesApi = { originId?: string; includeRoutineExecutions?: boolean; q?: string; + limit?: number; }, ) => { const params = new URLSearchParams(); if (filters?.status) params.set("status", filters.status); if (filters?.projectId) params.set("projectId", filters.projectId); + if (filters?.parentId) params.set("parentId", filters.parentId); if (filters?.assigneeAgentId) params.set("assigneeAgentId", filters.assigneeAgentId); if (filters?.participantAgentId) params.set("participantAgentId", filters.participantAgentId); if (filters?.assigneeUserId) params.set("assigneeUserId", filters.assigneeUserId); @@ -50,6 +56,7 @@ export const issuesApi = { if (filters?.originId) params.set("originId", filters.originId); if (filters?.includeRoutineExecutions) params.set("includeRoutineExecutions", "true"); if (filters?.q) params.set("q", filters.q); + if (filters?.limit) params.set("limit", String(filters.limit)); const qs = params.toString(); return api.get(`/companies/${companyId}/issues${qs ? `?${qs}` : ""}`); }, @@ -72,10 +79,44 @@ export const issuesApi = { checkout: (id: string, agentId: string) => api.post(`/issues/${id}/checkout`, { agentId, - expectedStatuses: ["todo", "backlog", "blocked"], + expectedStatuses: ["todo", "backlog", "blocked", "in_review"], }), release: (id: string) => api.post(`/issues/${id}/release`, {}), - listComments: (id: string) => api.get(`/issues/${id}/comments`), + listComments: ( + id: string, + filters?: { + after?: string; + order?: "asc" | "desc"; + limit?: number; + }, + ) => { + const params = new URLSearchParams(); + if (filters?.after) params.set("after", filters.after); + if (filters?.order) params.set("order", filters.order); + if (filters?.limit) params.set("limit", String(filters.limit)); + const qs = params.toString(); + return api.get(`/issues/${id}/comments${qs ? `?${qs}` : ""}`); + }, + listFeedbackVotes: (id: string) => api.get(`/issues/${id}/feedback-votes`), + listFeedbackTraces: (id: string, filters?: Record) => { + const params = new URLSearchParams(); + for (const [key, value] of Object.entries(filters ?? {})) { + if (value === undefined) continue; + params.set(key, String(value)); + } + const qs = params.toString(); + return api.get(`/issues/${id}/feedback-traces${qs ? `?${qs}` : ""}`); + }, + upsertFeedbackVote: ( + id: string, + data: { + targetType: FeedbackTargetType; + targetId: string; + vote: "up" | "down"; + reason?: string; + allowSharing?: boolean; + }, + ) => api.post(`/issues/${id}/feedback-votes`, data), addComment: (id: string, body: string, reopen?: boolean, interrupt?: boolean) => api.post( `/issues/${id}/comments`, @@ -91,6 +132,8 @@ export const issuesApi = { api.put(`/issues/${id}/documents/${encodeURIComponent(key)}`, data), listDocumentRevisions: (id: string, key: string) => api.get(`/issues/${id}/documents/${encodeURIComponent(key)}/revisions`), + restoreDocumentRevision: (id: string, key: string, revisionId: string) => + api.post(`/issues/${id}/documents/${encodeURIComponent(key)}/revisions/${revisionId}/restore`, {}), deleteDocument: (id: string, key: string) => api.delete<{ ok: true }>(`/issues/${id}/documents/${encodeURIComponent(key)}`), listAttachments: (id: string) => api.get(`/issues/${id}/attachments`), diff --git a/ui/src/components/ActiveAgentsPanel.tsx b/ui/src/components/ActiveAgentsPanel.tsx index c0883192148..26c969e3c14 100644 --- a/ui/src/components/ActiveAgentsPanel.tsx +++ b/ui/src/components/ActiveAgentsPanel.tsx @@ -3,13 +3,13 @@ import { Link } from "@/lib/router"; import { useQuery } from "@tanstack/react-query"; import type { Issue } from "@paperclipai/shared"; import { heartbeatsApi, type LiveRunForIssue } from "../api/heartbeats"; -import { issuesApi } from "../api/issues"; import type { TranscriptEntry } from "../adapters"; +import { issuesApi } from "../api/issues"; import { queryKeys } from "../lib/queryKeys"; import { cn, relativeTime } from "../lib/utils"; import { ExternalLink } from "lucide-react"; import { Identity } from "./Identity"; -import { RunTranscriptView } from "./transcript/RunTranscriptView"; +import { RunChatSurface } from "./RunChatSurface"; import { useLiveRunTranscripts } from "./transcript/useLiveRunTranscripts"; const MIN_DASHBOARD_RUNS = 4; @@ -63,6 +63,7 @@ export function ActiveAgentsPanel({ companyId }: ActiveAgentsPanelProps) { {runs.map((run) => (
-
diff --git a/ui/src/components/ActivityRow.tsx b/ui/src/components/ActivityRow.tsx index ebfe23c5211..dbe88785275 100644 --- a/ui/src/components/ActivityRow.tsx +++ b/ui/src/components/ActivityRow.tsx @@ -2,72 +2,9 @@ import { Link } from "@/lib/router"; import { Identity } from "./Identity"; import { timeAgo } from "../lib/timeAgo"; import { cn } from "../lib/utils"; +import { formatActivityVerb } from "../lib/activity-format"; import { deriveProjectUrlKey, type ActivityEvent, type Agent } from "@paperclipai/shared"; -const ACTION_VERBS: Record = { - "issue.created": "created", - "issue.updated": "updated", - "issue.checked_out": "checked out", - "issue.released": "released", - "issue.comment_added": "commented on", - "issue.attachment_added": "attached file to", - "issue.attachment_removed": "removed attachment from", - "issue.document_created": "created document for", - "issue.document_updated": "updated document on", - "issue.document_deleted": "deleted document from", - "issue.commented": "commented on", - "issue.deleted": "deleted", - "agent.created": "created", - "agent.updated": "updated", - "agent.paused": "paused", - "agent.resumed": "resumed", - "agent.terminated": "terminated", - "agent.key_created": "created API key for", - "agent.budget_updated": "updated budget for", - "agent.runtime_session_reset": "reset session for", - "heartbeat.invoked": "invoked heartbeat for", - "heartbeat.cancelled": "cancelled heartbeat for", - "approval.created": "requested approval", - "approval.approved": "approved", - "approval.rejected": "rejected", - "project.created": "created", - "project.updated": "updated", - "project.deleted": "deleted", - "goal.created": "created", - "goal.updated": "updated", - "goal.deleted": "deleted", - "cost.reported": "reported cost for", - "cost.recorded": "recorded cost for", - "company.created": "created company", - "company.updated": "updated company", - "company.archived": "archived", - "company.budget_updated": "updated budget for", -}; - -function humanizeValue(value: unknown): string { - if (typeof value !== "string") return String(value ?? "none"); - return value.replace(/_/g, " "); -} - -function formatVerb(action: string, details?: Record | null): string { - if (action === "issue.updated" && details) { - const previous = (details._previous ?? {}) as Record; - if (details.status !== undefined) { - const from = previous.status; - return from - ? `changed status from ${humanizeValue(from)} to ${humanizeValue(details.status)} on` - : `changed status to ${humanizeValue(details.status)} on`; - } - if (details.priority !== undefined) { - const from = previous.priority; - return from - ? `changed priority from ${humanizeValue(from)} to ${humanizeValue(details.priority)} on` - : `changed priority to ${humanizeValue(details.priority)} on`; - } - } - return ACTION_VERBS[action] ?? action.replace(/[._]/g, " "); -} - function entityLink(entityType: string, entityId: string, name?: string | null): string | null { switch (entityType) { case "issue": return `/issues/${name ?? entityId}`; @@ -88,7 +25,7 @@ interface ActivityRowProps { } export function ActivityRow({ event, agentMap, entityNameMap, entityTitleMap, className }: ActivityRowProps) { - const verb = formatVerb(event.action, event.details); + const verb = formatActivityVerb(event.action, event.details, { agentMap }); const isHeartbeatEvent = event.entityType === "heartbeat_run"; const heartbeatAgentId = isHeartbeatEvent diff --git a/ui/src/components/AgentConfigForm.tsx b/ui/src/components/AgentConfigForm.tsx index b9781a05c80..dd74c37626a 100644 --- a/ui/src/components/AgentConfigForm.tsx +++ b/ui/src/components/AgentConfigForm.tsx @@ -1,6 +1,5 @@ import { useState, useEffect, useRef, useMemo, useCallback } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { AGENT_ADAPTER_TYPES } from "@paperclipai/shared"; import type { Agent, AdapterEnvironmentTestResult, @@ -45,7 +44,11 @@ import { MarkdownEditor } from "./MarkdownEditor"; import { ChoosePathButton } from "./PathInstructionsModal"; import { OpenCodeLogoIcon } from "./OpenCodeLogoIcon"; import { ReportsToPicker } from "./ReportsToPicker"; +import { EnvVarEditor } from "./EnvVarEditor"; import { shouldShowLegacyWorkingDirectoryField } from "../lib/legacy-agent-config"; +import { listAdapterOptions, listVisibleAdapterTypes } from "../adapters/metadata"; +import { getAdapterLabel } from "../adapters/adapter-display-registry"; +import { useDisabledAdaptersSync } from "../adapters/use-disabled-adapters"; /* ---- Create mode values ---- */ @@ -140,6 +143,7 @@ const codexThinkingEffortOptions = [ { id: "low", label: "Low" }, { id: "medium", label: "Medium" }, { id: "high", label: "High" }, + { id: "xhigh", label: "X-High" }, ] as const; const openCodeThinkingEffortOptions = [ @@ -148,6 +152,7 @@ const openCodeThinkingEffortOptions = [ { id: "low", label: "Low" }, { id: "medium", label: "Medium" }, { id: "high", label: "High" }, + { id: "xhigh", label: "X-High" }, { id: "max", label: "Max" }, ] as const; @@ -178,6 +183,9 @@ export function AgentConfigForm(props: AgentConfigFormProps) { const { selectedCompanyId } = useCompany(); const queryClient = useQueryClient(); + // Sync disabled adapter types from server so dropdown filters them out + const disabledTypes = useDisabledAdaptersSync(); + const { data: availableSecrets = [] } = useQuery({ queryKey: selectedCompanyId ? queryKeys.secrets.list(selectedCompanyId) : ["secrets", "none"], queryFn: () => secretsApi.list(selectedCompanyId!), @@ -309,15 +317,9 @@ export function AgentConfigForm(props: AgentConfigFormProps) { const adapterType = isCreate ? props.values.adapterType : overlay.adapterType ?? props.agent.adapterType; - const isLocal = - adapterType === "claude_local" || - adapterType === "codex_local" || - adapterType === "gemini_local" || - adapterType === "hermes_local" || - adapterType === "opencode_local" || - adapterType === "pi_local" || - adapterType === "cursor"; - const isHermesLocal = adapterType === "hermes_local"; + const NONLOCAL_TYPES = new Set(["process", "http", "openclaw_gateway"]); + const isLocal = !NONLOCAL_TYPES.has(adapterType); + const showLegacyWorkingDirectoryField = isLocal && shouldShowLegacyWorkingDirectoryField({ isCreate, adapterConfig: config }); const uiAdapter = useMemo(() => getUIAdapter(adapterType), [adapterType]); @@ -343,13 +345,14 @@ export function AgentConfigForm(props: AgentConfigFormProps) { : ["agents", "none", "detect-model", adapterType], queryFn: () => { if (!selectedCompanyId) { - throw new Error("Select a company to detect the Hermes model"); + throw new Error("Select a company to detect the model"); } return agentsApi.detectModel(selectedCompanyId, adapterType); }, - enabled: Boolean(selectedCompanyId && isHermesLocal), + enabled: Boolean(selectedCompanyId && isLocal), }); const detectedModel = detectedModelData?.model ?? null; + const detectedModelCandidates = detectedModelData?.candidates ?? []; const { data: companyAgents = [] } = useQuery({ queryKey: selectedCompanyId ? queryKeys.agents.list(selectedCompanyId) : ["agents", "none", "list"], @@ -515,7 +518,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
mark("identity", "capabilities", v || null)} placeholder="Describe what this agent can do..." contentClassName="min-h-[44px] text-sm font-mono" @@ -581,6 +584,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) { { if (isCreate) { // Reset all adapter-specific fields to defaults when switching adapter type @@ -690,8 +694,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) { )} - {/* Adapter-specific fields */} - + {/* Adapter-specific fields are rendered inside Permissions & Configuration */} @@ -714,24 +717,19 @@ export function AgentConfigForm(props: AgentConfigFormProps) { onCommit={(v) => isCreate ? set!({ command: v }) - : mark("adapterConfig", "command", v || undefined) + : mark("adapterConfig", "command", v || null) } immediate className={inputClass} placeholder={ - adapterType === "codex_local" - ? "codex" - : adapterType === "gemini_local" - ? "gemini" - : adapterType === "hermes_local" - ? "hermes" - : adapterType === "pi_local" - ? "pi" - : adapterType === "cursor" - ? "agent" - : adapterType === "opencode_local" - ? "opencode" - : "claude" + ({ + claude_local: "claude", + codex_local: "codex", + gemini_local: "gemini", + pi_local: "pi", + cursor: "agent", + opencode_local: "opencode", + } as Record)[adapterType] ?? adapterType.replace(/_local$/, "") } /> @@ -746,18 +744,18 @@ export function AgentConfigForm(props: AgentConfigFormProps) { } open={modelOpen} onOpenChange={setModelOpen} - allowDefault={adapterType !== "opencode_local" && adapterType !== "hermes_local"} - required={adapterType === "opencode_local" || adapterType === "hermes_local"} + allowDefault={adapterType !== "opencode_local"} + required={adapterType === "opencode_local"} groupByProvider={adapterType === "opencode_local"} - creatable={adapterType === "hermes_local"} - detectedModel={adapterType === "hermes_local" ? detectedModel : null} - onDetectModel={adapterType === "hermes_local" - ? async () => { - const result = await refetchDetectedModel(); - return result.data?.model ?? null; - } - : undefined} - detectModelLabel={adapterType === "hermes_local" ? "Detect from Hermes config" : undefined} + creatable + detectedModel={detectedModel} + detectedModelCandidates={[]} + onDetectModel={async () => { + const result = await refetchDetectedModel(); + return result.data?.model ?? null; + }} + detectModelLabel="Detect model" + emptyDetectHint="No model detected. Select or enter one manually." /> {fetchedModelsError && (

@@ -818,6 +816,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) { {adapterType === "claude_local" && ( )} + isCreate ? set!({ extraArgs: v }) - : mark("adapterConfig", "extraArgs", v ? parseCommaArgs(v) : undefined) + : mark("adapterConfig", "extraArgs", v?.trim() ? parseCommaArgs(v) : null) } immediate className={inputClass} @@ -924,14 +923,14 @@ export function AgentConfigForm(props: AgentConfigFormProps) { mark("heartbeat", "enabled", v)} number={eff("heartbeat", "intervalSec", Number(heartbeat.intervalSec ?? 300))} onNumberChange={(v) => mark("heartbeat", "intervalSec", v)} numberLabel="sec" numberPrefix="Run heartbeat every" numberHint={help.intervalSec} - showNumber={eff("heartbeat", "enabled", heartbeat.enabled !== false)} + showNumber={eff("heartbeat", "enabled", heartbeat.enabled === true)} /> ({ - value: t, - label: adapterLabels[t] ?? t, - comingSoon: !ENABLED_ADAPTER_TYPES.has(t), - })), -]; - function AdapterTypeDropdown({ value, onChange, + disabledTypes, }: { value: string; onChange: (type: string) => void; + disabledTypes: Set; }) { + const [open, setOpen] = useState(false); + const adapterList = useMemo( + () => + listAdapterOptions((type) => adapterLabels[type] ?? getAdapterLabel(type)).filter( + (item) => !disabledTypes.has(item.value), + ), + [disabledTypes], + ); + return ( - + - {ADAPTER_DISPLAY_LIST.map((item) => ( + {adapterList.map((item) => ( - - ) : ( - <> - updateRow(i, { plainValue: e.target.value })} - /> - - - )} - {!isTrailing ? ( - - ) : ( -

- )} -
- ); - })} - {sealError &&

{sealError}

} -

- PAPERCLIP_* variables are injected automatically at runtime. -

- - ); -} - function ModelDropdown({ models, value, @@ -1341,8 +1094,10 @@ function ModelDropdown({ groupByProvider, creatable, detectedModel, + detectedModelCandidates, onDetectModel, detectModelLabel, + emptyDetectHint, }: { models: AdapterModel[]; value: string; @@ -1354,8 +1109,10 @@ function ModelDropdown({ groupByProvider: boolean; creatable?: boolean; detectedModel?: string | null; + detectedModelCandidates?: string[]; onDetectModel?: () => Promise; detectModelLabel?: string; + emptyDetectHint?: string; }) { const [modelSearch, setModelSearch] = useState(""); const [detectingModel, setDetectingModel] = useState(false); @@ -1366,8 +1123,19 @@ function ModelDropdown({ manualModel && !models.some((m) => m.id.toLowerCase() === manualModel.toLowerCase()), ); + // Model IDs already shown as detected/candidate badges — exclude from regular list + const promotedModelIds = useMemo(() => { + const set = new Set(); + if (detectedModel) set.add(detectedModel); + for (const c of detectedModelCandidates ?? []) { + if (c) set.add(c); + } + return set; + }, [detectedModel, detectedModelCandidates]); + const filteredModels = useMemo(() => { return models.filter((m) => { + if (promotedModelIds.has(m.id)) return false; if (!modelSearch.trim()) return true; const q = modelSearch.toLowerCase(); const provider = extractProviderId(m.id) ?? ""; @@ -1377,7 +1145,7 @@ function ModelDropdown({ provider.toLowerCase().includes(q) ); }); - }, [models, modelSearch]); + }, [models, modelSearch, promotedModelIds]); const groupedModels = useMemo(() => { if (!groupByProvider) { return [ @@ -1458,7 +1226,7 @@ function ModelDropdown({ )} - {onDetectModel && !detectedModel && !modelSearch.trim() && ( + {onDetectModel && !modelSearch.trim() && ( )} - {value && !models.some((m) => m.id === value) && ( + {value && (!models.some((m) => m.id === value) || promotedModelIds.has(value)) && ( )} + {detectedModelCandidates + ?.filter((candidate) => candidate && candidate !== detectedModel && candidate !== value) + .map((candidate) => { + const entry = models.find((m) => m.id === candidate); + return ( + + ); + })}
{allowDefault && (
diff --git a/ui/src/components/ApprovalCard.tsx b/ui/src/components/ApprovalCard.tsx index 2123fc57227..7084f6b0efb 100644 --- a/ui/src/components/ApprovalCard.tsx +++ b/ui/src/components/ApprovalCard.tsx @@ -1,10 +1,18 @@ import { CheckCircle2, XCircle, Clock } from "lucide-react"; import { Link } from "@/lib/router"; -import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { Button, buttonVariants } from "@/components/ui/button"; import { Identity } from "./Identity"; -import { approvalLabel, typeIcon, defaultTypeIcon, ApprovalPayloadRenderer } from "./ApprovalPayload"; +import { + approvalSubject, + typeIcon, + defaultTypeIcon, + ApprovalPayloadRenderer, + typeLabel, +} from "./ApprovalPayload"; import { timeAgo } from "../lib/timeAgo"; import type { Approval, Agent } from "@paperclipai/shared"; +import { cn } from "@/lib/utils"; function statusIcon(status: string) { if (status === "approved") return ; @@ -21,86 +29,124 @@ export function ApprovalCard({ onReject, onOpen, detailLink, - isPending, + isPending = false, + pendingAction = null, }: { approval: Approval; requesterAgent: Agent | null; - onApprove: () => void; - onReject: () => void; + onApprove?: () => void; + onReject?: () => void; onOpen?: () => void; detailLink?: string; - isPending: boolean; + isPending?: boolean; + pendingAction?: "approve" | "reject" | null; }) { + const payload = approval.payload as Record | null; const Icon = typeIcon[approval.type] ?? defaultTypeIcon; - const label = approvalLabel(approval.type, approval.payload as Record | null); + const kindLabel = typeLabel[approval.type] ?? approval.type; + const subject = approvalSubject(payload); const showResolutionButtons = + Boolean(onApprove && onReject) && approval.type !== "budget_override_required" && (approval.status === "pending" || approval.status === "revision_requested"); + const hasFooter = showResolutionButtons || Boolean(detailLink || onOpen); return ( -
- {/* Header */} -
-
- -
- {label} - {requesterAgent && ( - - requested by - - )} +
+
+
+
+
+ +
+
+
+ + {kindLabel} + + {requesterAgent && ( +
+ Requested by + +
+ )} +
+
+

+ {subject ?? kindLabel} +

+

+ Approval request created {timeAgo(approval.createdAt)} +

+
+
-
- {statusIcon(approval.status)} - {approval.status} - · {timeAgo(approval.createdAt)} +
+
+ {statusIcon(approval.status)} + {approval.status.replace(/_/g, " ")} +
- {/* Payload */} - +
+ +
- {/* Decision note */} {approval.decisionNote && ( -
- Note: {approval.decisionNote} +
+ Decision note. {approval.decisionNote}
)} - {/* Actions */} - {showResolutionButtons && ( -
- - + {hasFooter ? ( +
+
+ {showResolutionButtons && ( + <> + + + + )} +
+ {(detailLink || onOpen) ? ( + detailLink ? ( + + View details + + ) : ( + + ) + ) : null}
- )} -
- {detailLink ? ( - - ) : ( - - )} -
+ ) : null}
); } diff --git a/ui/src/components/ApprovalPayload.test.tsx b/ui/src/components/ApprovalPayload.test.tsx new file mode 100644 index 00000000000..c11405e92da --- /dev/null +++ b/ui/src/components/ApprovalPayload.test.tsx @@ -0,0 +1,88 @@ +// @vitest-environment jsdom + +import { act } from "react"; +import { createRoot } from "react-dom/client"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { ApprovalPayloadRenderer, approvalLabel } from "./ApprovalPayload"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true; + +describe("approvalLabel", () => { + it("uses payload titles for generic board approvals", () => { + expect( + approvalLabel("request_board_approval", { + title: "Reply with an ASCII frog", + }), + ).toBe("Board Approval: Reply with an ASCII frog"); + }); +}); + +describe("ApprovalPayloadRenderer", () => { + let container: HTMLDivElement; + + beforeEach(() => { + container = document.createElement("div"); + document.body.appendChild(container); + }); + + afterEach(() => { + container.remove(); + }); + + it("renders request_board_approval payload fields without falling back to raw JSON", () => { + const root = createRoot(container); + + act(() => { + root.render( + , + ); + }); + + expect(container.textContent).toContain("Reply with an ASCII frog"); + expect(container.textContent).toContain("Board asked for approval before posting the frog."); + expect(container.textContent).toContain("Approve the frog reply."); + expect(container.textContent).toContain("Post the frog comment on the issue."); + expect(container.textContent).toContain("The frog might be too powerful."); + expect(container.textContent).toContain("(o)<"); + expect(container.textContent).not.toContain("\"recommendedAction\""); + + act(() => { + root.unmount(); + }); + }); + + it("can hide the repeated title when the card header already shows it", () => { + const root = createRoot(container); + + act(() => { + root.render( + , + ); + }); + + expect(container.textContent).toContain("Board asked for approval before posting the frog."); + expect(container.textContent).not.toContain("TitleReply with an ASCII frog"); + + act(() => { + root.unmount(); + }); + }); +}); diff --git a/ui/src/components/ApprovalPayload.tsx b/ui/src/components/ApprovalPayload.tsx index 83b55c73d9e..6a1fe2597b3 100644 --- a/ui/src/components/ApprovalPayload.tsx +++ b/ui/src/components/ApprovalPayload.tsx @@ -5,13 +5,33 @@ export const typeLabel: Record = { hire_agent: "Hire Agent", approve_ceo_strategy: "CEO Strategy", budget_override_required: "Budget Override", + request_board_approval: "Board Approval", }; +function firstNonEmptyString(...values: unknown[]): string | null { + for (const value of values) { + if (typeof value === "string" && value.trim().length > 0) { + return value.trim(); + } + } + return null; +} + +export function approvalSubject(payload?: Record | null): string | null { + return firstNonEmptyString( + payload?.title, + payload?.name, + payload?.summary, + payload?.recommendedAction, + ); +} + /** Build a contextual label for an approval, e.g. "Hire Agent: Designer" */ export function approvalLabel(type: string, payload?: Record | null): string { const base = typeLabel[type] ?? type; - if (type === "hire_agent" && payload?.name) { - return `${base}: ${String(payload.name)}`; + const subject = approvalSubject(payload); + if (subject) { + return `${base}: ${subject}`; } return base; } @@ -20,6 +40,7 @@ export const typeIcon: Record = { hire_agent: UserPlus, approve_ceo_strategy: Lightbulb, budget_override_required: ShieldAlert, + request_board_approval: ShieldCheck, }; export const defaultTypeIcon = ShieldCheck; @@ -127,8 +148,100 @@ export function BudgetOverridePayload({ payload }: { payload: Record }) { +export function BoardApprovalPayload({ + payload, + hideTitle = false, +}: { + payload: Record; + hideTitle?: boolean; +}) { + const nextPayload = hideTitle ? { ...payload, title: undefined } : payload; + return ( + + ); +} + +function BoardApprovalPayloadContent({ payload }: { payload: Record }) { + const risks = Array.isArray(payload.risks) + ? payload.risks + .filter((value): value is string => typeof value === "string") + .map((value) => value.trim()) + .filter(Boolean) + : []; + const title = firstNonEmptyString(payload.title); + const summary = firstNonEmptyString(payload.summary); + const recommendedAction = firstNonEmptyString(payload.recommendedAction); + const nextActionOnApproval = firstNonEmptyString(payload.nextActionOnApproval); + const proposedComment = firstNonEmptyString(payload.proposedComment); + + return ( +
+ {title && ( +
+

Title

+

{title}

+
+ )} + {summary && ( +
+

Summary

+

{summary}

+
+ )} + {recommendedAction && ( +
+

+ Recommended action +

+

{recommendedAction}

+
+ )} + {nextActionOnApproval && ( +
+

On approval

+

{nextActionOnApproval}

+
+ )} + {risks.length > 0 && ( +
+

Risks

+
    + {risks.map((risk) => ( +
  • + + {risk} +
  • + ))} +
+
+ )} + {proposedComment && ( +
+

+ Proposed comment +

+
+            {proposedComment}
+          
+
+ )} +
+ ); +} + +export function ApprovalPayloadRenderer({ + type, + payload, + hidePrimaryTitle = false, +}: { + type: string; + payload: Record; + hidePrimaryTitle?: boolean; +}) { if (type === "hire_agent") return ; if (type === "budget_override_required") return ; + if (type === "request_board_approval") { + return ; + } return ; } diff --git a/ui/src/components/CommandPalette.tsx b/ui/src/components/CommandPalette.tsx index 9d84be52b0c..82987bf6547 100644 --- a/ui/src/components/CommandPalette.tsx +++ b/ui/src/components/CommandPalette.tsx @@ -60,12 +60,12 @@ export function CommandPalette() { const { data: issues = [] } = useQuery({ queryKey: queryKeys.issues.list(selectedCompanyId!), queryFn: () => issuesApi.list(selectedCompanyId!), - enabled: !!selectedCompanyId && open, + enabled: !!selectedCompanyId && open && searchQuery.length === 0, }); const { data: searchedIssues = [] } = useQuery({ - queryKey: queryKeys.issues.search(selectedCompanyId!, searchQuery), - queryFn: () => issuesApi.list(selectedCompanyId!, { q: searchQuery }), + queryKey: queryKeys.issues.search(selectedCompanyId!, searchQuery, undefined, 10), + queryFn: () => issuesApi.list(selectedCompanyId!, { q: searchQuery, limit: 10 }), enabled: !!selectedCompanyId && open && searchQuery.length > 0, }); diff --git a/ui/src/components/CommentThread.test.tsx b/ui/src/components/CommentThread.test.tsx new file mode 100644 index 00000000000..0fa73210108 --- /dev/null +++ b/ui/src/components/CommentThread.test.tsx @@ -0,0 +1,306 @@ +// @vitest-environment jsdom + +import { act } from "react"; +import type { ReactNode } from "react"; +import { createRoot } from "react-dom/client"; +import { MemoryRouter } from "react-router-dom"; +import type { Agent, Approval } from "@paperclipai/shared"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { CommentThread } from "./CommentThread"; + +vi.mock("./MarkdownBody", () => ({ + MarkdownBody: ({ children, className }: { children: ReactNode; className?: string }) => ( +
{children}
+ ), +})); + +vi.mock("./MarkdownEditor", () => ({ + MarkdownEditor: ({ value, onChange, placeholder }: { + value: string; + onChange: (value: string) => void; + placeholder?: string; + }) => ( +