Files
PaperClipAI/ui/src/api/activity.ts
T
DottaandPaperclip 9b3fe260ba fix(tasks): surface Codex ChatGPT model rejection (#15299)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for work.
> - Native Runner runs report provider errors and can also save a structured failure result.
> - Codex rejects a model that the user's ChatGPT account cannot use.
> - Master now diagnoses this rejection, but the task's compact run data omits its message. The thread can still call it a generic run failure.
> - Users need the account restriction and a clear step to repair the model selection.
> - This pull request shows the existing diagnosis as “Model unavailable” on the task.

## Linked Issues or Issue Description

**What happened?**

Codex returns HTTP 400 with `invalid_request_error` and the message `The 'gpt-6.1-sol' model is not supported when using Codex with a ChatGPT account.` Master now stores an actionable diagnosis for this rejection. The task thread still labels it “Run failed” and does not receive its error text in compact run data.

**Expected behavior**

Show the account restriction on the task and in the run error. Tell the user to choose a supported model or clear the task's model override before retrying.

**Steps to reproduce**

1. Use a Native Runner Codex agent signed in with a ChatGPT account.
2. Select a model that produces the rejection above and start a task.
3. Let the runner save its generic failed result. Inspect the task's failure marker and recovery notice.

**Additional context**

Refs: #15304. That merged PR diagnoses the provider failure and preserves worker and review recovery rules. This PR adds its task-facing message and guidance without changing that diagnosis or those rules.

Refs: #13134. That PR improves model discovery for ChatGPT accounts. This PR exposes the rejection when a configured model still fails at execution time.

## What Changed

- Return a bounded model rejection message in compact issue-run data.
- Show “Model unavailable” and model-change guidance in the task thread and recovery notice.
- Add database and UI regression tests, including both the original rejection text and master's fixed diagnosis. Document the new failure message.

## Verification

- Focused merged-branch validation: 279 tests passed across activity service, native provider failure observation and PostgreSQL integration, TaskChatThread, and ExecutionBlockerNotice.
- `pnpm -r typecheck`, `pnpm build`, and `pnpm check:token-gates` passed on the merged source tree.
- Greptile reviewed conflict-resolution commit `1db39c4e636a92e2e76ba224b71c6f1e2f556c81` at 5/5 with no findings or inline comments.
- All 55 check runs completed without failure on that commit. The two optional Storybook jobs were skipped. The legacy Snyk status passed. The branch has no merge conflicts.
- UI regression assertion: the task's failure marker says “Model unavailable” and retains the account restriction. It no longer says that this failure happened after a final response.

## Risks

- Provider recognition and recovery are owned by the existing master implementation. This PR exposes only bounded error text for failed runs with `native_provider_model_rejected`.
- Historical runs with the generic `adapter_failed` code are not reclassified. No stored run is rewritten.
- Existing retry and reconciliation gates remain in place. No schema migration or model configuration change is required.

## Model Used

OpenAI Codex, based on GPT-6, with reasoning, code execution, and browser inspection. The exact deployment model ID and context window are not exposed in this session.

## Checklist

- [x] I have included a thinking path that traces from project context to this change
- [x] I have specified the model used (with version and capability details)
- [x] I have 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>
2026-10-06 08:05:25 -05:00

85 lines
2.8 KiB
TypeScript

import type { ActivityEvent, ExecutionProjection, RunLivenessState } from "@paperclipai/shared";
import { api, type RequestOptions } from "./client";
export type { RunLivenessState } from "@paperclipai/shared";
export interface RunForIssue {
runId: string;
execution?: ExecutionProjection | null;
runtimeMode?: "legacy" | "native";
status: string;
agentId: string;
adapterType: string;
startedAt: string | null;
finishedAt: string | null;
createdAt: string;
invocationSource: string;
responsibleUserId?: string | null;
errorCode?: string | null;
/** Bounded provider message for a failed model rejection. */
error?: string | null;
usageJson: Record<string, unknown> | null;
resultJson: Record<string, unknown> | null;
logBytes?: number | null;
retryOfRunId?: string | null;
scheduledRetryAt?: string | null;
scheduledRetryAttempt?: number;
scheduledRetryReason?: string | null;
retryExhaustedReason?: string | null;
livenessState?: RunLivenessState | null;
livenessReason?: string | null;
continuationAttempt?: number;
lastUsefulActionAt?: string | null;
nextAction?: string | null;
wakeCommentIds?: string[] | null;
wakeCommentId?: string | null;
contextCommentId?: string | null;
contextIssueId?: string | null;
contextSnapshot?: Record<string, unknown> | null;
environment?: {
id: string;
name: string;
driver: string;
} | null;
environmentLease?: {
id: string;
status: string;
leasePolicy: string;
provider: string | null;
providerLeaseId: string | null;
executionWorkspaceId: string | null;
workspacePath: string | null;
failureReason: string | null;
cleanupStatus: string | null;
acquiredAt: string | Date;
releasedAt: string | Date | null;
} | null;
}
export interface IssueForRun {
issueId: string;
identifier: string | null;
title: string;
status: string;
priority: string;
}
export const activityApi = {
list: (
companyId: string,
filters?: { entityType?: string; entityId?: string; agentId?: string; limit?: number },
options?: RequestOptions,
) => {
const params = new URLSearchParams();
if (filters?.entityType) params.set("entityType", filters.entityType);
if (filters?.entityId) params.set("entityId", filters.entityId);
if (filters?.agentId) params.set("agentId", filters.agentId);
if (filters?.limit) params.set("limit", String(filters.limit));
const qs = params.toString();
return api.get<ActivityEvent[]>(`/companies/${companyId}/activity${qs ? `?${qs}` : ""}`, options);
},
forIssue: (issueId: string) => api.get<ActivityEvent[]>(`/issues/${issueId}/activity`),
runsForIssue: (issueId: string) => api.get<RunForIssue[]>(`/issues/${issueId}/runs`),
issuesForRun: (runId: string) => api.get<IssueForRun[]>(`/heartbeat-runs/${runId}/issues`),
};