mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-13 07:12:00 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Task messages contain GitHub pull request links. > - A hover loads PR status into a shared memory cache. > - A page reload clears that memory and returns each link to an unknown state. > - The browser already saves selected reads for each user and company. > - This pull request adds learned link previews to that cache. > - Reloads show the saved status. Only user interest can refresh stale previews. ## Linked Issues or Issue Description **What happened?** After a user loaded PR status, a page reload removed the learned state. The browser persistence rules excluded link-preview queries. Related implementation: #20593. Searches of open and closed PRs found no duplicate reload-cache fix. **Expected behavior** Keep learned PR status across reloads for the same account and company. Do not load every PR on page render. Reuse fresh results and refresh stale results on hover or keyboard focus. **Steps to reproduce** 1. Open a task with repeated GitHub PR links. 2. Hover one link and wait for its status. 3. Reload the page. 4. Before this fix, both links return to unknown until another hover. **Paperclip version or commit** Reproduced against master source at `30403781b9418355c78dc0ed978366af77036e76`. ## What Changed - Add link-preview queries to the existing user and company persistence rules. - Keep at most 100 previews across accessible companies. Use the existing 24-hour retention, build boundary, and sign-out cleanup. - Keep the original read time and freshness when restoring previews. Other query types keep their existing restore behavior. - Add regressions for saved status, negative results, bounds, age, account changes, membership loss, and sign-out. - Add a production-component Storybook example and browser reload checks. Update the cache documentation. - Use one query-key definition for the component, persistence rules, and cache tests. The browser read-cache module owns persistence and account cleanup. This change extends that owner. The link service and provider API contracts stay the same. ## Verification - Red: the new reload and retention tests failed because a fresh client had no saved PR data. - Green: 102 focused UI tests passed across the cache, sign-out, account-query, and query-client suites. - UI typecheck passed. - Storybook catalog generation and the static Storybook build passed. - All 21 browser checks passed with retries disabled. They prove status and negative-cache restoration after real reloads, no eager requests, no fresh-hover refetch, and one stale-interest refresh shared by duplicate links. Existing light/dark and narrow PR states, keyboard access, exact URLs, and card positioning also pass. - The first browser attempt could not launch because this session lacked Chromium. Chromium was installed before the complete passing run; that first attempt supplies no test verdict. - `pnpm verify --explain` returned exit 2. The browser test path requires broad CI coverage. No full local suite is claimed. - The supplied staging page was not accessible through the web tool. No deployed acceptance is claimed. - CI on the first head failed in a Slack long-publication timeout and a font-loading-frame browser check. Both files are outside this change. The two publication cases passed when run locally in isolation. The font case also passed locally against the built UI. [Final-head CI](https://github.com/paperclipai/paperclip/actions/runs/42909433167) passes on `e19d5b8bb1a910b1cbb9bb9a62ab87cdf684737f`. Typecheck, build, tests, E2E, and Rust checks pass. There are 58 successful check runs, three expected skips, and one neutral Paperclip Review check. The legacy Snyk status passes. Both Greptile and the supplemental Paperclip review report 5/5 on the final head. No actionable findings or unresolved review threads remain. The Paperclip Review check itself has a neutral conclusion. ## Risks - PR display data now remains in browser local storage until expiry, build change, or sign-out. Each query requires the matching user and an accessible company. - A stale saved status can remain visible for up to 24 hours. Hover or keyboard focus refreshes it after the provider freshness window. - Storage restrictions retain the existing memory-only fallback. No schema migration is required. ## Model Used OpenAI Codex, based on GPT-6, with reasoning, repository tools, code execution, and browser automation. The runtime does not expose the exact serving model ID or context-window size. No subagents were used. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
750 lines
33 KiB
TypeScript
750 lines
33 KiB
TypeScript
export const queryKeys = {
|
|
linkPreview: (companyId: string, userId: string | null, url: string) =>
|
|
["link-preview", companyId, userId, url] as const,
|
|
workspacePreferences: {
|
|
mine: (userId: string | null) => ["workspace-preferences", userId] as const,
|
|
},
|
|
agentCommentary: {
|
|
list: (companyId: string, filters: object) => ["agent-commentary", companyId, "list", filters] as const,
|
|
detail: (companyId: string, id: string | null) => ["agent-commentary", companyId, "detail", id] as const,
|
|
},
|
|
skillSources: {
|
|
projects: (companyId: string) => ['project-skill-repositories', companyId] as const,
|
|
preview: (companyId: string, repositoryUrl: string, connectionId: string | null, commitSha: string | null, skillPath: string, filePath: string) => ['skill-sources', companyId, 'preview', repositoryUrl, connectionId, commitSha, skillPath, filePath] as const,
|
|
all: (companyId: string) => ["skill-sources", companyId] as const,
|
|
repositories: (companyId: string) => ["skill-source-repositories", companyId] as const,
|
|
},
|
|
agentChats: {
|
|
list: (companyId: string | null, userId?: string | null) =>
|
|
["agent-chats", companyId, userId] as const,
|
|
detail: (companyId: string | null, userId: string | null, agentId: string | undefined) =>
|
|
["agent-chat", companyId, userId, agentId] as const,
|
|
},
|
|
companies: {
|
|
/**
|
|
* Prefix for everything company-shaped. Matches the list, details and stats
|
|
* below, so an `invalidateQueries` against it still reaches all of them.
|
|
* Not a cache entry of its own — the list lives under `list()`.
|
|
*/
|
|
all: ["companies"] as const,
|
|
/**
|
|
* The company list, scoped to the account it was fetched for. Which
|
|
* companies you belong to is an answer about *you*, and a single shared
|
|
* entry made it look like an answer about the app: for 30 seconds after an
|
|
* account change the previous account's list was served to anyone who
|
|
* asked. A different account is a different entry, so there is nothing to
|
|
* mistake. `null` is the signed-out/local_trusted key.
|
|
*/
|
|
list: (userId: string | null) =>
|
|
["companies", "list", userId ?? "anonymous"] as const,
|
|
directory: (userId: string | null) =>
|
|
["companies", "directory", userId ?? "anonymous"] as const,
|
|
detail: (id: string) => ["companies", id] as const,
|
|
stats: ["companies", "stats"] as const,
|
|
exportFidelity: (companyId: string) =>
|
|
["companies", companyId, "export-fidelity"] as const,
|
|
},
|
|
apps: {
|
|
gallery: (companyId: string) => ["apps", companyId, "gallery"] as const,
|
|
attention: (companyId: string) => ["apps", companyId, "attention"] as const,
|
|
connectionReviewCounts: (companyId: string, connectionId: string) =>
|
|
["apps", companyId, "attention", "connection", connectionId, "review-counts"] as const,
|
|
},
|
|
chatEndpoints: {
|
|
list: (companyId: string) => ["chat-endpoints", companyId] as const,
|
|
detail: (endpointId: string) => ["chat-endpoints", "detail", endpointId] as const,
|
|
resources: (endpointId: string) => ["chat-endpoints", endpointId, "resources"] as const,
|
|
principals: (endpointId: string) => ["chat-endpoints", endpointId, "principals"] as const,
|
|
conversations: (endpointId: string) => ["chat-endpoints", endpointId, "conversations"] as const,
|
|
activity: (endpointId: string) => ["chat-endpoints", endpointId, "activity"] as const,
|
|
},
|
|
tools: {
|
|
aggregatorApps: (connectionId: string, userId: string | null) => ["tools", "aggregator-apps", connectionId, userId] as const,
|
|
composioApps: (connectionId: string) => ["tools", "composio-apps", connectionId] as const,
|
|
applications: (companyId: string) =>
|
|
["tools", companyId, "applications"] as const,
|
|
connections: (companyId: string) =>
|
|
["tools", companyId, "connections"] as const,
|
|
connection: (connectionId: string) =>
|
|
["tools", "connection", connectionId] as const,
|
|
connectionInstalls: (connectionId: string) =>
|
|
["tools", "connection", connectionId, "installs"] as const,
|
|
connectionGrants: (connectionId: string) =>
|
|
["tools", "connection", connectionId, "grants"] as const,
|
|
catalog: (connectionId: string) => ["tools", "connection", connectionId, "catalog"] as const,
|
|
connectionActivity: (connectionId: string) =>
|
|
["tools", "connection", connectionId, "activity"] as const,
|
|
testAgents: (connectionId: string) =>
|
|
["tools", "connection", connectionId, "test-agents"] as const,
|
|
testAgentAccesses: () => ["tools", "test-agent-access"] as const,
|
|
testAgentAccessesForConnection: (connectionId: string) =>
|
|
["tools", "test-agent-access", connectionId] as const,
|
|
testAgentAccess: (connectionId: string, agentId: string) =>
|
|
["tools", "test-agent-access", connectionId, agentId] as const,
|
|
testCallStatus: (connectionId: string, actionRequestId: string) =>
|
|
[
|
|
"tools",
|
|
"connection",
|
|
connectionId,
|
|
"test-calls",
|
|
actionRequestId,
|
|
] as const,
|
|
actionRequests: (companyId: string, status: string) =>
|
|
["tools", companyId, "action-requests", status] as const,
|
|
gateways: (companyId: string) => ["tools", "gateways", companyId] as const,
|
|
connectionProfileConfiguration: (companyId: string, connectionId: string) =>
|
|
["tools", companyId, "profiles", "connection-configuration", connectionId] as const,
|
|
profiles: (companyId: string) => ["tools", companyId, "profiles"] as const,
|
|
profileNewTools: (profileId: string) =>
|
|
["tools", "profiles", profileId, "new-tools"] as const,
|
|
effectiveProfilesForAgent: (companyId: string, agentId: string) =>
|
|
["tools", companyId, "profiles", "effective", "agent", agentId] as const,
|
|
stdioTemplates: (companyId: string) =>
|
|
["tools", companyId, "stdio-templates"] as const,
|
|
runtimeSlots: (companyId: string) =>
|
|
["tools", companyId, "runtime-slots"] as const,
|
|
runtimeHealth: (companyId: string) =>
|
|
["tools", companyId, "runtime-health"] as const,
|
|
runDecisions: (companyId: string, runId: string) =>
|
|
["tools", companyId, "runs", runId, "decisions"] as const,
|
|
liveRuntimeSlots: (companyId: string) =>
|
|
["tools", companyId, "runtime-slots", "live"] as const,
|
|
policies: (companyId: string) => ["tools", companyId, "policies"] as const,
|
|
trustRules: (companyId: string) =>
|
|
["tools", companyId, "trust-rules"] as const,
|
|
audit: (companyId: string, limit: number) =>
|
|
["tools", companyId, "audit", limit] as const,
|
|
activity: (
|
|
companyId: string,
|
|
filters: { gateway?: string; app?: string; agent?: string; outcome?: string; window?: string; search?: string },
|
|
) =>
|
|
[
|
|
"tools",
|
|
companyId,
|
|
"activity",
|
|
filters.gateway ?? "__all",
|
|
filters.app ?? "__all",
|
|
filters.agent ?? "__all",
|
|
filters.outcome ?? "__all",
|
|
filters.window ?? "24h",
|
|
filters.search ?? "",
|
|
] as const,
|
|
},
|
|
audit: {
|
|
runs: (companyId: string, agentId?: string | null) =>
|
|
["audit", companyId, "runs", agentId ?? "__all"] as const,
|
|
agentActions: (
|
|
companyId: string,
|
|
filters: {
|
|
actorScope?: "agents" | "all" | null;
|
|
agentId?: string | null;
|
|
responsibleUserId?: string | null;
|
|
runId?: string | null;
|
|
entityType?: string | null;
|
|
entityId?: string | null;
|
|
action?: string | null;
|
|
from?: string | null;
|
|
to?: string | null;
|
|
actorType?: string | null;
|
|
},
|
|
) =>
|
|
[
|
|
"audit",
|
|
companyId,
|
|
"agent-actions",
|
|
filters.actorScope ?? "agents",
|
|
filters.agentId ?? "__all",
|
|
filters.responsibleUserId ?? "__all",
|
|
filters.runId ?? "__all",
|
|
filters.entityType ?? "__all",
|
|
filters.entityId ?? "__all",
|
|
filters.action ?? "__all",
|
|
filters.actorType ?? "__all",
|
|
filters.from ?? "",
|
|
filters.to ?? "",
|
|
] as const,
|
|
},
|
|
smokeLab: {
|
|
services: (companyId: string) =>
|
|
["smoke-lab", companyId, "services"] as const,
|
|
runs: (companyId: string) => ["smoke-lab", companyId, "runs"] as const,
|
|
run: (companyId: string, runId: string) =>
|
|
["smoke-lab", companyId, "runs", runId] as const,
|
|
},
|
|
companySkills: {
|
|
list: (companyId: string) => ["company-skills", companyId] as const,
|
|
listRecent: (companyId: string) =>
|
|
["company-skills", companyId, "recent-updated"] as const,
|
|
detail: (companyId: string, skillId: string) =>
|
|
["company-skills", companyId, skillId] as const,
|
|
versions: (companyId: string, skillId: string) =>
|
|
["company-skills", companyId, skillId, "versions"] as const,
|
|
comments: (companyId: string, skillId: string) =>
|
|
["company-skills", companyId, skillId, "comments"] as const,
|
|
updateStatus: (companyId: string, skillId: string) =>
|
|
["company-skills", companyId, skillId, "update-status"] as const,
|
|
forkPrecheck: (companyId: string, skillId: string) =>
|
|
["company-skills", companyId, skillId, "fork-precheck"] as const,
|
|
file: (companyId: string, skillId: string, relativePath: string) =>
|
|
["company-skills", companyId, skillId, "file", relativePath] as const,
|
|
catalog: (filters: { kind?: string; category?: string; q?: string } = {}) =>
|
|
[
|
|
"company-skills",
|
|
"catalog",
|
|
filters.kind ?? "__all-kinds__",
|
|
filters.category ?? "__all-categories__",
|
|
filters.q ?? "",
|
|
] as const,
|
|
catalogDetail: (catalogRef: string) =>
|
|
["company-skills", "catalog", "detail", catalogRef] as const,
|
|
catalogFile: (catalogRef: string, relativePath: string) =>
|
|
["company-skills", "catalog", "file", catalogRef, relativePath] as const,
|
|
testInputs: (companyId: string, skillId: string) =>
|
|
["company-skills", companyId, skillId, "test-inputs"] as const,
|
|
testRunTemplates: (companyId: string) =>
|
|
["company-skills", companyId, "test-run-templates"] as const,
|
|
testRuns: (companyId: string, skillId: string, inputId?: string | null) =>
|
|
[
|
|
"company-skills",
|
|
companyId,
|
|
skillId,
|
|
"test-runs",
|
|
inputId ?? "__all-inputs__",
|
|
] as const,
|
|
testRunDetail: (companyId: string, skillId: string, runId: string) =>
|
|
["company-skills", companyId, skillId, "test-run", runId] as const,
|
|
},
|
|
teamCatalog: {
|
|
catalog: (filters: { kind?: string; category?: string; q?: string } = {}) =>
|
|
[
|
|
"team-catalog",
|
|
"catalog",
|
|
filters.kind ?? "__all-kinds__",
|
|
filters.category ?? "__all-categories__",
|
|
filters.q ?? "",
|
|
] as const,
|
|
catalogDetail: (catalogRef: string) =>
|
|
["team-catalog", "catalog", "detail", catalogRef] as const,
|
|
catalogFile: (catalogRef: string, relativePath: string) =>
|
|
["team-catalog", "catalog", "file", catalogRef, relativePath] as const,
|
|
installed: (companyId: string) =>
|
|
["team-catalog", "installed", companyId] as const,
|
|
},
|
|
agents: {
|
|
projectAccess: (id: string) => ["agents", "detail", id, "project-access"] as const,
|
|
identity: (id: string) => ["agents", "identity", id] as const,
|
|
list: (companyId: string) => ["agents", companyId] as const,
|
|
detail: (id: string) => ["agents", "detail", id] as const,
|
|
runtimeState: (id: string) => ["agents", "runtime-state", id] as const,
|
|
taskSessions: (id: string) => ["agents", "task-sessions", id] as const,
|
|
skills: (id: string) => ["agents", "skills", id] as const,
|
|
instructionsBundle: (id: string) =>
|
|
["agents", "instructions-bundle", id] as const,
|
|
instructionCandidates: (id: string) =>
|
|
["agents", "instruction-candidates", id] as const,
|
|
instructionsFile: (id: string, relativePath: string) =>
|
|
["agents", "instructions-bundle", id, "file", relativePath] as const,
|
|
keys: (agentId: string) => ["agents", "keys", agentId] as const,
|
|
configRevisions: (agentId: string) =>
|
|
["agents", "config-revisions", agentId] as const,
|
|
adapterModels: (
|
|
companyId: string,
|
|
adapterType: string,
|
|
environmentId?: string | null,
|
|
provider?: string,
|
|
) =>
|
|
[
|
|
"agents",
|
|
companyId,
|
|
"adapter-models",
|
|
adapterType,
|
|
environmentId ?? null,
|
|
provider ?? null,
|
|
] as const,
|
|
detectModel: (companyId: string, adapterType: string) =>
|
|
["agents", companyId, "detect-model", adapterType] as const,
|
|
authSignal: (companyId: string, adapterType: string, environmentId?: string | null) =>
|
|
["agents", companyId, "auth-signal", adapterType, environmentId ?? null] as const,
|
|
activeLoginSession: (companyId: string, adapterType: string) =>
|
|
["agents", companyId, "active-login-session", adapterType] as const,
|
|
},
|
|
builtInAgents: {
|
|
list: (companyId: string) => ["built-in-agents", companyId] as const,
|
|
},
|
|
summarySlots: {
|
|
detail: (
|
|
companyId: string,
|
|
scopeKind: string,
|
|
slotKey: string,
|
|
scopeId?: string | null,
|
|
) =>
|
|
[
|
|
"summary-slots",
|
|
companyId,
|
|
scopeKind,
|
|
slotKey,
|
|
scopeId ?? null,
|
|
] as const,
|
|
revisions: (
|
|
companyId: string,
|
|
scopeKind: string,
|
|
slotKey: string,
|
|
scopeId?: string | null,
|
|
) =>
|
|
[
|
|
"summary-slots",
|
|
companyId,
|
|
scopeKind,
|
|
slotKey,
|
|
scopeId ?? null,
|
|
"revisions",
|
|
] as const,
|
|
},
|
|
statusCards: {
|
|
list: (companyId: string, archived: boolean) =>
|
|
["status-cards", companyId, archived ? "archived" : "active"] as const,
|
|
detail: (id: string) => ["status-cards", "detail", id] as const,
|
|
updates: (id: string) => ["status-cards", "detail", id, "updates"] as const,
|
|
summaryRevisions: (id: string) =>
|
|
["status-cards", "detail", id, "summary-revisions"] as const,
|
|
dryRun: (id: string) => ["status-cards", "detail", id, "dry-run"] as const,
|
|
},
|
|
issues: {
|
|
recoveryActions: (id: string, actionId: string | null, runId: string | null) =>
|
|
["issues", id, "recovery-actions", actionId, runId] as const,
|
|
allRecoveryActions: (id: string) => ["issues", id, "recovery-actions"] as const,
|
|
list: (companyId: string) => ["issues", companyId] as const,
|
|
onboardingFirstTask: (companyId: string) =>
|
|
["issues", companyId, "onboarding-first-task"] as const,
|
|
starPromptDone: (companyId: string) =>
|
|
["issues", companyId, "star-prompt-done"] as const,
|
|
mentionPool: (companyId: string) =>
|
|
["issues", companyId, "mention-pool"] as const,
|
|
search: (
|
|
companyId: string,
|
|
q: string,
|
|
projectId?: string,
|
|
limit?: number,
|
|
) =>
|
|
[
|
|
"issues",
|
|
companyId,
|
|
"search",
|
|
q,
|
|
projectId ?? "__all-projects__",
|
|
limit ?? "__no-limit__",
|
|
] as const,
|
|
listAssignedToMe: (companyId: string) =>
|
|
["issues", companyId, "assigned-to-me"] as const,
|
|
listMineByMe: (companyId: string) =>
|
|
["issues", companyId, "mine-by-me"] as const,
|
|
listTouchedByMe: (companyId: string) =>
|
|
["issues", companyId, "touched-by-me"] as const,
|
|
listUnreadTouchedByMe: (companyId: string) =>
|
|
["issues", companyId, "unread-touched-by-me"] as const,
|
|
listBlockedAttention: (companyId: string) =>
|
|
["issues", companyId, "blocked-attention"] as const,
|
|
countBlockedAttention: (companyId: string) =>
|
|
["issues", companyId, "blocked-attention", "count"] as const,
|
|
labels: (companyId: string) => ["issues", companyId, "labels"] as const,
|
|
listByProject: (companyId: string, projectId: string) =>
|
|
["issues", companyId, "project", projectId] as const,
|
|
listPluginOperationsByProject: (
|
|
companyId: string,
|
|
projectId: string,
|
|
originKindPrefix: string,
|
|
) =>
|
|
[
|
|
"issues",
|
|
companyId,
|
|
"project",
|
|
projectId,
|
|
"plugin-operations",
|
|
originKindPrefix,
|
|
] as const,
|
|
listParticipatedByAgent: (companyId: string, agentId: string) =>
|
|
["issues", companyId, "participated-by-agent", agentId] as const,
|
|
listByParent: (companyId: string, parentId: string) =>
|
|
["issues", companyId, "parent", parentId] as const,
|
|
listCreatedFromIssue: (companyId: string, issueId: string) =>
|
|
["issues", companyId, "created-from", issueId] as const,
|
|
listByDescendantRoot: (companyId: string, rootIssueId: string) =>
|
|
["issues", companyId, "descendants", rootIssueId] as const,
|
|
listByExecutionWorkspace: (
|
|
companyId: string,
|
|
executionWorkspaceId: string,
|
|
) =>
|
|
[
|
|
"issues",
|
|
companyId,
|
|
"execution-workspace",
|
|
executionWorkspaceId,
|
|
] as const,
|
|
summaries: (companyId: string) => ["issues", companyId, "summaries"] as const,
|
|
summaryBatch: (companyId: string, userId: string | null | undefined, sortedIds: readonly string[]) =>
|
|
["issues", companyId, "summaries", userId ?? "__local_board__", sortedIds] as const,
|
|
detail: (id: string) => ["issues", "detail", id] as const,
|
|
comments: (issueId: string) => ["issues", "comments", issueId] as const,
|
|
acknowledgedComments: (issueId: string) => ["issues", "comments", issueId, "acknowledged"] as const,
|
|
earlierComments: (issueId: string) => ["issues", "comments", issueId, "earlier"] as const,
|
|
commentsList: (issueId: string) =>
|
|
["issues", "comments", issueId, "list"] as const,
|
|
queuedComments: (issueId: string) =>
|
|
["issues", "queued-comments", issueId] as const,
|
|
interactions: (issueId: string) =>
|
|
["issues", "interactions", issueId] as const,
|
|
acceptedPlanDecompositions: (issueId: string) =>
|
|
["issues", "accepted-plan-decompositions", issueId] as const,
|
|
feedbackVotes: (issueId: string) =>
|
|
["issues", "feedback-votes", issueId] as const,
|
|
costSummary: (issueId: string, options: { excludeRoot?: boolean } = {}) =>
|
|
options.excludeRoot
|
|
? (["issues", "cost-summary", issueId, "exclude-root"] as const)
|
|
: (["issues", "cost-summary", issueId] as const),
|
|
privacyConstraints: (issueId: string) => ["issues", "privacy-constraints", issueId] as const,
|
|
accessGrants: (issueId: string) => ["issues", "access-grants", issueId] as const,
|
|
attachments: (issueId: string) => ["issues", "attachments", issueId] as const,
|
|
attachmentPreview: (attachmentId: string) => ["issues", "attachment-preview", attachmentId] as const,
|
|
documents: (issueId: string) => ["issues", "documents", issueId] as const,
|
|
document: (issueId: string, key: string) =>
|
|
["issues", "document", issueId, key] as const,
|
|
documentRevisions: (issueId: string, key: string) =>
|
|
["issues", "document-revisions", issueId, key] as const,
|
|
documentAnnotations: (
|
|
issueId: string,
|
|
key: string,
|
|
status: "open" | "resolved" | "all" = "all",
|
|
) => ["issues", "document-annotations", issueId, key, status] as const,
|
|
activity: (issueId: string) => ["issues", "activity", issueId] as const,
|
|
runs: (issueId: string) => ["issues", "runs", issueId] as const,
|
|
approvals: (issueId: string) => ["issues", "approvals", issueId] as const,
|
|
liveRuns: (issueId: string) => ["issues", "live-runs", issueId] as const,
|
|
activeRun: (issueId: string) => ["issues", "active-run", issueId] as const,
|
|
contextUsage: (issueId: string, assigneeAgentId: string | null) =>
|
|
["issues", "context-usage", issueId, assigneeAgentId] as const,
|
|
runnerGoal: (issueId: string, agentId?: string | null) =>
|
|
["issues", "runner-goal", issueId, agentId ?? "__effective__"] as const,
|
|
workProducts: (issueId: string) =>
|
|
["issues", "work-products", issueId] as const,
|
|
workProductPullRequestRefresh: (issueId: string) =>
|
|
["issues", "work-product-pr-refresh", issueId] as const,
|
|
fileResources: (
|
|
issueId: string,
|
|
options: {
|
|
workspace?: string;
|
|
projectId?: string | null;
|
|
workspaceId?: string | null;
|
|
path?: string | null;
|
|
mode?: string;
|
|
q?: string | null;
|
|
limit?: number;
|
|
offset?: number;
|
|
} = {},
|
|
) => ["issues", "file-resources", issueId, "list", options] as const,
|
|
fileResource: (
|
|
issueId: string,
|
|
query: {
|
|
path: string;
|
|
workspace?: string;
|
|
projectId?: string | null;
|
|
workspaceId?: string | null;
|
|
},
|
|
) => ["issues", "file-resources", issueId, "resolve", query] as const,
|
|
fileResourceContent: (
|
|
issueId: string,
|
|
query: {
|
|
path: string;
|
|
workspace?: string;
|
|
projectId?: string | null;
|
|
workspaceId?: string | null;
|
|
},
|
|
) => ["issues", "file-resources", issueId, "content", query] as const,
|
|
/**
|
|
* Batched availability preflight. `refKeys` are the deduplicated,
|
|
* lexicographically sorted reference keys in the request so identical
|
|
* batches share one cache entry.
|
|
*/
|
|
fileResourceAvailability: (issueId: string, refKeys: readonly string[]) =>
|
|
["issues", "file-resources", issueId, "availability", refKeys] as const,
|
|
},
|
|
routines: {
|
|
list: (companyId: string, filters?: { projectId?: string | null }) =>
|
|
[
|
|
"routines",
|
|
companyId,
|
|
filters?.projectId ?? "__all-projects__",
|
|
] as const,
|
|
detail: (id: string) => ["routines", "detail", id] as const,
|
|
runs: (id: string) => ["routines", "runs", id] as const,
|
|
revisions: (id: string) => ["routines", "revisions", id] as const,
|
|
activity: (companyId: string, id: string) =>
|
|
["routines", "activity", companyId, id] as const,
|
|
documentAnnotations: (
|
|
routineId: string,
|
|
key: "description",
|
|
status: "open" | "resolved" | "all" = "all",
|
|
) => ["routines", "document-annotations", routineId, key, status] as const,
|
|
},
|
|
folders: {
|
|
list: (companyId: string, kind: string) =>
|
|
["folders", companyId, kind] as const,
|
|
},
|
|
executionWorkspaces: {
|
|
list: (
|
|
companyId: string,
|
|
filters?: Record<string, string | boolean | undefined>,
|
|
) => ["execution-workspaces", companyId, filters ?? {}] as const,
|
|
summaryList: (
|
|
companyId: string,
|
|
filters?: Record<string, string | boolean | undefined>,
|
|
) => ["execution-workspaces", companyId, "summary", filters ?? {}] as const,
|
|
overview: (
|
|
companyId: string,
|
|
filters?: Record<string, string | number | boolean | undefined>,
|
|
) =>
|
|
["execution-workspaces", companyId, "overview", filters ?? {}] as const,
|
|
detail: (id: string) => ["execution-workspaces", "detail", id] as const,
|
|
closeReadiness: (id: string) =>
|
|
["execution-workspaces", "close-readiness", id] as const,
|
|
workspaceOperations: (id: string) =>
|
|
["execution-workspaces", "workspace-operations", id] as const,
|
|
},
|
|
environments: {
|
|
list: (companyId: string) => ["environments", companyId] as const,
|
|
capabilities: (companyId: string) =>
|
|
["environment-capabilities", companyId] as const,
|
|
customImageTemplate: (environmentId: string) =>
|
|
["environments", environmentId, "custom-image-template"] as const,
|
|
customImageSetupSession: (sessionId: string) =>
|
|
["environment-custom-image-setup-sessions", sessionId] as const,
|
|
},
|
|
projects: {
|
|
environments: (companyId: string) => ["project-environments", companyId] as const,
|
|
all: (companyId: string) => ["projects", companyId] as const,
|
|
list: (companyId: string, opts: { includeArchived?: boolean } = {}) =>
|
|
[
|
|
"projects",
|
|
companyId,
|
|
{ includeArchived: opts.includeArchived === true },
|
|
] as const,
|
|
detail: (id: string) => ["projects", "detail", id] as const,
|
|
accessMembers: (id: string) => ["projects", "access-members", id] as const,
|
|
},
|
|
cases: {
|
|
handoffs: (id: string) => ["cases", "handoffs", id] as const,
|
|
list: (companyId: string) => ["cases", companyId] as const,
|
|
detail: (id: string) => ["cases", "detail", id] as const,
|
|
documents: (id: string) => ["cases", "documents", id] as const,
|
|
documentAnnotations: (
|
|
caseId: string,
|
|
key: string,
|
|
status: "open" | "resolved" | "all" = "all",
|
|
) => ["cases", "document-annotations", caseId, key, status] as const,
|
|
events: (id: string) => ["cases", "events", id] as const,
|
|
children: (parentId: string) => ["cases", "children", parentId] as const,
|
|
revisions: (id: string, key: string) =>
|
|
["cases", "revisions", id, key] as const,
|
|
forIssue: (issueId: string) => ["cases", "for-issue", issueId] as const,
|
|
},
|
|
externalObjects: {
|
|
byIssue: (issueId: string) =>
|
|
["external-objects", "by-issue", issueId] as const,
|
|
issueSummary: (issueId: string) =>
|
|
["external-objects", "issue-summary", issueId] as const,
|
|
issueSummaries: (companyId: string, issueIds: readonly string[]) =>
|
|
["external-objects", "issue-summaries", companyId, issueIds] as const,
|
|
projectSummary: (projectId: string) =>
|
|
["external-objects", "project-summary", projectId] as const,
|
|
},
|
|
goals: {
|
|
list: (companyId: string) => ["goals", companyId] as const,
|
|
detail: (id: string) => ["goals", "detail", id] as const,
|
|
},
|
|
artifacts: {
|
|
list: (
|
|
companyId: string,
|
|
kind?: string,
|
|
q?: string,
|
|
groupBy?: string,
|
|
groupIssueId?: string,
|
|
) =>
|
|
[
|
|
"artifacts",
|
|
companyId,
|
|
kind ?? "all",
|
|
q ?? "",
|
|
groupBy ?? "none",
|
|
groupIssueId ?? "",
|
|
] as const,
|
|
byAgent: (companyId: string, agentId: string) =>
|
|
["artifacts", companyId, "by-agent", agentId] as const,
|
|
},
|
|
budgets: {
|
|
overview: (companyId: string) =>
|
|
["budgets", "overview", companyId] as const,
|
|
},
|
|
approvals: {
|
|
list: (companyId: string, status?: string) =>
|
|
["approvals", companyId, status] as const,
|
|
detail: (approvalId: string) =>
|
|
["approvals", "detail", approvalId] as const,
|
|
comments: (approvalId: string) =>
|
|
["approvals", "comments", approvalId] as const,
|
|
issues: (approvalId: string) =>
|
|
["approvals", "issues", approvalId] as const,
|
|
},
|
|
access: {
|
|
invites: (companyId: string, state: string = "all", limit: number = 20) =>
|
|
["access", "invites", "paginated-v1", companyId, state, limit] as const,
|
|
joinRequests: (companyId: string, status: string = "pending_approval") =>
|
|
["access", "join-requests", companyId, status] as const,
|
|
companyMembers: (companyId: string) =>
|
|
["access", "company-members", companyId] as const,
|
|
companyUserDirectory: (companyId: string) =>
|
|
["access", "company-user-directory", companyId] as const,
|
|
adminUsers: (query: string) => ["access", "admin-users", query] as const,
|
|
userCompanyAccess: (userId: string) =>
|
|
["access", "user-company-access", userId] as const,
|
|
invite: (token: string) => ["access", "invite", token] as const,
|
|
currentBoardAccess: ["access", "current-board-access"] as const,
|
|
},
|
|
auth: {
|
|
session: ["auth", "session"] as const,
|
|
},
|
|
inboxAgentPolicy: {
|
|
mine: (companyId: string) =>
|
|
["inbox-agent-policy", companyId, "me"] as const,
|
|
},
|
|
sidebarPreferences: {
|
|
companyOrder: (userId: string) =>
|
|
["sidebar-preferences", "company-order", userId] as const,
|
|
projectOrder: (companyId: string, userId: string) =>
|
|
["sidebar-preferences", "project-order", companyId, userId] as const,
|
|
},
|
|
primaryAgent: {
|
|
mine: (companyId: string, userId: string) => ["primary-agent", companyId, userId] as const,
|
|
},
|
|
resourceMemberships: {
|
|
mine: (companyId: string) =>
|
|
["resource-memberships", companyId, "me"] as const,
|
|
},
|
|
instance: {
|
|
settings: ["instance", "settings"] as const,
|
|
generalSettings: ["instance", "general-settings"] as const,
|
|
experimentalSettings: ["instance", "experimental-settings"] as const,
|
|
},
|
|
health: ["health"] as const,
|
|
stagingCommit: ["staging-commit"] as const,
|
|
cloud: {
|
|
stacks: ["cloud", "stacks"] as const,
|
|
},
|
|
secrets: {
|
|
list: (companyId: string) => ["secrets", companyId] as const,
|
|
providers: (companyId: string) => ["secret-providers", companyId] as const,
|
|
providerConfigs: (companyId: string) =>
|
|
["secret-provider-configs", companyId] as const,
|
|
usage: (secretId: string) => ["secrets", "usage", secretId] as const,
|
|
accessEvents: (secretId: string) =>
|
|
["secrets", "access-events", secretId] as const,
|
|
userDefinitions: (companyId: string) =>
|
|
["user-secret-definitions", companyId] as const,
|
|
userDefinitionCoverage: (companyId: string, definitionId: string) =>
|
|
["user-secret-definitions", companyId, definitionId, "coverage"] as const,
|
|
myUserSecrets: (companyId: string) =>
|
|
["my-user-secrets", companyId] as const,
|
|
proposals: (companyId: string, status: string = "pending") =>
|
|
["secret-proposals", companyId, status] as const,
|
|
},
|
|
companySearch: {
|
|
search: (
|
|
companyId: string,
|
|
q: string,
|
|
scope: string,
|
|
limit: number,
|
|
offset: number,
|
|
) => ["company-search", companyId, q, scope, limit, offset] as const,
|
|
},
|
|
dashboard: (companyId: string) => ["dashboard", companyId] as const,
|
|
attention: (companyId: string) => ["attention", companyId] as const,
|
|
decisions: {
|
|
list: (companyId: string, status?: string) =>
|
|
["decisions", companyId, status ?? "__all-statuses__"] as const,
|
|
detail: (id: string) => ["decisions", "detail", id] as const,
|
|
forTargetIssue: (companyId: string, issueId: string) =>
|
|
["decisions", companyId, "target", issueId] as const,
|
|
taskRecord: (companyId: string, issueId: string, viewerId: string | null, decisionId: string | null) =>
|
|
["decisions", companyId, "task-record", issueId, viewerId, decisionId] as const,
|
|
},
|
|
decisionQueues: {
|
|
list: (companyId: string) => ["decision-queues", companyId] as const,
|
|
items: (companyId: string, key: string) =>
|
|
["decision-queues", companyId, "items", key] as const,
|
|
seedRules: (companyId: string) =>
|
|
["decision-queues", companyId, "seed-rules"] as const,
|
|
triage: (companyId: string, sourceKind: string, sourceId: string) =>
|
|
["decision-triage", companyId, sourceKind, sourceId] as const,
|
|
},
|
|
workTimeline: (companyId: string, lens?: string) =>
|
|
["work-timeline", companyId, lens ?? "all"] as const,
|
|
userProfile: (companyId: string, userSlug: string) =>
|
|
["user-profile", companyId, userSlug] as const,
|
|
sidebarBadges: (companyId: string) => ["sidebar-badges", companyId] as const,
|
|
inboxDismissals: (companyId: string) =>
|
|
["inbox-dismissals", companyId] as const,
|
|
activity: (companyId: string) => ["activity", companyId] as const,
|
|
costs: (companyId: string, from?: string, to?: string) =>
|
|
["costs", companyId, from, to] as const,
|
|
usageByProvider: (companyId: string, from?: string, to?: string) =>
|
|
["usage-by-provider", companyId, from, to] as const,
|
|
usageByBiller: (companyId: string, from?: string, to?: string) =>
|
|
["usage-by-biller", companyId, from, to] as const,
|
|
financeSummary: (companyId: string, from?: string, to?: string) =>
|
|
["finance-summary", companyId, from, to] as const,
|
|
financeByBiller: (companyId: string, from?: string, to?: string) =>
|
|
["finance-by-biller", companyId, from, to] as const,
|
|
financeByKind: (companyId: string, from?: string, to?: string) =>
|
|
["finance-by-kind", companyId, from, to] as const,
|
|
financeEvents: (
|
|
companyId: string,
|
|
from?: string,
|
|
to?: string,
|
|
limit: number = 100,
|
|
) => ["finance-events", companyId, from, to, limit] as const,
|
|
usageWindowSpend: (companyId: string) =>
|
|
["usage-window-spend", companyId] as const,
|
|
usageQuotaWindows: (companyId: string) =>
|
|
["usage-quota-windows", companyId] as const,
|
|
heartbeats: (companyId: string, agentId?: string) =>
|
|
["heartbeats", companyId, agentId] as const,
|
|
runDetail: (runId: string) => ["heartbeat-run", runId] as const,
|
|
providerTraceMetadata: (companyId: string, runIds: readonly string[]) =>
|
|
["provider-trace-metadata", companyId, [...runIds].sort()] as const,
|
|
runWorkspaceOperations: (runId: string) =>
|
|
["heartbeat-run", runId, "workspace-operations"] as const,
|
|
liveRuns: (companyId: string) => ["live-runs", companyId] as const,
|
|
runIssues: (runId: string) => ["run-issues", runId] as const,
|
|
org: (companyId: string) => ["org", companyId] as const,
|
|
skills: {
|
|
available: ["skills", "available"] as const,
|
|
},
|
|
plugins: {
|
|
all: ["plugins"] as const,
|
|
examples: ["plugins", "examples"] as const,
|
|
detail: (pluginId: string) => ["plugins", pluginId] as const,
|
|
health: (pluginId: string) => ["plugins", pluginId, "health"] as const,
|
|
uiContributions: ["plugins", "ui-contributions"] as const,
|
|
config: (pluginId: string, companyId: string) =>
|
|
["plugins", pluginId, "companies", companyId, "config"] as const,
|
|
localFolders: (pluginId: string, companyId: string) =>
|
|
["plugins", pluginId, "companies", companyId, "local-folders"] as const,
|
|
dashboard: (pluginId: string) =>
|
|
["plugins", pluginId, "dashboard"] as const,
|
|
logs: (pluginId: string) => ["plugins", pluginId, "logs"] as const,
|
|
},
|
|
adapters: {
|
|
all: ["adapters"] as const,
|
|
availability: (companyId: string | null, environmentId?: string | null) =>
|
|
["adapters", "availability", companyId, environmentId ?? null] as const,
|
|
},
|
|
};
|