diff --git a/README.md b/README.md
index ddaccce..898e97a 100644
--- a/README.md
+++ b/README.md
@@ -71,9 +71,12 @@ Restart `pnpm dev` after changing them. Keep credentials server-side. A direct
Core URL in the connection dialog is only for a compatible Core that explicitly
allows the Web origin, methods, and headers through CORS.
-Do not have a Core running yet? Follow [Connecting Agent Core](docs/core-connection.md).
-That guide contains the complete PostgreSQL, caller key, device, daemon, native
-harness, `CODEX_HOME`, security, verification, and shutdown procedures.
+Do not have a Core running yet? Use the immutable
+[current Parsar setup guide](https://github.com/MiniMax-AI-Dev/parsar/blob/d91ba48ac6c49cfdf6f08d7687b9be76ba6d53ee/services/agents-api/README.md#standalone-http-service).
+The repository's [legacy Web connection runbook](docs/core-connection.md) is pinned
+to the older revision stated at its top; revalidate its PostgreSQL, caller-key,
+device, daemon, native-harness, `CODEX_HOME`, verification, and shutdown steps before
+applying them to a newer Core.
## First use
@@ -110,16 +113,18 @@ See [Architecture](docs/architecture.md) for the full component and trust bounda
| --- | --- |
| Web cannot reach Core | Confirm the Core address and `AGENTS_API_PROXY_TARGET`, then restart Vite |
| `401 invalid_api_key` | The plaintext caller bearer must match the current Core key binding |
-| `503 execution_unavailable` / `Execution is not enabled` | Core is reachable but has no enabled execution path; connect its configured executor/daemon |
+| `503 execution_unavailable` / `Execution is not enabled` | Core rejected execution; inspect its safe error plus runtime and ownership state. A worker, executor, or daemon may be unconfigured or disconnected, or an execution lease may have been lost |
| Agent saves but its model fails | Use a model ID and provider credential supported by the connected runtime |
-`/healthz` proves HTTP liveness only, not chat readiness. See the
-[full troubleshooting guide](docs/core-connection.md#troubleshooting) before retrying
-an uncertain request.
+`/healthz` proves HTTP liveness only, not chat readiness. Check durable Core state and
+the current pinned Parsar guide before retrying an uncertain request; the
+[legacy 043 troubleshooting snapshot](docs/core-connection.md#troubleshooting) is
+historical context only.
## Documentation
-- [Connect Agent Core](docs/core-connection.md) — full local and deployment setup
+- [Current Parsar Core setup](https://github.com/MiniMax-AI-Dev/parsar/blob/d91ba48ac6c49cfdf6f08d7687b9be76ba6d53ee/services/agents-api/README.md#standalone-http-service) — immutable current upstream guide
+- [Legacy Web connection runbook](docs/core-connection.md) — historical `0438880` snapshot; revalidate before use
- [Protocol coverage](docs/protocol-coverage.md) — exact supported API surface
- [Architecture](docs/architecture.md) — ownership, runtime, and trust boundaries
- [Roadmap](docs/roadmap.md) — planned Web and Core integrations
diff --git a/README.zh-CN.md b/README.zh-CN.md
index a186461..badac83 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -70,9 +70,11 @@ AGENTS_API_PROXY_TOKEN_FILE=/absolute/private/path/to/web-token
修改后重启 `pnpm dev`。凭据应保留在服务端。只有兼容 Core 通过 CORS
明确允许 Web 的源、方法和请求头时,才能在连接对话框中使用 Core 直连 URL。
-还没有运行中的 Core?请参阅[连接 Agent Core](docs/core-connection.md)。
-该文档包含完整的 PostgreSQL、调用方凭据、执行设备、daemon、原生执行适配层(harness)、
-`CODEX_HOME`、安全、验证和停止流程。
+还没有运行中的 Core?请使用不可变的
+[当前 Parsar 配置指南](https://github.com/MiniMax-AI-Dev/parsar/blob/d91ba48ac6c49cfdf6f08d7687b9be76ba6d53ee/services/agents-api/README.md#standalone-http-service)。
+仓库内的[旧版 Web 连接手册](docs/core-connection.md)固定在文首标注的旧 revision;
+将其中 PostgreSQL、调用方凭据、执行设备、daemon、原生执行适配层(harness)、
+`CODEX_HOME`、验证和停止流程用于更新版 Core 前必须重新核对。
## 第一次使用
@@ -108,15 +110,17 @@ WebSocket 当作 API URL。
| --- | --- |
| Web 无法访问 Core | 确认 Core 地址和 `AGENTS_API_PROXY_TARGET`,然后重启 Vite |
| `401 invalid_api_key` | 明文调用方 Bearer 凭据必须与 Core 当前的密钥绑定匹配 |
-| `503 execution_unavailable` / `Execution is not enabled` | Core 可以访问,但没有已启用的执行链路;请连接其配置的 executor/daemon |
+| `503 execution_unavailable` / `Execution is not enabled` | Core 拒绝执行;请检查其安全错误、运行时和 ownership 状态。worker、executor 或 daemon 可能未配置或已断连,也可能丢失了执行 lease |
| Agent 保存成功但模型运行失败 | 使用已连接运行时支持的 model ID 和提供商凭据 |
`/healthz` 只能证明 HTTP 存活,不能证明聊天已就绪。重试结果不确定的请求前,
-请先查看[完整故障排查](docs/core-connection.md#troubleshooting)。
+请先核对 Core 持久状态和当前固定版本的 Parsar 指南;
+[旧版 043 故障排查快照](docs/core-connection.md#troubleshooting)仅供历史参考。
## 文档入口
-- [连接 Agent Core](docs/core-connection.md) — 完整本地和部署配置
+- [当前 Parsar Core 配置](https://github.com/MiniMax-AI-Dev/parsar/blob/d91ba48ac6c49cfdf6f08d7687b9be76ba6d53ee/services/agents-api/README.md#standalone-http-service) — 不可变的当前上游指南
+- [旧版 Web 连接手册](docs/core-connection.md) — 历史 `0438880` 快照,使用前必须重新核对
- [协议覆盖范围](docs/protocol-coverage.md) — 准确的已支持 API 范围
- [架构说明](docs/architecture.md) — 所有权、运行时和信任边界
- [路线图](docs/roadmap.md) — 计划中的 Web 和 Core 集成
diff --git a/apps/web/e2e/agents-lifecycle.spec.ts b/apps/web/e2e/agents-lifecycle.spec.ts
index a157cc0..29ac6c2 100644
--- a/apps/web/e2e/agents-lifecycle.spec.ts
+++ b/apps/web/e2e/agents-lifecycle.spec.ts
@@ -503,8 +503,7 @@ test("keeps the Agent ledger and dialogs usable at 390 px in light and dark mode
expect(metrics.ledger?.right).toBeLessThanOrEqual(390);
await attachScreenshot(page, testInfo, "narrow-light-agent-ledger");
- await page.getByRole("button", { name: "Environments", exact: true }).click();
- await expect(page.getByRole("heading", { name: "Environments Observed" })).toBeVisible();
+ await expect(page.getByRole("button", { name: "Environments", exact: true })).toHaveCount(0);
const sessionsNavigation = page.getByRole("button", { name: "Sessions", exact: true });
await sessionsNavigation.click();
await expect(sessionsNavigation).toHaveAttribute("aria-current", "page");
@@ -515,6 +514,9 @@ test("keeps the Agent ledger and dialogs usable at 390 px in light and dark mode
await globalCreate.click();
const createPanel = page.getByRole("menu", { name: "Create" });
await expect(createPanel).toBeVisible();
+ await expect(createPanel.getByRole("menuitem")).toHaveCount(2);
+ await expect(createPanel.getByRole("menuitem", { name: /Environment template/i })).toHaveCount(0);
+ await expect(createPanel.getByRole("menuitem", { name: /Environment key/i })).toHaveCount(0);
const createPanelBox = await createPanel.boundingBox();
expect(createPanelBox).not.toBeNull();
expect(createPanelBox?.x ?? -1).toBeGreaterThanOrEqual(0);
@@ -1043,7 +1045,10 @@ test("renders self-hosted Environment and Workspace state safely across reconnec
await expect(panel.getByRole("link", { name: "Launcher setup" })).toBeVisible();
await expect(page.getByRole("region", { name: "Environment connection required" })).toBeVisible();
await expect(page.getByRole("region", { name: "Function result required" })).toBeVisible();
- await expect(page.getByLabel("Function result or error")).toBeVisible();
+ await expect(page.getByLabel("Function result or error")).toBeEnabled();
+ await expect(page.getByRole("button", { name: "Return error" })).toBeEnabled();
+ await expect(page.getByRole("button", { name: "Submit result" })).toBeDisabled();
+ await expect(page.getByRole("button", { name: "Cancel active Turn" })).toBeEnabled();
await expect(page.locator("body")).not.toContainText("launcher:private");
await expect(page.locator("body")).not.toContainText("executor_token=secret");
await expect(page.locator('a[href^="file:"]')).toHaveCount(0);
@@ -1404,3 +1409,25 @@ test("manually retries uncertain sends with the original key only while the payl
expect(sends[5]?.idempotencyKey).not.toBe(sends[4]?.idempotencyKey);
await attachScreenshot(page, testInfo, "desktop-send-recovery");
});
+
+test("keeps cancellation available for an Environment-only required action", async ({ page, request }) => {
+ await resetFixture(request);
+ await controlFixture(request, { environmentScenario: 6 });
+ await page.goto("/");
+ await expect(page.getByText("listening", { exact: true })).toBeVisible();
+ await expect(page.getByRole("region", { name: "Environment connection required" })).toBeVisible();
+ await expect(page.getByRole("region", { name: "Function result required" })).toHaveCount(0);
+ const writesBefore = (await fixtureRequests(request)).filter(
+ (entry) => entry.method === "POST" && entry.path.endsWith("/events"),
+ ).length;
+ const cancel = page.getByRole("button", { name: "Cancel active Turn" });
+ await expect(cancel).toBeEnabled();
+ await cancel.click();
+ await expect.poll(async () => (await fixtureRequests(request)).filter(
+ (entry) => entry.method === "POST" && entry.path.endsWith("/events"),
+ ).length).toBe(writesBefore + 1);
+ const writes = (await fixtureRequests(request)).filter(
+ (entry) => entry.method === "POST" && entry.path.endsWith("/events"),
+ );
+ expect(writes.at(-1)?.body).toEqual({ events: [{ type: "agent.session.input.cancel" }] });
+});
diff --git a/apps/web/e2e/core-connection.spec.ts b/apps/web/e2e/core-connection.spec.ts
index c7b86c4..e357bcd 100644
--- a/apps/web/e2e/core-connection.spec.ts
+++ b/apps/web/e2e/core-connection.spec.ts
@@ -303,7 +303,7 @@ test("announces loading, authenticated access, and each safe failure state from
methods.push(route.request().method());
const reply = replies.shift();
if (!reply) return route.abort("failed");
- await new Promise((resolve) => setTimeout(resolve, 50));
+ await new Promise((resolve) => setTimeout(resolve, 150));
if ("abort" in reply) return route.abort("failed");
return route.fulfill({
status: reply.status,
@@ -333,7 +333,8 @@ test("announces loading, authenticated access, and each safe failure state from
await expect(loading).toContainText("Testing Core connection…");
const terminal = dialog.getByRole(expected.role);
await expect(terminal).toContainText(expected.text);
- await expect(terminal).toContainText("Execution readiness: Unknown / not verified");
+ await expect(terminal).toContainText("Chat uses the current Agents API contract");
+ await expect(terminal).toContainText("does not start a Turn or verify its runtime dependencies");
if (expected.absentText) await expect(terminal).not.toContainText(expected.absentText);
}
@@ -373,7 +374,8 @@ test("turns a stalled probe into one bounded unreachable result", async ({ page,
(window as ProbeInstrumentationWindow).__stalledProbeCallCount ?? 0
))).toBe(1);
await expect(dialog.getByRole("alert")).toContainText("Core unreachable", { timeout: 7_500 });
- await expect(dialog.getByRole("alert")).toContainText("Execution readiness: Unknown / not verified");
+ await expect(dialog.getByRole("alert")).toContainText("Chat uses the current Agents API contract");
+ await expect(dialog.getByRole("alert")).toContainText("does not start a Turn or verify its runtime dependencies");
expect(await page.evaluate(() => (window as ProbeInstrumentationWindow).__stalledProbeCallCount)).toBe(1);
});
diff --git a/apps/web/e2e/fixture-core.mjs b/apps/web/e2e/fixture-core.mjs
index e6dfee1..577736b 100644
--- a/apps/web/e2e/fixture-core.mjs
+++ b/apps/web/e2e/fixture-core.mjs
@@ -253,7 +253,7 @@ function applyEnvironmentScenario(value) {
const session = state.sessions[0];
if (!session) return;
const hostileRemote = "https://launcher:private@executor.example.test/connect?executor_token=secret#credential";
- if (value === 1 || value === 4 || value === 5) {
+ if (value === 1 || value === 4 || value === 5 || value === 6) {
session.environment = {
type: "self_hosted",
id: value === 5 ? canonicalEnvironmentUuid.toUpperCase() : "environment_fixture",
@@ -261,11 +261,15 @@ function applyEnvironmentScenario(value) {
workspace_directory: `/workspace//${"long/".repeat(45)}project`,
capability_directories: ["/capabilities/read-only", `/capabilities/${"wide/".repeat(55)}`],
};
- session.status = value === 1 ? "requires_action" : "idle";
- session.required_actions = value === 1 ? [
- { type: "environment_connection", environment_id: "environment_fixture" },
- { type: "function_call", call_id: "call_fixture", turn_id: "turn_fixture", name: "confirm", arguments: { safe: true } },
- ] : [];
+ session.status = value === 1 || value === 6 ? "requires_action" : "idle";
+ session.required_actions = value === 1
+ ? [
+ { type: "environment_connection", environment_id: "environment_fixture" },
+ { type: "function_call", call_id: "call_fixture", turn_id: "turn_fixture", name: "confirm", arguments: { safe: true } },
+ ]
+ : value === 6
+ ? [{ type: "environment_connection", environment_id: "environment_fixture" }]
+ : [];
return;
}
if (value === 2) {
diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx
index bccdca3..c9fb58e 100644
--- a/apps/web/src/App.tsx
+++ b/apps/web/src/App.tsx
@@ -29,7 +29,6 @@ import {
requestAgentDetail,
requestAgentUpdate,
} from "./features/agents/agent-actions";
-import { EnvironmentsView } from "./features/environments/EnvironmentsView";
import {
SessionsView,
type SessionDetailState,
@@ -1264,16 +1263,6 @@ export function App() {
onUpdate={updateAgent}
/>
) : null}
- {view === "environments" ? (
- {
- setSelectedId(sessionId);
- setView("sessions");
- }}
- />
- ) : null}
{view === "system" ? : null}
diff --git a/apps/web/src/components/ConnectionModal.test.tsx b/apps/web/src/components/ConnectionModal.test.tsx
index b4536fd..7c07ea6 100644
--- a/apps/web/src/components/ConnectionModal.test.tsx
+++ b/apps/web/src/components/ConnectionModal.test.tsx
@@ -89,11 +89,12 @@ describe("Agent Core connection modes", () => {
const markup = renderModal("/v1", true);
expect(markup).toContain("Operator-owned setup");
- expect(markup).toContain("Connection guide");
- expect(markup).toContain("Troubleshooting");
+ expect(markup).toContain("Legacy Web guide · 043 snapshot");
+ expect(markup).toContain("Legacy troubleshooting · 043 snapshot");
expect(markup).toContain("Parsar Core setup");
expect(markup).toContain("98c5b3312ad33e1fae8b94283a011eb3e5f4ee2c");
- expect(markup).toContain("0438880ab21aa16d05cb91a4c7f91cc0abc12358");
+ expect(markup).toContain("d91ba48ac6c49cfdf6f08d7687b9be76ba6d53ee");
+ expect(markup).not.toContain("0438880ab21aa16d05cb91a4c7f91cc0abc12358");
expect(markup).not.toContain("f7cdf591396529880d80f8211fc7a0f4768fdf46");
expect(markup).not.toContain("8cc2898ca42b272cb3771234ee6a0ad0d2e932ba");
expect(markup).not.toContain("7409e00ca25311805a9f8f0d03614f820e407642");
@@ -104,14 +105,14 @@ describe("Agent Core connection modes", () => {
expect(markup).not.toContain("AGENTS_API_DAEMON_WS_URL");
});
- it("states the GET-only probe boundary and unknown execution readiness", () => {
+ it("states the GET-only probe boundary without disabling the current chat contract", () => {
const markup = renderModal("/v1", true);
expect(markup).toContain("Test connection");
expect(markup).toContain("with one GET");
expect(markup).toContain("never creates an Agent, Session, Turn, or Item");
- expect(markup).toContain("Execution readiness remains Unknown / not verified");
- expect(markup).toContain("does not prove a daemon, model, or provider is ready");
+ expect(markup).toContain("Chat uses the current Core events contract");
+ expect(markup).toContain("a real request can still fail");
});
});
@@ -172,8 +173,8 @@ describe("Connection probe status", () => {
const markup = renderToStaticMarkup();
expect(markup).toContain(expected);
- expect(markup).toContain("Execution readiness: Unknown / not verified");
- expect(markup).not.toContain("Execution readiness: Ready");
+ expect(markup).toContain("Chat uses the current Agents API contract");
+ expect(markup).toContain("does not start a Turn or verify its runtime dependencies");
});
it("uses an alert for failures and a polite status for authenticated access", () => {
diff --git a/apps/web/src/components/ConnectionModal.tsx b/apps/web/src/components/ConnectionModal.tsx
index 63ccc76..a63ac08 100644
--- a/apps/web/src/components/ConnectionModal.tsx
+++ b/apps/web/src/components/ConnectionModal.tsx
@@ -29,7 +29,7 @@ export type ConnectionProbeState =
| { status: "complete"; result: CoreProbeResult };
const webBaseline = "98c5b3312ad33e1fae8b94283a011eb3e5f4ee2c";
-const parsarBaseline = "0438880ab21aa16d05cb91a4c7f91cc0abc12358";
+const parsarBaseline = "d91ba48ac6c49cfdf6f08d7687b9be76ba6d53ee";
const operatorGuideUrl = `https://github.com/MiniMax-AI-Dev/agents-core-web/blob/${webBaseline}/docs/core-connection.md`;
const troubleshootingUrl = `${operatorGuideUrl}#troubleshooting`;
const parsarCoreSetupUrl = `https://github.com/MiniMax-AI-Dev/parsar/blob/${parsarBaseline}/services/agents-api/README.md#standalone-http-service`;
@@ -102,7 +102,7 @@ export function ConnectionProbeStatus({ state }: { state: ConnectionProbeState }
>
{copy.title}
{copy.detail}
- Execution readiness: Unknown / not verified.
+ Chat uses the current Agents API contract. This read-only probe does not start a Turn or verify its runtime dependencies.
);
}
@@ -316,11 +316,11 @@ export function ConnectionModal({
);
@@ -434,6 +456,10 @@ export function SessionsView({
const unsupportedActionCount = requiredActions.length - environmentConnections.length - functionActions.length + (
!requiredActionsAreValid || selected?.status === "requires_action" && !requiredActions.length ? 1 : 0
);
+ const showCancelOnly = Boolean(
+ (selected?.status === "in_progress" || selected?.status === "requires_action") &&
+ (unsupportedActionCount > 0 || environmentConnections.length > 0 && functionActions.length === 0),
+ );
return (
@@ -617,21 +643,21 @@ export function SessionsView({
{sendError ? (
- Executor setup
+
+ Core runtime setup
) : undefined}
@@ -673,6 +699,9 @@ export function SessionsView({
))}
{unsupportedActionCount ? : null}
+ {showCancelOnly ? (
+
+ ) : null}
{!unsupportedActionCount && functionActions.length ? (
{selected.agent.name || "Untitled Agent"}
{selected.status === "in_progress" || selected.status === "requires_action" ? (
-
-
-
+
) : (
p {
+ margin: 0;
+ color: var(--fg-muted);
+ font-size: 12px;
+ line-height: 17px;
+}
+
.composer {
display: flex;
width: 100%;
@@ -3797,8 +3821,7 @@ button.trace-step-row:hover {
box-shadow: var(--shadow-floating);
}
-.create-menu-panel > button,
-.create-menu-unavailable {
+.create-menu-panel > button {
display: grid;
grid-template-columns: 18px minmax(0, 1fr);
min-height: 54px;
@@ -3816,8 +3839,7 @@ button.trace-step-row:hover {
background: var(--hover);
}
-.create-menu-panel > button:disabled,
-.create-menu-unavailable {
+.create-menu-panel > button:disabled {
color: var(--fg-muted);
opacity: 0.72;
}
@@ -4145,235 +4167,6 @@ button.trace-step-row:hover {
line-height: 17px;
}
-/* Session-derived Environments overview. */
-
-.environments-page {
- display: flex;
- min-height: 0;
- flex-direction: column;
- overflow: hidden;
-}
-
-.environments-header > div:first-child {
- min-width: 0;
-}
-
-.environments-header h1 span {
- color: var(--fg-muted);
- font-size: 12px;
- font-weight: 400;
-}
-
-.environments-header p {
- margin: 2px 0 0;
- overflow: hidden;
- color: var(--fg-muted);
- font-size: 11px;
- line-height: 15px;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.environments-header .search-control {
- width: min(340px, 40vw);
- margin-left: auto;
-}
-
-.environments-scroll {
- min-height: 0;
- padding: 22px 24px 48px;
- overflow-y: auto;
-}
-
-.environment-catalog-boundary {
- display: flex;
- max-width: 960px;
- padding: 12px 14px;
- gap: 10px;
- color: var(--fg);
- background: color-mix(in srgb, var(--warning) 7%, var(--surface));
- border: 1px solid color-mix(in srgb, var(--warning) 25%, var(--line));
- border-radius: 8px;
-}
-
-.environment-catalog-boundary > svg {
- flex: 0 0 auto;
- margin-top: 1px;
- color: var(--warning);
-}
-
-.environment-catalog-boundary strong {
- font-size: 13px;
- font-weight: 500;
-}
-
-.environment-catalog-boundary p {
- margin: 2px 0 0;
- color: var(--fg-muted);
- font-size: 12px;
- line-height: 17px;
-}
-
-.environment-unavailable-grid {
- display: grid;
- max-width: 960px;
- margin-top: 14px;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 10px;
-}
-
-.environment-unavailable-grid article {
- display: grid;
- grid-template-columns: 20px minmax(0, 1fr);
- padding: 13px;
- gap: 4px 9px;
- background: var(--surface-subtle);
- border: 1px solid var(--line);
- border-radius: 8px;
-}
-
-.environment-unavailable-grid article > svg {
- margin-top: 1px;
- color: var(--fg-muted);
-}
-
-.environment-unavailable-grid article > div {
- display: flex;
- align-items: baseline;
- justify-content: space-between;
- gap: 8px;
-}
-
-.environment-unavailable-grid strong {
- font-size: 13px;
- font-weight: 500;
-}
-
-.environment-unavailable-grid span {
- color: var(--fg-muted);
- font-size: 10px;
- text-transform: uppercase;
-}
-
-.environment-unavailable-grid p {
- grid-column: 2;
- margin: 0;
- color: var(--fg-muted);
- font-size: 11px;
- line-height: 16px;
-}
-
-.observed-environments {
- max-width: 960px;
- margin-top: 26px;
-}
-
-.observed-environments > header {
- display: flex;
- align-items: flex-end;
- justify-content: space-between;
- margin-bottom: 9px;
- gap: 12px;
-}
-
-.observed-environments h2 {
- margin: 0;
- font-size: 15px;
- font-weight: 600;
-}
-
-.observed-environments header p {
- margin: 2px 0 0;
- color: var(--fg-muted);
- font-size: 11px;
- line-height: 16px;
-}
-
-.observed-environments > header > span {
- color: var(--fg-muted);
- font-family: var(--font-mono);
- font-size: 12px;
-}
-
-.observed-environment-list {
- display: grid;
- gap: 10px;
-}
-
-.observed-environment {
- overflow: hidden;
- border: 1px solid var(--line);
- border-radius: 8px;
-}
-
-.observed-environment > header {
- display: flex;
- min-height: 46px;
- align-items: center;
- justify-content: space-between;
- padding: 7px 10px 7px 13px;
- gap: 12px;
- background: var(--surface-subtle);
- border-bottom: 1px solid var(--line);
-}
-
-.observed-environment > header > div {
- display: flex;
- min-width: 0;
- flex-direction: column;
-}
-
-.observed-environment > header strong {
- font-size: 13px;
- font-weight: 500;
-}
-
-.observed-environment > header span {
- overflow: hidden;
- color: var(--fg-muted);
- font-size: 11px;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.observed-environment > header code {
- font-family: var(--font-mono);
- font-size: 10px;
-}
-
-.observed-environment > .environment-panel {
- margin: 0;
- border: 0;
- border-radius: 0;
-}
-
-.environment-overview-empty {
- display: flex;
- min-height: 180px;
- align-items: center;
- justify-content: center;
- padding: 24px;
- flex-direction: column;
- color: var(--fg-muted);
- text-align: center;
- background: var(--surface-subtle);
- border: 1px dashed var(--line-strong);
- border-radius: 8px;
-}
-
-.environment-overview-empty h3 {
- margin: 9px 0 2px;
- color: var(--fg);
- font-size: 14px;
- font-weight: 500;
-}
-
-.environment-overview-empty p {
- max-width: 560px;
- margin: 0 0 12px;
- font-size: 12px;
-}
-
@media (max-width: 1000px) {
.agent-setup-layout {
grid-template-columns: minmax(340px, 1fr) minmax(360px, 1fr);
@@ -4439,8 +4232,7 @@ button.trace-step-row:hover {
border-top: 1px solid var(--line);
}
- .agent-form-grid,
- .environment-unavailable-grid {
+ .agent-form-grid {
grid-template-columns: 1fr;
}
@@ -4453,30 +4245,6 @@ button.trace-step-row:hover {
margin-left: 0;
}
- .environments-header {
- min-height: auto;
- flex: 0 0 auto;
- flex-wrap: wrap;
- padding: 10px 12px;
- }
-
- .environments-header .search-control {
- width: 100%;
- margin-left: 0;
- }
-
- .environments-scroll {
- padding: 14px 12px 32px;
- }
-
- .observed-environment > header {
- align-items: flex-start;
- flex-direction: column;
- }
-
- .observed-environment > header .button {
- width: 100%;
- }
}
@keyframes overlay-in {
diff --git a/docs/architecture.md b/docs/architecture.md
index 91eb0d5..b7a3fa5 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -17,8 +17,8 @@ This boundary is intentional:
- Core fixes and runtime-provider work are contributed upstream instead of copied or
simulated in Web.
-The current compatibility baseline is Parsar
-[`0438880a`](https://github.com/MiniMax-AI-Dev/parsar/commit/0438880ab21aa16d05cb91a4c7f91cc0abc12358),
+The current compatibility audit baseline is Parsar
+[`d91ba48a`](https://github.com/MiniMax-AI-Dev/parsar/commit/d91ba48ac6c49cfdf6f08d7687b9be76ba6d53ee),
whose contract is pinned to `openai-python` 3.13.0 commit
[`d7c41efe`](https://github.com/openai/openai-python/tree/d7c41efee1b0802b79f3f88a678ef2052b06e9ce/src/openai/resources/beta/agents).
This is a fixed beta subset, not a claim that every current OpenAI Agents API
@@ -142,17 +142,19 @@ Session and Environment identity, request/event revisions, stream epoch, selecti
and abort checks reject late state. The TypeScript client exposes Turn list/retrieve
for diagnostics, but the current UI does not use them in recovery.
-The client never retries an uncertain write automatically. The current UI does not
-persist one generated idempotency key across a manual resend, so it must not imply
-that pressing Send again is a safe retry. Durable/live terminal precedence and
-pending-operation key persistence remain M1 hardening work.
+The client never retries an uncertain write automatically. It keeps a failed
+message payload and idempotency key in memory only for an explicit byte-for-byte
+unchanged manual resend. Editing the payload, changing Session/Core, receiving a
+successful exact HTTP 204, or receiving a permanent rejection creates a new
+operation. Any other 2xx fails closed and remains uncertain because the documented
+Session events contract admits writes only with 204.
## Runtime profiles
-The Web currently creates `environment: {"type":"none"}` Sessions. On Parsar
-`0438880a`, Core also contains a narrow Codex `self_hosted` profile for empty Sessions
-followed by constrained idle text input. The Web does not create or connect that
-profile, but for an already selected self-hosted Session it reads the durable
+The Web currently creates `environment: {"type":"none"}` Sessions. Parsar
+`d91ba48a` also exposes Session-bound `self_hosted` data and documented event inputs.
+The Web does not create or connect that profile, but for an already selected
+self-hosted Session it reads the durable
Environment's exact safe projection and status. Durable `expired` and live-only
`ready` remain separate states; empty installation arrays do not describe a host or
Workspace. This profile is not equivalent to the internal daemon socket, Docker,
@@ -193,8 +195,10 @@ workaround.
The development proxy is loopback-only convenience, not a production security
boundary. Production must terminate TLS, authenticate Web users, authorize requests,
-and hold the Core bearer in a reverse proxy/BFF. See
-[Connecting Agent Core](core-connection.md) for the local setup and credential flow.
+and hold the Core bearer in a reverse proxy/BFF. Use the immutable
+[current Parsar setup guide](https://github.com/MiniMax-AI-Dev/parsar/blob/d91ba48ac6c49cfdf6f08d7687b9be76ba6d53ee/services/agents-api/README.md#standalone-http-service)
+for Core lifecycle and treat [Connecting Agent Core](core-connection.md) as a legacy
+Web runbook pinned to the older revision stated at its top.
## Sources
@@ -202,6 +206,6 @@ and hold the Core bearer in a reverse proxy/BFF. See
- [Official OpenAI Agents API overview](https://developers.openai.com/api/docs/guides/agents-api/overview)
- [Official OpenAI Session lifecycle](https://developers.openai.com/api/docs/guides/agents-api/sessions)
- [Pinned `openai-python` Agents resources](https://github.com/openai/openai-python/tree/d7c41efee1b0802b79f3f88a678ef2052b06e9ce/src/openai/resources/beta/agents)
-- [Parsar Agents API contract at `0438880a`](https://github.com/MiniMax-AI-Dev/parsar/blob/0438880ab21aa16d05cb91a4c7f91cc0abc12358/contracts/agents-api/README.md)
-- [Parsar Environment contract at `0438880a`](https://github.com/MiniMax-AI-Dev/parsar/blob/0438880ab21aa16d05cb91a4c7f91cc0abc12358/contracts/agents-api/environments.md)
-- [Parsar standalone service guide at `0438880a`](https://github.com/MiniMax-AI-Dev/parsar/blob/0438880ab21aa16d05cb91a4c7f91cc0abc12358/services/agents-api/README.md)
+- [Parsar Agents API contract at `d91ba48a`](https://github.com/MiniMax-AI-Dev/parsar/blob/d91ba48ac6c49cfdf6f08d7687b9be76ba6d53ee/contracts/agents-api/README.md)
+- [Parsar Environment contract at `d91ba48a`](https://github.com/MiniMax-AI-Dev/parsar/blob/d91ba48ac6c49cfdf6f08d7687b9be76ba6d53ee/contracts/agents-api/environments.md)
+- [Parsar standalone service guide at `d91ba48a`](https://github.com/MiniMax-AI-Dev/parsar/blob/d91ba48ac6c49cfdf6f08d7687b9be76ba6d53ee/services/agents-api/README.md)
diff --git a/docs/protocol-coverage.md b/docs/protocol-coverage.md
index c4f357c..22305b3 100644
--- a/docs/protocol-coverage.md
+++ b/docs/protocol-coverage.md
@@ -5,24 +5,25 @@ OpenAI-hosted service compatibility.
## Compatibility baseline
-- Parsar Core:
- [`0438880a`](https://github.com/MiniMax-AI-Dev/parsar/commit/0438880ab21aa16d05cb91a4c7f91cc0abc12358)
-- The product-navigation, Agent execution-admission, and Environment-unavailable boundaries were re-audited
- against Parsar [`c15d42a2`](https://github.com/MiniMax-AI-Dev/parsar/commit/c15d42a270c0667bcaa83a6b9d01a9892ebf7edd).
- That revision still exposes only Environment retrieve plus Session-bound
- `self_hosted` creation; it adds no public Environment list, template, file, or
- browser-facing key management route.
+- Current Parsar Core compatibility audit:
+ [`d91ba48a`](https://github.com/MiniMax-AI-Dev/parsar/commit/d91ba48ac6c49cfdf6f08d7687b9be76ba6d53ee).
+ This immutable revision re-confirms the Web-used Agent admission, chat, and
+ Environment boundaries. `OpenAI-Beta: agents=v1` plus the `/v1/agents/**`
+ resources and Session events endpoint are the versioned Web/Core contract. Core
+ exposes no additional public execution-readiness, capability, or build-version
+ resource; Web does not require one before using the documented chat events. It
+ exposes Environment retrieve plus Session-bound `self_hosted` data, but no public
+ Environment list, template, file-management, or browser-facing key route.
- Upstream resource source: `openai-python` 3.13.0 beta Agents resources at
[`d7c41efe`](https://github.com/openai/openai-python/tree/d7c41efee1b0802b79f3f88a678ef2052b06e9ce/src/openai/resources/beta/agents)
- Required beta header: `OpenAI-Beta: agents=v1`
- Core base: same-origin `/v1` through the Web proxy for stock Parsar Core; a direct
URL only for a compatible Core or proxy with explicit CORS support
-Parsar's pinned inventory contains 42 upstream operations in 15 resource classes;
-the referenced Core revision has handlers for 20 operations, and those handlers
-still implement partial request/event semantics. Importing an official SDK or
-accepting extra fields is not compatibility proof. Unsupported capabilities must
-fail explicitly.
+Parsar implements a partial subset of the pinned upstream resource inventory, and
+its handlers still implement partial request/event semantics. Importing an official
+SDK or accepting extra fields is not compatibility proof. Unsupported capabilities
+must fail explicitly.
Requests use `Authorization: Bearer `. Optional
`OpenAI-Organization` and `OpenAI-Project` headers, when present, must exactly match
@@ -37,21 +38,21 @@ the Core key binding. Agents Core Web's local proxy owns the bearer server-side.
| Sessions create/list/retrieve | Yes | Yes | UI creates idle `environment:none` Sessions; client types also cover the pinned `self_hosted` request and safe response projection |
| Sessions update/delete | Yes | Yes | Title/string metadata editing and one-Session confirmed deletion; no bulk or Workspace deletion |
| Session live events | Yes | Yes | Authenticated `fetch` stream, not `EventSource` |
-| Input message | Yes | Yes | Opens SSE before submission; uncertain failures retain the in-memory payload/key for an explicit unchanged manual retry only |
+| Input message / steering | Yes | Yes | Opens SSE before submitting `agent.session.input.message`; only HTTP 204 is durable admission, while Core errors or an unexpected 2xx remain visible and uncertain failures retain the in-memory payload/key for an explicit unchanged manual retry only |
| Active Turn cancel | Yes | Yes | Submitted as a Session event, not a Turn-create endpoint |
| Turn list | Yes | Yes, read-only | Selected Sessions load every page in ascending creation order; no Turn mutation UI |
| Turn retrieve | Yes | No | Reusable client diagnostic method; timeline recovery uses the all-pages list |
| Item list/recovery | Yes | Yes | Authoritative recovery after stream loss |
| Parsar `apply_patch` Item presentation | Existing function Item fields | Yes, read-only | Parsar extension recognized only for the pinned `changes[].{path,kind,diff}` shape; not an OpenAI standard Item type |
-| Function result/error | Yes | Yes | Initial UI supports text result/error handoff only for `function_call` actions |
+| Function result/error | Yes | Yes | Supports text `agent.session.input.tool_result` success/error handoff for exact `function_call` actions |
| Initial-input creation stream | Later | No | Idle-create flow avoids the early-event race |
| Artifacts/files | Later | No | Required Core resources are not implemented |
| Environment connection action | Yes | Render-only | `environment_connection` is distinct from a function call; Web shows an operator-owned, non-actionable state and sends no result |
| Environment lifecycle events | Yes | Read-only | UI projects pinned pending, ready, connected, disconnected, and failed live snapshots; unknown/malformed status events clear prior live claims and render as unavailable |
| Environment retrieve | Yes | Yes, read-only | For a valid `self_hosted` Session Environment ID, reads the exact public resource fields and durable status; no create/list/update/delete support |
-| Environment overview | Session-derived only | Yes, read-only | Shows only `self_hosted` projections in currently loaded Sessions and their already-observed matching status; explicitly not a Core Environment catalog |
-| Environment templates | No | Explicitly unavailable | The navigation/Create surface does not simulate template reads or writes |
-| Environment keys | No public browser API | Explicitly unavailable | Operator-issued executor credentials never enter browser state or request previews |
+| Environment overview | No public list API | No top-level UI | Web does not turn loaded Session projections into a catalog; a selected Session may still show its exact Environment data |
+| Environment templates | No | Hidden | No navigation or Create entry is shown without a Core contract |
+| Environment keys | No public browser API | Hidden | Operator-issued executor credentials never enter browser state, request previews, navigation, or Create actions |
| Vaults | Later | No | Credentials must never be stored in browser metadata |
| Protocol Subagents / enabled multi-agent | Later | No | Distinct from storing multiple Agent configurations |
| Usage/observability | Response types | Yes, scoped | Session aggregate and per-Turn token Usage are labelled separately; unavailable measurements remain unknown, not zero |
@@ -59,15 +60,22 @@ the Core key binding. Agents Core Web's local proxy owns the bearer server-side.
## Runtime boundary
- The Web currently creates only `environment: {"type":"none"}` Sessions.
+- Message, active-Turn steering, cancel, and function-result/error writes use the
+ current `agents=v1` Session events contract. Web does not add a separate private
+ runtime-readiness gate. Only exact HTTP 204 denotes durable event admission;
+ every other status, including another 2xx, fails closed. HTTP acceptance, health,
+ Agent creation, and an open SSE stream still do not prove that execution will
+ complete; subsequent durable Session, Turn, and Item state is authoritative.
- Product navigation and the global Create menu do not widen the protocol. Agent
- and idle Session creation call the existing client methods. Environment template
- and Environment key entries are non-actionable unavailable states.
+ and idle Session creation call the existing client methods. The top-level
+ Environments destination and Environment template/key entries are absent because
+ Core exposes no corresponding list or management APIs.
- The Agent setup request preview is derived entirely from editable Agent fields and
the sanitized Core base URL. Its authorization header always contains the literal
`${AGENTS_CORE_API_KEY}` placeholder; it never reads or renders the connection's
server-managed or current-tab bearer.
- Saved Agent persistence and Session execution are separate contracts. Parsar
- `c15d42a2` can store explicit reasoning, non-`auto` service tiers, and JSON-schema
+ `d91ba48a` can store explicit reasoning, non-`auto` service tiers, and JSON-schema
text formats, but rejects each of them before creating a Session. Enabled
multi-agent configuration, saved-only tool types, deferred/invalid/duplicate
function or MCP identities, and MCP credentials without attached Vaults are
@@ -80,13 +88,9 @@ the Core key binding. Agents Core Web's local proxy owns the bearer server-side.
- Saved Agent names are limited to 128 Unicode characters. Agent metadata is limited
to 16 string pairs, 64 Unicode characters per key, and 512 per value; the Web
enforces those limits before a write.
-- The Environments overview filters the currently loaded Session collection to exact
- `self_hosted` projections. A matching durable/live observation may annotate that
- row, but an absent row or observation remains unknown. The view does not derive a
- list from database state, Environment IDs, executor registrations, or operator keys.
-- Parsar Core at the pinned revision has a narrow `self_hosted` profile for empty
- Session creation followed by constrained idle text input. The Web does not create
- that profile, but it safely renders selected Sessions that already carry one. This
+- Parsar Core at the audited revision supports Session-bound `self_hosted` data and
+ its documented event inputs. This Web does not create or connect that profile; it
+ safely renders selected Sessions that already carry one. That read-only projection
does not expand the missing Environment create/list/update/delete, template, or
file-operation surface.
- The reusable client distinguishes the admitted `self_hosted` request fields
@@ -161,7 +165,8 @@ the Core key binding. Agents Core Web's local proxy owns the bearer server-side.
multi-agent, executable-tool-shape, and unattached-credential incompatibilities
are authoritative at Session creation; a real Turn remains necessary to prove
model/provider execution and conditional Codex `low`/`high` verbosity support.
- Claude SDK accepts medium verbosity only.
+ Web never sends a paid Turn merely as a capability probe. Claude SDK accepts
+ medium verbosity only.
Environment creation and management beyond the narrow read, provider selection,
Files, Plugins, Skills, Artifacts, Vault, hosted runtimes, and Workspace lifecycle
@@ -297,14 +302,14 @@ read keeps the existing conversation usable.
observability. They are not per-Item timing, monetary cost, provider attribution,
or a complete OpenAI Trace waterfall.
-The client never retries a write automatically. For an input message that fails with
-a network/response-loss error, HTTP 5xx, or transient 408/409/425/429, the Web keeps
-the original payload and idempotency key in memory. Only a later user-initiated Send
-of the byte-for-byte unchanged payload reuses that key. Editing the payload, changing
-Session/Core, a successful response, or a permanent 4xx starts a new operation with a
-new key. This state is intentionally not stored in browser persistence, and the UI
-cannot prove whether an uncertain request was accepted until durable Core state
-reconciles.
+The client never retries a write automatically. For an input message that receives
+an unexpected non-204 2xx, loses its network/response, or fails with HTTP 5xx or a
+transient 408/409/425/429, the Web keeps the original payload and idempotency key in
+memory. Only a later user-initiated Send of the byte-for-byte unchanged payload
+reuses that key. Editing the payload, changing Session/Core, a successful response,
+or a permanent 4xx starts a new operation with a new key. This state is intentionally
+not stored in browser persistence, and the UI cannot prove whether an uncertain
+request was accepted until durable Core state reconciles.
HTTP acceptance, `/healthz`, an open SSE connection, and successful Agent creation
do not prove that a daemon, native harness, model ID, or provider credential can
diff --git a/packages/agents-client/src/client.test.ts b/packages/agents-client/src/client.test.ts
index 7b19dc3..c0aaecd 100644
--- a/packages/agents-client/src/client.test.ts
+++ b/packages/agents-client/src/client.test.ts
@@ -436,6 +436,38 @@ describe("OpenAIAgentsClient", () => {
});
});
+ it.each([
+ ["message", (client: OpenAIAgentsClient) => client.sendMessage("session", "hello", "event-key")],
+ ["cancel", (client: OpenAIAgentsClient) => client.cancelTurn("session", "event-key")],
+ ["tool result", (client: OpenAIAgentsClient) => client.submitFunctionResult("session", {
+ callId: "call_1",
+ turnId: "turn_1",
+ success: false,
+ error: "safe failure",
+ }, "event-key")],
+ ])("requires HTTP 204 for %s event submission without retrying", async (_label, submit) => {
+ const successCalls: FetchCall[] = [];
+ const successClient = new OpenAIAgentsClient({
+ fetch: recordingFetch(new Response(null, { status: 204 }), successCalls),
+ });
+
+ await expect(submit(successClient)).resolves.toBeUndefined();
+ expect(successCalls).toHaveLength(1);
+
+ for (const status of [200, 202]) {
+ const calls: FetchCall[] = [];
+ const client = new OpenAIAgentsClient({
+ fetch: recordingFetch(jsonResponse({ accepted: true }, status), calls),
+ });
+
+ await expect(submit(client)).rejects.toMatchObject({
+ status,
+ message: `Agent core request failed (${status}).`,
+ });
+ expect(calls).toHaveLength(1);
+ }
+ });
+
it("rejects stream=true before the JSON create method performs a request", async () => {
const calls: FetchCall[] = [];
const client = new OpenAIAgentsClient({
diff --git a/packages/agents-client/src/client.ts b/packages/agents-client/src/client.ts
index 42433e6..bb4087c 100644
--- a/packages/agents-client/src/client.ts
+++ b/packages/agents-client/src/client.ts
@@ -264,11 +264,15 @@ export class OpenAIAgentsClient implements AgentCore {
}
private submitEvents(sessionId: string, events: unknown[], idempotencyKey = randomKey()): Promise {
- return this.request(`/agents/sessions/${encodeURIComponent(sessionId)}/events`, {
- method: "POST",
- headers: { "Idempotency-Key": idempotencyKey },
- body: JSON.stringify({ events }),
- });
+ return this.request(
+ `/agents/sessions/${encodeURIComponent(sessionId)}/events`,
+ {
+ method: "POST",
+ headers: { "Idempotency-Key": idempotencyKey },
+ body: JSON.stringify({ events }),
+ },
+ 204,
+ );
}
sendMessage(sessionId: string, text: string, idempotencyKey?: string): Promise {