Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion apps/web/src/features/files/file-operations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ describe("error mapping", () => {
it("classifies a failed first list read", () => {
expect(classifyFilesListError(new AgentCoreError("missing", 404))).toBe("unsupported");
expect(classifyFilesListError(new AgentCoreError("method", 405))).toBe("unsupported");
expect(classifyFilesListError(new AgentCoreError("storage", 503, "file_storage_unavailable"))).toBe("storage-unavailable");
expect(classifyFilesListError(new AgentCoreError("busy", 503))).toBe("failed");
expect(classifyFilesListError(new TypeError("Failed to fetch"))).toBe("failed");
});
Expand Down
4 changes: 1 addition & 3 deletions apps/web/src/features/files/file-operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,12 @@ export function isAbortError(error: unknown): boolean {
return error instanceof DOMException && error.name === "AbortError";
}

export type FilesListFailure = "unsupported" | "storage-unavailable" | "failed";
export type FilesListFailure = "unsupported" | "failed";

/** How a failed first list read is presented. */
export function classifyFilesListError(error: unknown): FilesListFailure {
if (error instanceof AgentCoreError) {
if (error.status === 404 || error.status === 405) return "unsupported";
if (error.status === 503 && error.code === "file_storage_unavailable") return "storage-unavailable";
}
return "failed";
}
Expand All @@ -53,7 +52,6 @@ export function filesErrorReason(error: unknown, t: Translate): string {
if (error instanceof AgentCoreError) {
if (error.status === 401 || error.status === 403) return t("errors.unauthorized");
if (error.status === 413) return t("errors.tooLarge");
if (error.status === 503 && error.code === "file_storage_unavailable") return t("errors.storage");
if (error.status === 502 && typeof error.code === "string" && error.code.startsWith("invalid_source_file")) {
return t("errors.invalidResponse");
}
Expand Down
5 changes: 1 addition & 4 deletions apps/web/src/features/skills/skill-operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ import { appendCollectionPage } from "../../lib/collection-pagination";
* Skill navigation support, from the first Skill list request:
* - `supported`: the list succeeded;
* - `unsupported`: 404 or 405, an older Core without Skills (hide the entry);
* - `storage-unavailable`: 503 `skill_storage_unavailable` (show the entry with an explanation);
* - `error`: anything else (show the entry with a retry).
*/
export type SkillsSupport = "supported" | "unsupported" | "storage-unavailable" | "error";
export type SkillsSupport = "supported" | "unsupported" | "error";

export const SKILLS_PAGE_SIZE = 20;

Expand All @@ -20,7 +19,6 @@ export function isAbortError(error: unknown): boolean {
export function classifySkillsError(error: unknown): Exclude<SkillsSupport, "supported"> {
if (error instanceof AgentCoreError) {
if (error.status === 404 || error.status === 405) return "unsupported";
if (error.status === 503 && error.code === "skill_storage_unavailable") return "storage-unavailable";
}
return "error";
}
Expand All @@ -34,7 +32,6 @@ export function coreErrorMessage(error: unknown): string {
export type SkillUploadFailure =
| { kind: "invalid"; message: string }
| { kind: "too-large" }
| { kind: "storage-unavailable" }
| { kind: "interrupted"; cancelled: boolean }
| { kind: "other"; message: string };

Expand Down
1 change: 0 additions & 1 deletion apps/web/src/i18n/locales/en/core-errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ export const coreErrors = {
"project_api_key_exists": "An active API key with this name already exists.",
"executor_credential_exists": "An executor credential with this name already exists.",
"credential_storage_unavailable": "Core credential storage is unavailable. Check its credential encryption configuration.",
"diagnostics_unavailable": "Core diagnostics are unavailable.",
"internal_error": "Core could not complete the request.",
"sandbox_generation_stale": "Core has a newer sandbox configuration. Refresh and review it before submitting again.",
"sandbox_reset_required": "Reset the sandbox deployment before changing this configuration.",
Expand Down
5 changes: 0 additions & 5 deletions apps/web/src/i18n/locales/en/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,6 @@ export const files = {
title: "This Core does not list files",
description: "The Files list returned HTTP {{status}}.",
},
storage: {
title: "File storage is not configured",
description: "Core has no file storage configured, so files cannot be listed or uploaded.",
},
upload: {
uploading: "Uploading {{name}}…",
uploaded: "Uploaded {{name}}",
Expand All @@ -74,7 +70,6 @@ export const files = {
transport: "The request did not reach Core or its response was lost.",
unauthorized: "Core rejected the connection credentials.",
tooLarge: "The file is larger than Core accepts.",
storage: "Core has no file storage configured.",
invalidResponse: "Core returned a response this console does not recognize.",
},
} as const;
5 changes: 0 additions & 5 deletions apps/web/src/i18n/locales/en/skills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,6 @@ export const skills = {
description: "Upload a folder, or a ZIP of one, whose SKILL.md starts like this:",
exampleLabel: "Minimal report/SKILL.md",
},
storage: {
title: "Core has no Skill storage configured",
description: "Skills can be listed and uploaded once the Core operator configures Skill storage.",
},
unsupported: {
title: "This Core does not offer Skills",
description: "The connected Core answered the Skill list with {{status}}.",
Expand Down Expand Up @@ -178,7 +174,6 @@ export const skills = {
errors: {
invalid: "Core rejected the Skill: {{message}}",
tooLarge: "The files exceed the size limit.",
storage: "Core has no Skill storage configured.",
interrupted: "The upload did not finish. Your selection is kept; Core may have stored it anyway, so check the list before trying again.",
cancelled: "Upload cancelled. Your selection is kept; Core may have stored it anyway, so check the list before trying again.",
other: "The upload failed: {{message}}",
Expand Down
1 change: 0 additions & 1 deletion apps/web/src/i18n/locales/zh-CN/core-errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ export const coreErrors = {
"project_api_key_exists": "此名称已被使用中的 API Key 占用。",
"executor_credential_exists": "此名称的执行器凭证已存在。",
"credential_storage_unavailable": "Core 凭证存储不可用,请检查凭证加密配置。",
"diagnostics_unavailable": "Core 诊断暂不可用。",
"internal_error": "Core 未能完成请求。",
"sandbox_generation_stale": "Core 的沙箱配置已更新。请刷新并检查后再提交。",
"sandbox_reset_required": "请先重置沙箱部署,再更改此配置。",
Expand Down
5 changes: 0 additions & 5 deletions apps/web/src/i18n/locales/zh-CN/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,6 @@ export const files: TranslationShape<typeof english> = {
title: "此 Core 不支持列出文件",
description: "文件列表请求返回 HTTP {{status}}。",
},
storage: {
title: "未配置文件存储",
description: "Core 未配置文件存储,因此无法列出或上传文件。",
},
upload: {
uploading: "正在上传 {{name}}…",
uploaded: "已上传 {{name}}",
Expand All @@ -78,7 +74,6 @@ export const files: TranslationShape<typeof english> = {
transport: "请求未到达 Core,或响应已丢失。",
unauthorized: "Core 拒绝了当前连接凭据。",
tooLarge: "文件超过 Core 的大小上限。",
storage: "Core 未配置文件存储。",
invalidResponse: "Core 返回了控制台无法识别的响应。",
},
};
5 changes: 0 additions & 5 deletions apps/web/src/i18n/locales/zh-CN/skills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,6 @@ export const skills = {
description: "上传一个文件夹或它的 ZIP,其中的 SKILL.md 以这样的内容开头:",
exampleLabel: "最小的 report/SKILL.md",
},
storage: {
title: "Core 未配置 Skill 存储",
description: "Core 运维方配置 Skill 存储后,才能查看和上传 Skill。",
},
unsupported: {
title: "当前 Core 不提供 Skill",
description: "所连接的 Core 对 Skill 列表请求返回了 {{status}}。",
Expand Down Expand Up @@ -178,7 +174,6 @@ export const skills = {
errors: {
invalid: "Core 拒绝了这个 Skill:{{message}}",
tooLarge: "文件超出大小限制。",
storage: "Core 未配置 Skill 存储。",
interrupted: "上传没有完成。已选的文件仍然保留;Core 可能已经保存了它,重试前请先查看列表。",
cancelled: "已取消上传。已选的文件仍然保留;Core 可能已经保存了它,重试前请先查看列表。",
other: "上传失败:{{message}}",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/lib/core-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const codes = new Set<keyof typeof coreErrors>([
"invalid_name", "invalid_node_capacity", "invalid_model_provider", "model_configuration_model_invalid", "harness_config_invalid", "model_provider_base_url_invalid",
"model_provider_protocol_unsupported", "model_provider_api_key_invalid", "model_provider_token_limits_invalid",
"invalid_sandbox_configuration", "project_archived", "project_exists", "project_api_key_exists",
"executor_credential_exists", "credential_storage_unavailable", "diagnostics_unavailable", "internal_error",
"executor_credential_exists", "credential_storage_unavailable", "internal_error",
]);

/** Only catalogued, correctly typed detail keys can enter localized text. */
Expand Down
4 changes: 2 additions & 2 deletions contracts/agents-api/core-errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Errors on `/v1` and `/api/v1` keep their own envelopes and never carry `details`

## Optional details

`error.details`, when present, is a nonempty flat object. Its values are strings, finite numbers, booleans, null or arrays of strings (possibly empty). It holds only Core-owned facts: never submitted names, URLs or keys, echoed request values, native error text or provider response bodies. Each code that has details lists its exact keys below.
`error.details`, when present, is a nonempty flat object. Its values are strings, finite numbers, null or arrays of strings (possibly empty). It holds only Core-owned facts: never submitted names, URLs or keys, echoed request values, native error text or provider response bodies. Each code that has details lists its exact keys below.

| Code | Details |
| --- | --- |
Expand Down Expand Up @@ -101,6 +101,6 @@ The [Session and Turn diagnostics reads](session-diagnostics.md) return these ca
| `environment_unavailable` | Environment unavailable for initial input |
| `environment_provisioning_failed` | Hosted provisioning failure; params contain nullable `step`, `index`, `exit_code` from a sanitized receipt |

When the diagnostics reader is not configured, the reads return 503 `diagnostics_unavailable` without details. A database failure is an error, never an empty or healthy snapshot. Provisioning reasons and native messages are never parsed for categories or parameters.
A database failure is an error, never an empty or healthy snapshot. Provisioning reasons and native messages are never parsed for categories or parameters.

Native categories apply only to a failed Turn whose outcome has `error_code: engine_failed`. Core accepts only the listed `engine_error_code` values; an unknown, malformed or absent value stays `harness_error`. Only `connection_failed` uses `engine_http_status`. Nested metadata and provider text never classify a failure. Core storage, incomplete-stream and cancellation failures take precedence, and cancelled or completed Turns have no failure. [Native error classification](../../docs/runtime-protocol.md#native-failure-classification) lists which adapters report each category.
2 changes: 1 addition & 1 deletion contracts/agents-api/core-metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

`GET /core/v1/metrics?range=1h|6h|24h|7d` reports Core's own health: its process, execution queue and slots, PostgreSQL and background jobs. It requires the Core key ([Core administration API](admin-api.md)).

`range` is the only parameter, sent at most once; it defaults to `1h`. An empty, repeated or unsupported value, or any other parameter, returns 400 `invalid_request`. When Core has no metrics service, or cannot read it, the route returns 503 `core_metrics_unavailable`. When only some measurements fail, the response is still `200` with `service.status` set to `degraded` and each missing value set to null. The response never contains database or native error text, credentials, bodies, resource IDs or tenant labels.
`range` is the only parameter, sent at most once; it defaults to `1h`. An empty, repeated or unsupported value, or any other parameter, returns 400 `invalid_request`. When Core cannot read its metrics, the route returns 503 `core_metrics_unavailable`. When only some measurements fail, the response is still `200` with `service.status` set to `degraded` and each missing value set to null. The response never contains database or native error text, credentials, bodies, resource IDs or tenant labels.

## Time and missing data

Expand Down
2 changes: 1 addition & 1 deletion contracts/agents-api/harness-onboarding.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ An omitted or unknown policy, missing required callback, or callback paired with

Run the `engine` and `execution` tests for omission, policy, combination and error precedence coverage, and the public onboarding/store tests for admission and Runtime dispatch. Test fixtures use `engine/enginetest`, whose exhaustive literal also requires a decision when a field is added; it is not a production profile.

`execution.Policy` supplies immutable service qualification to HTTP admission, Worker device selection and final dispatch. Custom composition gives the same Policy to `api.WithExecutionPolicy` and the Core dispatcher's `Policy`. The zero value uses the built-in profiles; an explicitly empty catalog authorizes none. There is no mutable global registration.
`execution.Policy` supplies immutable service qualification to HTTP admission, Worker device selection and final dispatch. Custom composition gives the same Policy to `api.Dependencies.Policy` and the Core dispatcher's `Policy`. The zero value uses the built-in profiles; an explicitly empty catalog authorizes none. There is no mutable global registration.

## Native model configuration

Expand Down
19 changes: 0 additions & 19 deletions packages/agents-client/src/fixtures/parsar-d3f55046/skills.json
Original file line number Diff line number Diff line change
Expand Up @@ -307,25 +307,6 @@
"param": null
}
}
},
"error_storage_unavailable": {
"request": {
"method": "GET",
"path": "/v1/skills"
},
"status": 503,
"headers": {
"content-type": "application/json",
"cache-control": "no-store"
},
"body": {
"error": {
"message": "Skill storage is unavailable.",
"type": "server_error",
"code": "skill_storage_unavailable",
"param": null
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -227,26 +227,6 @@
"param": "purpose"
}
}
},
"storage_unavailable": {
"request": {
"method": "GET",
"path": "/v1/files",
"query": "limit=100"
},
"status": 503,
"headers": {
"content-type": "application/json",
"cache-control": "no-store"
},
"body": {
"error": {
"message": "Source file storage is unavailable.",
"type": "server_error",
"code": "file_storage_unavailable",
"param": null
}
}
}
}
}
2 changes: 0 additions & 2 deletions packages/agents-client/src/skills.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,13 @@ describe("Skill fixtures at Core d3f55046", () => {
it("surfaces Core error envelopes as typed errors", async () => {
const { client } = clientFor(
fixtures.error_not_found,
fixtures.error_storage_unavailable,
fixtures.error_request_too_large,
fixtures.error_invalid_upload,
fixtures.error_default_version_delete,
);
const upload: SkillUploadInput = { kind: "zip", file: new Blob(["zip"]), filename: "report.zip" };

await expect(client.retrieveSkill("skill_missing")).rejects.toMatchObject({ status: 404, code: null });
await expect(client.listSkills()).rejects.toMatchObject({ status: 503, code: "skill_storage_unavailable" });
await expect(client.uploadSkill(upload)).rejects.toMatchObject({ status: 413, code: "request_too_large" });
await expect(client.uploadSkill(upload)).rejects.toMatchObject({ status: 400, message: "Invalid resource identifier or request limits." });
await expect(client.deleteSkillVersion(skillId, "2")).rejects.toMatchObject({ status: 400, code: "invalid_value", param: "version" });
Expand Down
5 changes: 0 additions & 5 deletions packages/agents-client/src/source-files-list.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,6 @@ describe("Files list", () => {
});

it("maps Core errors", async () => {
const unavailable = recordingClient(responses.storage_unavailable);
await expect(unavailable.client.listSourceFiles({ limit: 100 })).rejects.toMatchObject({
status: 503, code: "file_storage_unavailable", errorType: "server_error",
});

const invalid = recordingClient(responses.list_invalid_purpose);
await expect(invalid.client.listSourceFiles()).rejects.toMatchObject({ status: 400, param: "purpose", code: null });

Expand Down
Loading
Loading