mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-15 10:05:18 +00:00
feat: add interactive permission prompt UI to Inspector
Add permission request handling to the Inspector UI so users can Allow, Always Allow, or Reject tool calls that require permissions instead of having them auto-cancelled. Wires up SDK onPermissionRequest/respondPermission through App → ChatPanel → ChatMessages with proper toolCallId-to-pendingId mapping. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
1720b6d0ba
commit
e8ffd78ac0
27 changed files with 812 additions and 563 deletions
24
CLAUDE.md
24
CLAUDE.md
|
|
@ -43,6 +43,12 @@
|
|||
- Canonical extension namespace/domain string is `sandboxagent.dev` (no hyphen).
|
||||
- Canonical custom ACP extension method prefix is `_sandboxagent/...` (no hyphen).
|
||||
|
||||
## Docs Terminology
|
||||
|
||||
- Never mention "ACP" in user-facing docs (`docs/**/*.mdx`) except in docs that are specifically about ACP itself (e.g. `docs/acp-http-client.mdx`).
|
||||
- Never expose underlying protocol method names (e.g. `session/request_permission`, `session/create`, `_sandboxagent/session/detach`) in non-ACP docs. Describe the behavior in user-facing terms instead.
|
||||
- Do not describe the underlying protocol implementation in docs. Only document the SDK surface (methods, types, options). ACP protocol details belong exclusively in ACP-specific pages.
|
||||
|
||||
## Architecture (Brief)
|
||||
|
||||
- HTTP contract and problem/error mapping: `server/packages/sandbox-agent/src/router.rs`
|
||||
|
|
@ -56,10 +62,15 @@
|
|||
- `acp-http-client`: protocol-pure ACP-over-HTTP (`/v1/acp`) with no Sandbox-specific HTTP helpers.
|
||||
- `sandbox-agent`: `SandboxAgent` SDK wrapper that combines ACP session operations with Sandbox control-plane and filesystem helpers.
|
||||
- `SandboxAgent` entry points are `SandboxAgent.connect(...)` and `SandboxAgent.start(...)`.
|
||||
- Stable Sandbox session methods are `createSession`, `resumeSession`, `resumeOrCreateSession`, `destroySession`, `sendSessionMethod`, `onSessionEvent`, `setSessionMode`, `setSessionModel`, `setSessionThoughtLevel`, `setSessionConfigOption`, `getSessionConfigOptions`, and `getSessionModes`.
|
||||
- `Session` helpers are `prompt(...)`, `send(...)`, `onEvent(...)`, `setMode(...)`, `setModel(...)`, `setThoughtLevel(...)`, `setConfigOption(...)`, `getConfigOptions()`, and `getModes()`.
|
||||
- Stable Sandbox session methods are `createSession`, `resumeSession`, `resumeOrCreateSession`, `destroySession`, `rawSendSessionMethod`, `onSessionEvent`, `setSessionMode`, `setSessionModel`, `setSessionThoughtLevel`, `setSessionConfigOption`, `getSessionConfigOptions`, `getSessionModes`, `respondPermission`, `rawRespondPermission`, and `onPermissionRequest`.
|
||||
- `Session` helpers are `prompt(...)`, `rawSend(...)`, `onEvent(...)`, `setMode(...)`, `setModel(...)`, `setThoughtLevel(...)`, `setConfigOption(...)`, `getConfigOptions()`, `getModes()`, `respondPermission(...)`, `rawRespondPermission(...)`, and `onPermissionRequest(...)`.
|
||||
- Cleanup is `sdk.dispose()`.
|
||||
|
||||
### TypeScript SDK Naming Conventions
|
||||
|
||||
- Use `respond<Thing>(id, reply)` for SDK methods that reply to an agent-initiated request (e.g. `respondPermission`). This is the standard pattern for answering any inbound JSON-RPC request from the agent.
|
||||
- Prefix raw/low-level escape hatches with `raw` (e.g. `rawRespondPermission`, `rawSend`). These accept protocol-level types directly and bypass SDK abstractions.
|
||||
|
||||
### Docs Source Of Truth
|
||||
|
||||
- For TypeScript docs/examples, source of truth is implementation in:
|
||||
|
|
@ -72,8 +83,17 @@
|
|||
- `server/packages/sandbox-agent/src/cli.rs`
|
||||
- Keep docs aligned to implemented endpoints/commands only (for example ACP under `/v1/acp`, not legacy `/v1/sessions` APIs).
|
||||
|
||||
## ACP Protocol Compliance
|
||||
|
||||
- Before adding any new ACP method, property, or config option category to the SDK, verify it exists in the ACP spec at `https://agentclientprotocol.com/llms-full.txt`.
|
||||
- Valid `SessionConfigOptionCategory` values are: `mode`, `model`, `thought_level`, `other`, or custom categories prefixed with `_` (e.g. `_permission_mode`).
|
||||
- Do not invent ACP properties or categories (e.g. `permission_mode` is not a valid ACP category — use `_permission_mode` if it's a custom extension, or use existing ACP mechanisms like `session/set_mode`).
|
||||
- `NewSessionRequest` only has `_meta`, `cwd`, and `mcpServers`. Do not add non-ACP fields to it.
|
||||
- Sandbox Agent SDK abstractions (like `SessionCreateRequest`) may add convenience properties, but must clearly map to real ACP methods internally and not send fabricated fields over the wire.
|
||||
|
||||
## Source Documents
|
||||
|
||||
- ACP protocol specification (full LLM-readable reference): `https://agentclientprotocol.com/llms-full.txt`
|
||||
- `~/misc/acp-docs/schema/schema.json`
|
||||
- `~/misc/acp-docs/schema/meta.json`
|
||||
- `research/acp/spec.md`
|
||||
|
|
|
|||
|
|
@ -118,7 +118,6 @@ const agents = await client.listAgents();
|
|||
await client.createSession("demo", {
|
||||
agent: "codex",
|
||||
agentMode: "default",
|
||||
permissionMode: "plan",
|
||||
});
|
||||
|
||||
await client.postMessage("demo", { message: "Hello from the SDK." });
|
||||
|
|
@ -128,9 +127,7 @@ for await (const event of client.streamEvents("demo", { offset: 0 })) {
|
|||
}
|
||||
```
|
||||
|
||||
`permissionMode: "acceptEdits"` passes through to Claude, auto-approves file changes for Codex, and is treated as `default` for other agents.
|
||||
|
||||
[SDK documentation](https://sandboxagent.dev/docs/sdks/typescript) — [Building a Chat UI](https://sandboxagent.dev/docs/building-chat-ui) — [Managing Sessions](https://sandboxagent.dev/docs/manage-sessions)
|
||||
[SDK documentation](https://sandboxagent.dev/docs/sdks/typescript) — [Managing Sessions](https://sandboxagent.dev/docs/manage-sessions)
|
||||
|
||||
### HTTP Server
|
||||
|
||||
|
|
|
|||
|
|
@ -96,14 +96,6 @@ const session = await sdk.createSession({
|
|||
});
|
||||
```
|
||||
|
||||
```ts
|
||||
// Claude permission modes are exposed as a first-class helper.
|
||||
const claude = await sdk.createSession({
|
||||
agent: "claude",
|
||||
permissionMode: "default",
|
||||
});
|
||||
```
|
||||
|
||||
```ts
|
||||
// After creation
|
||||
await session.setModel("gpt-5.2-codex");
|
||||
|
|
@ -111,10 +103,6 @@ await session.setMode("full-access");
|
|||
await session.setThoughtLevel("medium");
|
||||
```
|
||||
|
||||
```ts
|
||||
await claude.setPermissionMode("acceptEdits");
|
||||
```
|
||||
|
||||
Query available modes:
|
||||
|
||||
```ts
|
||||
|
|
@ -139,26 +127,41 @@ await session.setConfigOption("some-agent-option", "value");
|
|||
|
||||
## Handle permission requests
|
||||
|
||||
For agents that use ACP `session/request_permission` (for example Claude in `default` mode), register a permission listener and reply with `once`, `always`, or `reject`:
|
||||
For agents that request tool-use permissions, register a permission listener and reply with `once`, `always`, or `reject`:
|
||||
|
||||
```ts
|
||||
const claude = await sdk.createSession({
|
||||
const session = await sdk.createSession({
|
||||
agent: "claude",
|
||||
permissionMode: "default",
|
||||
mode: "default",
|
||||
});
|
||||
|
||||
claude.onPermissionRequest((request) => {
|
||||
session.onPermissionRequest((request) => {
|
||||
console.log(request.toolCall.title, request.availableReplies);
|
||||
void claude.replyPermission(request.id, "once");
|
||||
void session.respondPermission(request.id, "once");
|
||||
});
|
||||
|
||||
await claude.prompt([
|
||||
await session.prompt([
|
||||
{ type: "text", text: "Create ./permission-example.txt with the text hello." },
|
||||
]);
|
||||
```
|
||||
|
||||
|
||||
### Auto-approving permissions
|
||||
|
||||
To auto-approve all permission requests, respond with `"once"` or `"always"` in your listener:
|
||||
|
||||
```ts
|
||||
session.onPermissionRequest((request) => {
|
||||
void session.respondPermission(request.id, "always");
|
||||
});
|
||||
```
|
||||
|
||||
See `examples/permissions/src/index.ts` for a complete permissions example that works with Claude and Codex.
|
||||
|
||||
<Info>
|
||||
Some agents like Claude allow configuring permission behavior through modes (e.g. `bypassPermissions`, `acceptEdits`). We recommend leaving the mode as `default` and handling permission decisions explicitly in `onPermissionRequest` instead.
|
||||
</Info>
|
||||
|
||||
## Destroy a session
|
||||
|
||||
```ts
|
||||
|
|
|
|||
|
|
@ -58,4 +58,4 @@ Use the filesystem API to upload files, then include file references in prompt c
|
|||
|
||||
- Use absolute file URIs in `resource_link` blocks.
|
||||
- If `mimeType` is omitted, the agent/runtime may infer a default.
|
||||
- Support for non-text resources depends on each agent's ACP prompt capabilities.
|
||||
- Support for non-text resources depends on each agent's prompt capabilities.
|
||||
|
|
|
|||
|
|
@ -1,370 +0,0 @@
|
|||
---
|
||||
title: "Building a Chat UI"
|
||||
description: "Build a chat interface using the universal event stream."
|
||||
icon: "comments"
|
||||
---
|
||||
|
||||
## Setup
|
||||
|
||||
### List agents
|
||||
|
||||
```ts
|
||||
const { agents } = await client.listAgents();
|
||||
|
||||
// Each agent exposes feature coverage via `capabilities` to determine what UI to show
|
||||
const claude = agents.find((a) => a.id === "claude");
|
||||
if (claude?.capabilities.permissions) {
|
||||
// Show permission approval UI
|
||||
}
|
||||
if (claude?.capabilities.questions) {
|
||||
// Show question response UI
|
||||
}
|
||||
```
|
||||
|
||||
### Create a session
|
||||
|
||||
```ts
|
||||
const sessionId = `session-${crypto.randomUUID()}`;
|
||||
|
||||
await client.createSession(sessionId, {
|
||||
agent: "claude",
|
||||
agentMode: "code", // Optional: agent-specific mode
|
||||
permissionMode: "default", // Optional: "default" | "plan" | "bypass" | "acceptEdits" (Claude: accept edits; Codex: auto-approve file changes; others: default)
|
||||
model: "claude-sonnet-4", // Optional: model override
|
||||
});
|
||||
```
|
||||
|
||||
### Send a message
|
||||
|
||||
```ts
|
||||
await client.postMessage(sessionId, { message: "Hello, world!" });
|
||||
```
|
||||
|
||||
### Stream events
|
||||
|
||||
Three options for receiving events:
|
||||
|
||||
```ts
|
||||
// Option 1: SSE (recommended for real-time UI)
|
||||
const stream = client.streamEvents(sessionId, { offset: 0 });
|
||||
for await (const event of stream) {
|
||||
handleEvent(event);
|
||||
}
|
||||
|
||||
// Option 2: Polling
|
||||
const { events, hasMore } = await client.getEvents(sessionId, { offset: 0 });
|
||||
events.forEach(handleEvent);
|
||||
|
||||
// Option 3: Turn streaming (send + stream in one call)
|
||||
const stream = client.streamTurn(sessionId, { message: "Hello" });
|
||||
for await (const event of stream) {
|
||||
handleEvent(event);
|
||||
}
|
||||
```
|
||||
|
||||
Use `offset` to track the last seen `sequence` number and resume from where you left off.
|
||||
|
||||
---
|
||||
|
||||
## Handling Events
|
||||
|
||||
### Bare minimum
|
||||
|
||||
Handle item lifecycle plus turn lifecycle to render a basic chat:
|
||||
|
||||
```ts
|
||||
type ItemState = {
|
||||
item: UniversalItem;
|
||||
deltas: string[];
|
||||
};
|
||||
|
||||
const items = new Map<string, ItemState>();
|
||||
let turnInProgress = false;
|
||||
|
||||
function handleEvent(event: UniversalEvent) {
|
||||
switch (event.type) {
|
||||
case "turn.started": {
|
||||
turnInProgress = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case "turn.ended": {
|
||||
turnInProgress = false;
|
||||
break;
|
||||
}
|
||||
|
||||
case "item.started": {
|
||||
const { item } = event.data as ItemEventData;
|
||||
items.set(item.item_id, { item, deltas: [] });
|
||||
break;
|
||||
}
|
||||
|
||||
case "item.delta": {
|
||||
const { item_id, delta } = event.data as ItemDeltaData;
|
||||
const state = items.get(item_id);
|
||||
if (state) {
|
||||
state.deltas.push(delta);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "item.completed": {
|
||||
const { item } = event.data as ItemEventData;
|
||||
const state = items.get(item.item_id);
|
||||
if (state) {
|
||||
state.item = item;
|
||||
state.deltas = []; // Clear deltas, use final content
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When rendering:
|
||||
- Use `turnInProgress` for turn-level UI state (disable send button, show global "Agent is responding", etc.).
|
||||
- Use `item.status === "in_progress"` for per-item streaming state.
|
||||
|
||||
```ts
|
||||
function renderItem(state: ItemState) {
|
||||
const { item, deltas } = state;
|
||||
const isItemLoading = item.status === "in_progress";
|
||||
|
||||
// For streaming text, combine item content with accumulated deltas
|
||||
const text = item.content
|
||||
.filter((p) => p.type === "text")
|
||||
.map((p) => p.text)
|
||||
.join("");
|
||||
const streamedText = text + deltas.join("");
|
||||
|
||||
return {
|
||||
content: streamedText,
|
||||
isItemLoading,
|
||||
isTurnLoading: turnInProgress,
|
||||
role: item.role,
|
||||
kind: item.kind,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Extra events
|
||||
|
||||
Handle these for a complete implementation:
|
||||
|
||||
```ts
|
||||
function handleEvent(event: UniversalEvent) {
|
||||
switch (event.type) {
|
||||
// ... bare minimum events above ...
|
||||
|
||||
case "session.started": {
|
||||
// Session is ready
|
||||
break;
|
||||
}
|
||||
|
||||
case "session.ended": {
|
||||
const { reason, terminated_by } = event.data as SessionEndedData;
|
||||
// Disable input, show end reason
|
||||
// reason: "completed" | "error" | "terminated"
|
||||
// terminated_by: "agent" | "daemon"
|
||||
break;
|
||||
}
|
||||
|
||||
case "error": {
|
||||
const { message, code } = event.data as ErrorData;
|
||||
// Display error to user
|
||||
break;
|
||||
}
|
||||
|
||||
case "agent.unparsed": {
|
||||
const { error, location } = event.data as AgentUnparsedData;
|
||||
// Parsing failure - treat as bug in development
|
||||
console.error(`Parse error at ${location}: ${error}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Content parts
|
||||
|
||||
Each item has `content` parts. Render based on `type`:
|
||||
|
||||
```ts
|
||||
function renderContentPart(part: ContentPart) {
|
||||
switch (part.type) {
|
||||
case "text":
|
||||
return <Markdown>{part.text}</Markdown>;
|
||||
|
||||
case "tool_call":
|
||||
return <ToolCall name={part.name} args={part.arguments} />;
|
||||
|
||||
case "tool_result":
|
||||
return <ToolResult output={part.output} />;
|
||||
|
||||
case "file_ref":
|
||||
return <FileChange path={part.path} action={part.action} diff={part.diff} />;
|
||||
|
||||
case "reasoning":
|
||||
return <Reasoning>{part.text}</Reasoning>;
|
||||
|
||||
case "status":
|
||||
return <Status label={part.label} detail={part.detail} />;
|
||||
|
||||
case "image":
|
||||
return <Image src={part.path} />;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Handling Permissions
|
||||
|
||||
When `permission.requested` arrives, show an approval UI:
|
||||
|
||||
```ts
|
||||
const pendingPermissions = new Map<string, PermissionEventData>();
|
||||
|
||||
function handleEvent(event: UniversalEvent) {
|
||||
if (event.type === "permission.requested") {
|
||||
const data = event.data as PermissionEventData;
|
||||
pendingPermissions.set(data.permission_id, data);
|
||||
}
|
||||
|
||||
if (event.type === "permission.resolved") {
|
||||
const data = event.data as PermissionEventData;
|
||||
pendingPermissions.delete(data.permission_id);
|
||||
}
|
||||
}
|
||||
|
||||
// User clicks approve/deny
|
||||
async function replyPermission(id: string, reply: "once" | "always" | "reject") {
|
||||
await client.replyPermission(sessionId, id, { reply });
|
||||
pendingPermissions.delete(id);
|
||||
}
|
||||
```
|
||||
|
||||
Render permission requests:
|
||||
|
||||
```ts
|
||||
function PermissionRequest({ data }: { data: PermissionEventData }) {
|
||||
return (
|
||||
<div>
|
||||
<p>Allow: {data.action}</p>
|
||||
<button onClick={() => replyPermission(data.permission_id, "once")}>
|
||||
Allow Once
|
||||
</button>
|
||||
<button onClick={() => replyPermission(data.permission_id, "always")}>
|
||||
Always Allow
|
||||
</button>
|
||||
<button onClick={() => replyPermission(data.permission_id, "reject")}>
|
||||
Reject
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Handling Questions
|
||||
|
||||
When `question.requested` arrives, show a selection UI:
|
||||
|
||||
```ts
|
||||
const pendingQuestions = new Map<string, QuestionEventData>();
|
||||
|
||||
function handleEvent(event: UniversalEvent) {
|
||||
if (event.type === "question.requested") {
|
||||
const data = event.data as QuestionEventData;
|
||||
pendingQuestions.set(data.question_id, data);
|
||||
}
|
||||
|
||||
if (event.type === "question.resolved") {
|
||||
const data = event.data as QuestionEventData;
|
||||
pendingQuestions.delete(data.question_id);
|
||||
}
|
||||
}
|
||||
|
||||
// User selects answer(s)
|
||||
async function answerQuestion(id: string, answers: string[][]) {
|
||||
await client.replyQuestion(sessionId, id, { answers });
|
||||
pendingQuestions.delete(id);
|
||||
}
|
||||
|
||||
async function rejectQuestion(id: string) {
|
||||
await client.rejectQuestion(sessionId, id);
|
||||
pendingQuestions.delete(id);
|
||||
}
|
||||
```
|
||||
|
||||
Render question requests:
|
||||
|
||||
```ts
|
||||
function QuestionRequest({ data }: { data: QuestionEventData }) {
|
||||
const [selected, setSelected] = useState<string[]>([]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>{data.prompt}</p>
|
||||
{data.options.map((option) => (
|
||||
<label key={option}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selected.includes(option)}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
setSelected([...selected, option]);
|
||||
} else {
|
||||
setSelected(selected.filter((s) => s !== option));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{option}
|
||||
</label>
|
||||
))}
|
||||
<button onClick={() => answerQuestion(data.question_id, [selected])}>
|
||||
Submit
|
||||
</button>
|
||||
<button onClick={() => rejectQuestion(data.question_id)}>
|
||||
Reject
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing with Mock Agent
|
||||
|
||||
The `mock` agent lets you test UI behaviors without external credentials:
|
||||
|
||||
```ts
|
||||
await client.createSession("test-session", { agent: "mock" });
|
||||
```
|
||||
|
||||
Send `help` to see available commands:
|
||||
|
||||
| Command | Tests |
|
||||
|---------|-------|
|
||||
| `help` | Lists all commands |
|
||||
| `demo` | Full UI coverage sequence with markers |
|
||||
| `markdown` | Streaming markdown rendering |
|
||||
| `tool` | Tool call + result with file refs |
|
||||
| `status` | Status item updates |
|
||||
| `image` | Image content part |
|
||||
| `permission` | Permission request flow |
|
||||
| `question` | Question request flow |
|
||||
| `error` | Error + unparsed events |
|
||||
| `end` | Session ended event |
|
||||
| `echo <text>` | Echo text as assistant message |
|
||||
|
||||
Any unrecognized text is echoed back as an assistant message.
|
||||
|
||||
---
|
||||
|
||||
## Reference Implementation
|
||||
|
||||
The [Inspector UI](https://github.com/rivet-dev/sandbox-agent/blob/main/frontend/packages/inspector/src/App.tsx)
|
||||
is a complete reference showing session management, event rendering, and HITL flows.
|
||||
|
|
@ -181,7 +181,7 @@ sandbox-agent api agents list
|
|||
|
||||
#### api agents report
|
||||
|
||||
Emit a JSON report of available models, modes, and thought levels for every agent. Calls `GET /v1/agents?config=true` and groups each agent's config options by category.
|
||||
Emit a JSON report of available models, modes, and thought levels for every agent, grouped by category.
|
||||
|
||||
```bash
|
||||
sandbox-agent api agents report --endpoint http://127.0.0.1:2468 | jq .
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ When prompting, Sandbox Agent does not pre-validate provider credentials. Agent-
|
|||
|
||||
### API
|
||||
|
||||
`GET /v1/agents` includes `credentialsAvailable` per agent.
|
||||
`sdk.listAgents()` includes `credentialsAvailable` per agent.
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
|
|||
|
|
@ -115,8 +115,8 @@ This keeps all Sandbox Agent calls inside the Cloudflare sandbox routing path an
|
|||
## Troubleshooting streaming updates
|
||||
|
||||
If you only receive:
|
||||
- outbound `session/prompt`
|
||||
- final `{ stopReason: "end_turn" }`
|
||||
- the outbound prompt request
|
||||
- the final `{ stopReason: "end_turn" }` response
|
||||
|
||||
then the streamed update channel dropped. In Cloudflare sandbox paths, this is typically caused by forwarding `AbortSignal` from SDK fetch init into `containerFetch(...)`.
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ icon: "database"
|
|||
|
||||
Sandbox Agent stores sessions in memory only. When the server restarts or the sandbox is destroyed, all session data is lost. It's your responsibility to persist events to your own database.
|
||||
|
||||
See the [Building a Chat UI](/building-chat-ui) guide for understanding session lifecycle events like `session.started` and `session.ended`.
|
||||
|
||||
## Recommended approach
|
||||
|
||||
1. Store events to your database as they arrive
|
||||
|
|
@ -18,11 +16,11 @@ This prevents duplicate writes and lets you recover from disconnects.
|
|||
|
||||
## Receiving Events
|
||||
|
||||
Two ways to receive events: SSE streaming (recommended) or polling.
|
||||
Two ways to receive events: streaming (recommended) or polling.
|
||||
|
||||
### Streaming
|
||||
|
||||
Use SSE for real-time events with automatic reconnection support.
|
||||
Use streaming for real-time events with automatic reconnection support.
|
||||
|
||||
```typescript
|
||||
import { SandboxAgentClient } from "sandbox-agent";
|
||||
|
|
@ -44,7 +42,7 @@ for await (const event of client.streamEvents("my-session", { offset })) {
|
|||
|
||||
### Polling
|
||||
|
||||
If you can't use SSE streaming, poll the events endpoint:
|
||||
If you can't use streaming, poll the events endpoint:
|
||||
|
||||
```typescript
|
||||
const lastEvent = await db.getLastEvent("my-session");
|
||||
|
|
@ -244,7 +242,7 @@ const events = await redis.lrange(`session:${sessionId}`, offset, -1);
|
|||
|
||||
## Handling disconnects
|
||||
|
||||
The SSE stream may disconnect due to network issues. Handle reconnection gracefully:
|
||||
The event stream may disconnect due to network issues. Handle reconnection gracefully:
|
||||
|
||||
```typescript
|
||||
async function streamWithRetry(sessionId: string) {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ The process API supports:
|
|||
|
||||
- **One-shot execution** — run a command to completion and capture stdout, stderr, and exit code
|
||||
- **Managed processes** — spawn, list, stop, kill, and delete long-lived processes
|
||||
- **Log streaming** — fetch buffered logs or follow live output via SSE
|
||||
- **Log streaming** — fetch buffered logs or follow live output
|
||||
- **Terminals** — full PTY support with bidirectional WebSocket I/O
|
||||
- **Configurable limits** — control concurrency, timeouts, and buffer sizes per runtime
|
||||
|
||||
|
|
@ -155,7 +155,7 @@ curl "http://127.0.0.1:2468/v1/processes/proc_1/logs?tail=50&stream=combined"
|
|||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Follow logs via SSE
|
||||
### Follow logs
|
||||
|
||||
Stream log entries in real time. The subscription replays buffered entries first, then streams new output as it arrives.
|
||||
|
||||
|
|
|
|||
|
|
@ -138,16 +138,16 @@ const options = await session.getConfigOptions();
|
|||
const modes = await session.getModes();
|
||||
```
|
||||
|
||||
Permission modes use the same surface:
|
||||
Handle permission requests from agents that ask before executing tools:
|
||||
|
||||
```ts
|
||||
const claude = await sdk.createSession({
|
||||
agent: "claude",
|
||||
permissionMode: "default",
|
||||
mode: "default",
|
||||
});
|
||||
|
||||
claude.onPermissionRequest((request) => {
|
||||
void claude.replyPermission(request.id, "once");
|
||||
void claude.respondPermission(request.id, "once");
|
||||
});
|
||||
```
|
||||
|
||||
|
|
@ -222,6 +222,6 @@ Parameters:
|
|||
- `baseUrl` (required unless `fetch` is provided): Sandbox Agent server URL
|
||||
- `token` (optional): Bearer token for authenticated servers
|
||||
- `headers` (optional): Additional request headers
|
||||
- `fetch` (optional): Custom fetch implementation used by SDK HTTP and ACP calls
|
||||
- `waitForHealth` (optional, defaults to enabled): waits for `/v1/health` before HTTP helpers and ACP session setup proceed; pass `false` to disable or `{ timeoutMs }` to bound the wait
|
||||
- `fetch` (optional): Custom fetch implementation used by SDK HTTP and session calls
|
||||
- `waitForHealth` (optional, defaults to enabled): waits for `/v1/health` before HTTP helpers and session setup proceed; pass `false` to disable or `{ timeoutMs }` to bound the wait
|
||||
- `signal` (optional): aborts the startup `/v1/health` wait used by `connect()`
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ export function App() {
|
|||
if (event.type === "permission.requested") {
|
||||
const data = event.data as PermissionEventData;
|
||||
log(`[Auto-approved] ${data.action}`);
|
||||
await client.replyPermission(sessionIdRef.current, data.permission_id, { reply: "once" });
|
||||
await client.respondPermission(sessionIdRef.current, data.permission_id, { reply: "once" });
|
||||
}
|
||||
|
||||
// Reject questions (don't support interactive input)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import {
|
|||
|
||||
const options = parseOptions();
|
||||
const agent = options.agent.trim().toLowerCase();
|
||||
const permissionMode = options.permissionMode.trim();
|
||||
const autoReply = parsePermissionReply(options.reply);
|
||||
const promptText =
|
||||
options.prompt?.trim() ||
|
||||
|
|
@ -32,9 +31,14 @@ try {
|
|||
: [];
|
||||
const modeOption = configOptions.find((option) => option.category === "mode");
|
||||
const availableModes = extractOptionValues(modeOption);
|
||||
const mode =
|
||||
options.mode?.trim() ||
|
||||
(typeof modeOption?.currentValue === "string" ? modeOption.currentValue : "") ||
|
||||
availableModes[0] ||
|
||||
"";
|
||||
|
||||
console.log(`Agent: ${agent}`);
|
||||
console.log(`Permission mode: ${permissionMode}`);
|
||||
console.log(`Mode: ${mode || "(default)"}`);
|
||||
if (availableModes.length > 0) {
|
||||
console.log(`Available modes: ${availableModes.join(", ")}`);
|
||||
}
|
||||
|
|
@ -48,7 +52,7 @@ try {
|
|||
|
||||
const session = await sdk.createSession({
|
||||
agent,
|
||||
permissionMode,
|
||||
...(mode ? { mode } : {}),
|
||||
sessionInit: {
|
||||
cwd: process.cwd(),
|
||||
mcpServers: [],
|
||||
|
|
@ -76,7 +80,7 @@ try {
|
|||
|
||||
async function handlePermissionRequest(
|
||||
session: {
|
||||
replyPermission(permissionId: string, reply: PermissionReply): Promise<void>;
|
||||
respondPermission(permissionId: string, reply: PermissionReply): Promise<void>;
|
||||
},
|
||||
request: SessionPermissionRequest,
|
||||
auto: PermissionReply | null,
|
||||
|
|
@ -84,7 +88,7 @@ async function handlePermissionRequest(
|
|||
): Promise<void> {
|
||||
const reply = auto ?? (await promptForReply(request, rl));
|
||||
console.log(`Permission ${reply}: ${request.toolCall.title ?? request.toolCall.toolCallId}`);
|
||||
await session.replyPermission(request.id, reply);
|
||||
await session.respondPermission(request.id, reply);
|
||||
}
|
||||
|
||||
async function promptForReply(
|
||||
|
|
@ -163,7 +167,7 @@ function parsePermissionReply(value: string | undefined): PermissionReply | null
|
|||
|
||||
function parseOptions(): {
|
||||
agent: string;
|
||||
permissionMode: string;
|
||||
mode?: string;
|
||||
prompt?: string;
|
||||
reply?: string;
|
||||
} {
|
||||
|
|
@ -172,16 +176,16 @@ function parseOptions(): {
|
|||
const program = new Command();
|
||||
program
|
||||
.name("permissions")
|
||||
.description("Run a permissions example against an ACP agent session.")
|
||||
.description("Run a permissions example against an agent session.")
|
||||
.requiredOption("--agent <agent>", "Agent to run, for example 'claude' or 'codex'")
|
||||
.requiredOption("--permission-mode <mode>", "Permission mode to configure for the session")
|
||||
.option("--mode <mode>", "Mode to configure for the session (uses agent default if omitted)")
|
||||
.option("--prompt <text>", "Prompt to send after the session starts")
|
||||
.option("--reply <reply>", "Automatically answer permission prompts with once, always, or reject");
|
||||
|
||||
program.parse(normalizedArgv, { from: "user" });
|
||||
return program.opts<{
|
||||
agent: string;
|
||||
permissionMode: string;
|
||||
mode?: string;
|
||||
prompt?: string;
|
||||
reply?: string;
|
||||
}>();
|
||||
|
|
|
|||
|
|
@ -81,11 +81,12 @@ For all Rivet/RivetKit implementation:
|
|||
- Do not add `workspaceId`/`repoId`/`handoffId` columns just to "namespace" rows for a given actor instance; use actor state and/or the actor key instead.
|
||||
- Example: the `handoff` actor instance already represents `(workspaceId, repoId, handoffId)`, so its SQLite tables should not need those columns for primary keys.
|
||||
3. Do not use backend-global SQLite singletons; database access must go through actor `db` providers (`c.db`).
|
||||
4. Do not use published RivetKit npm packages.
|
||||
5. RivetKit is linked via pnpm `link:` protocol to `../rivet/rivetkit-typescript/packages/rivetkit`. Sub-packages (`@rivetkit/sqlite-vfs`, etc.) resolve transitively from the rivet workspace.
|
||||
4. The default dependency source for RivetKit is the published `rivetkit` package so workspace installs and CI remain self-contained.
|
||||
5. When working on coordinated RivetKit changes, you may temporarily relink to a local checkout instead of the published package.
|
||||
- Dedicated local checkout for this workspace: `/Users/nathan/conductor/workspaces/handoff/rivet-checkout`
|
||||
- Dev worktree note: when working on RivetKit fixes for this repo, prefer the dedicated local checkout above and link to `../rivet-checkout/rivetkit-typescript/packages/rivetkit`.
|
||||
6. Before using, build RivetKit in the rivet repo:
|
||||
- Preferred local link target: `../rivet-checkout/rivetkit-typescript/packages/rivetkit`
|
||||
- Sub-packages (`@rivetkit/sqlite-vfs`, etc.) resolve transitively from the RivetKit workspace when using the local checkout.
|
||||
6. Before using a local checkout, build RivetKit in the rivet repo:
|
||||
```bash
|
||||
cd ../rivet-checkout/rivetkit-typescript
|
||||
pnpm install
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
"drizzle-orm": "^0.44.5",
|
||||
"hono": "^4.11.9",
|
||||
"pino": "^10.3.1",
|
||||
"rivetkit": "link:../../../../../handoff/rivet-checkout/rivetkit-typescript/packages/rivetkit",
|
||||
"rivetkit": "2.1.6",
|
||||
"sandbox-agent": "workspace:*",
|
||||
"uuid": "^13.0.0",
|
||||
"zod": "^4.1.5"
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@openhandoff/shared": "workspace:*",
|
||||
"rivetkit": "link:../../../../../handoff/rivet-checkout/rivetkit-typescript/packages/rivetkit"
|
||||
"rivetkit": "2.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsup": "^8.5.0"
|
||||
|
|
|
|||
|
|
@ -1595,6 +1595,118 @@
|
|||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Permission prompt */
|
||||
.permission-prompt {
|
||||
margin: 8px 0;
|
||||
padding: 12px 14px;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.permission-prompt.resolved {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.permission-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.permission-icon {
|
||||
color: var(--warning, #f59e0b);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.permission-prompt.resolved .permission-icon {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.permission-title {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.permission-description {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 8px;
|
||||
padding-left: 22px;
|
||||
}
|
||||
|
||||
.permission-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
padding-left: 22px;
|
||||
}
|
||||
|
||||
.permission-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--border);
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: background var(--transition), color var(--transition), border-color var(--transition);
|
||||
}
|
||||
|
||||
.permission-btn:hover:not(:disabled) {
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.permission-btn.allow:hover:not(:disabled) {
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
border-color: rgba(34, 197, 94, 0.3);
|
||||
color: #22c55e;
|
||||
}
|
||||
|
||||
.permission-btn.reject:hover:not(:disabled) {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
border-color: rgba(239, 68, 68, 0.3);
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.permission-btn:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.permission-btn.selected {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.permission-btn.selected.allow {
|
||||
background: rgba(34, 197, 94, 0.15);
|
||||
border-color: rgba(34, 197, 94, 0.4);
|
||||
color: #22c55e;
|
||||
}
|
||||
|
||||
.permission-btn.selected.reject {
|
||||
background: rgba(239, 68, 68, 0.15);
|
||||
border-color: rgba(239, 68, 68, 0.4);
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.permission-btn.dimmed {
|
||||
opacity: 0.35;
|
||||
}
|
||||
|
||||
.permission-auto-resolved {
|
||||
font-size: 11px;
|
||||
color: var(--muted);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.status-divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import {
|
|||
type AgentInfo,
|
||||
type SessionEvent,
|
||||
type Session,
|
||||
type SessionPermissionRequest,
|
||||
type PermissionReply,
|
||||
InMemorySessionPersistDriver,
|
||||
type SessionPersistDriver,
|
||||
} from "sandbox-agent";
|
||||
|
|
@ -295,6 +297,11 @@ export default function App() {
|
|||
const clientRef = useRef<SandboxAgent | null>(null);
|
||||
const activeSessionRef = useRef<Session | null>(null);
|
||||
const eventUnsubRef = useRef<(() => void) | null>(null);
|
||||
const permissionUnsubRef = useRef<(() => void) | null>(null);
|
||||
const pendingPermissionsRef = useRef<Map<string, SessionPermissionRequest>>(new Map());
|
||||
const permissionToolCallToIdRef = useRef<Map<string, string>>(new Map());
|
||||
const [pendingPermissionIds, setPendingPermissionIds] = useState<Set<string>>(new Set());
|
||||
const [resolvedPermissions, setResolvedPermissions] = useState<Map<string, string>>(new Map());
|
||||
const sessionEventsCacheRef = useRef<Map<string, SessionEvent[]>>(new Map());
|
||||
const selectedSessionIdRef = useRef(sessionId);
|
||||
const resumeInFlightSessionIdRef = useRef<string | null>(null);
|
||||
|
|
@ -538,8 +545,45 @@ export default function App() {
|
|||
});
|
||||
});
|
||||
eventUnsubRef.current = unsub;
|
||||
|
||||
// Subscribe to permission requests
|
||||
if (permissionUnsubRef.current) {
|
||||
permissionUnsubRef.current();
|
||||
permissionUnsubRef.current = null;
|
||||
}
|
||||
const permUnsub = session.onPermissionRequest((request: SessionPermissionRequest) => {
|
||||
if (!isCurrentSubscription()) return;
|
||||
pendingPermissionsRef.current.set(request.id, request);
|
||||
if (request.toolCall?.toolCallId) {
|
||||
permissionToolCallToIdRef.current.set(request.toolCall.toolCallId, request.id);
|
||||
}
|
||||
setPendingPermissionIds((prev) => new Set([...prev, request.id]));
|
||||
});
|
||||
permissionUnsubRef.current = permUnsub;
|
||||
}, [getClient]);
|
||||
|
||||
const handlePermissionReply = useCallback(async (permissionId: string, reply: PermissionReply) => {
|
||||
const session = activeSessionRef.current;
|
||||
if (!session) return;
|
||||
try {
|
||||
await session.respondPermission(permissionId, reply);
|
||||
const request = pendingPermissionsRef.current.get(permissionId);
|
||||
const selectedOption = request?.options.find((o) =>
|
||||
reply === "always" ? o.kind === "allow_always" :
|
||||
reply === "once" ? o.kind === "allow_once" :
|
||||
o.kind === "reject_once" || o.kind === "reject_always"
|
||||
);
|
||||
setResolvedPermissions((prev) => new Map([...prev, [permissionId, selectedOption?.optionId ?? reply]]));
|
||||
setPendingPermissionIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.delete(permissionId);
|
||||
return next;
|
||||
});
|
||||
} catch (error) {
|
||||
pushErrorToast(error, "Failed to respond to permission request");
|
||||
}
|
||||
}, [pushErrorToast]);
|
||||
|
||||
const connectToDaemon = async (reportError: boolean, overrideEndpoint?: string) => {
|
||||
setConnecting(true);
|
||||
if (reportError) {
|
||||
|
|
@ -551,6 +595,10 @@ export default function App() {
|
|||
eventUnsubRef.current();
|
||||
eventUnsubRef.current = null;
|
||||
}
|
||||
if (permissionUnsubRef.current) {
|
||||
permissionUnsubRef.current();
|
||||
permissionUnsubRef.current = null;
|
||||
}
|
||||
subscriptionGenerationRef.current += 1;
|
||||
activeSessionRef.current = null;
|
||||
if (clientRef.current) {
|
||||
|
|
@ -603,6 +651,10 @@ export default function App() {
|
|||
eventUnsubRef.current();
|
||||
eventUnsubRef.current = null;
|
||||
}
|
||||
if (permissionUnsubRef.current) {
|
||||
permissionUnsubRef.current();
|
||||
permissionUnsubRef.current = null;
|
||||
}
|
||||
subscriptionGenerationRef.current += 1;
|
||||
activeSessionRef.current = null;
|
||||
if (clientRef.current) {
|
||||
|
|
@ -880,6 +932,10 @@ export default function App() {
|
|||
eventUnsubRef.current();
|
||||
eventUnsubRef.current = null;
|
||||
}
|
||||
if (permissionUnsubRef.current) {
|
||||
permissionUnsubRef.current();
|
||||
permissionUnsubRef.current = null;
|
||||
}
|
||||
activeSessionRef.current = null;
|
||||
await fetchSessions();
|
||||
} catch (error) {
|
||||
|
|
@ -1165,6 +1221,44 @@ export default function App() {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (event.sender === "agent" && method === "session/request_permission") {
|
||||
const params = payload.params as {
|
||||
options?: Array<{ optionId: string; name: string; kind: string }>;
|
||||
toolCall?: { title?: string; toolCallId?: string; description?: string };
|
||||
} | undefined;
|
||||
const toolCallId = params?.toolCall?.toolCallId;
|
||||
const sdkPermissionId = toolCallId
|
||||
? permissionToolCallToIdRef.current.get(toolCallId)
|
||||
: undefined;
|
||||
const permissionId = sdkPermissionId
|
||||
?? (typeof payload.id === "number" || typeof payload.id === "string"
|
||||
? String(payload.id)
|
||||
: event.id);
|
||||
const options = (params?.options ?? []).map((o) => ({
|
||||
optionId: o.optionId,
|
||||
name: o.name,
|
||||
kind: o.kind,
|
||||
}));
|
||||
const title = params?.toolCall?.title ?? params?.toolCall?.toolCallId ?? "Permission request";
|
||||
const resolved = resolvedPermissions.get(permissionId);
|
||||
const isPending = pendingPermissionIds.has(permissionId);
|
||||
entries.push({
|
||||
id: event.id,
|
||||
eventId: event.id,
|
||||
kind: "permission",
|
||||
time,
|
||||
permission: {
|
||||
permissionId,
|
||||
title,
|
||||
description: params?.toolCall?.description,
|
||||
options,
|
||||
resolved: resolved != null || !isPending,
|
||||
selectedOptionId: resolved,
|
||||
},
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (event.sender === "agent" && method === "_sandboxagent/agent/unparsed") {
|
||||
const params = payload.params as { error?: string; location?: string } | undefined;
|
||||
entries.push({
|
||||
|
|
@ -1194,7 +1288,7 @@ export default function App() {
|
|||
}
|
||||
|
||||
return entries;
|
||||
}, [events]);
|
||||
}, [events, pendingPermissionIds, resolvedPermissions]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
|
|
@ -1202,6 +1296,10 @@ export default function App() {
|
|||
eventUnsubRef.current();
|
||||
eventUnsubRef.current = null;
|
||||
}
|
||||
if (permissionUnsubRef.current) {
|
||||
permissionUnsubRef.current();
|
||||
permissionUnsubRef.current = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
|
@ -1684,6 +1782,7 @@ export default function App() {
|
|||
isThinking={isThinking}
|
||||
agentId={agentId}
|
||||
tokenUsage={tokenUsage}
|
||||
onPermissionReply={handlePermissionReply}
|
||||
/>
|
||||
|
||||
<DebugPanel
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useState } from "react";
|
||||
import { getMessageClass } from "./messageUtils";
|
||||
import type { TimelineEntry } from "./types";
|
||||
import { AlertTriangle, ChevronRight, ChevronDown, Wrench, Brain, Info, ExternalLink, PlayCircle } from "lucide-react";
|
||||
import { AlertTriangle, ChevronRight, ChevronDown, Wrench, Brain, Info, ExternalLink, PlayCircle, Shield, Check, X } from "lucide-react";
|
||||
import MarkdownText from "./MarkdownText";
|
||||
|
||||
const ToolItem = ({
|
||||
|
|
@ -170,6 +170,73 @@ const ToolGroup = ({ entries, onEventClick }: { entries: TimelineEntry[]; onEven
|
|||
);
|
||||
};
|
||||
|
||||
const PermissionPrompt = ({
|
||||
entry,
|
||||
onPermissionReply,
|
||||
}: {
|
||||
entry: TimelineEntry;
|
||||
onPermissionReply?: (permissionId: string, reply: "once" | "always" | "reject") => void;
|
||||
}) => {
|
||||
const perm = entry.permission;
|
||||
if (!perm) return null;
|
||||
|
||||
const resolved = perm.resolved;
|
||||
const selectedId = perm.selectedOptionId;
|
||||
|
||||
const replyForOption = (kind: string): "once" | "always" | "reject" => {
|
||||
if (kind === "allow_once") return "once";
|
||||
if (kind === "allow_always") return "always";
|
||||
return "reject";
|
||||
};
|
||||
|
||||
const labelForKind = (kind: string, name: string): string => {
|
||||
if (name) return name;
|
||||
if (kind === "allow_once") return "Allow Once";
|
||||
if (kind === "allow_always") return "Always Allow";
|
||||
if (kind === "reject_once") return "Reject";
|
||||
if (kind === "reject_always") return "Reject Always";
|
||||
return kind;
|
||||
};
|
||||
|
||||
const classForKind = (kind: string): string => {
|
||||
if (kind.startsWith("allow")) return "allow";
|
||||
return "reject";
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`permission-prompt ${resolved ? "resolved" : ""}`}>
|
||||
<div className="permission-header">
|
||||
<Shield size={14} className="permission-icon" />
|
||||
<span className="permission-title">{perm.title}</span>
|
||||
</div>
|
||||
{perm.description && (
|
||||
<div className="permission-description">{perm.description}</div>
|
||||
)}
|
||||
<div className="permission-actions">
|
||||
{perm.options.map((opt) => {
|
||||
const isSelected = resolved && selectedId === opt.optionId;
|
||||
const wasRejected = resolved && !isSelected && selectedId != null;
|
||||
return (
|
||||
<button
|
||||
key={opt.optionId}
|
||||
type="button"
|
||||
className={`permission-btn ${classForKind(opt.kind)} ${isSelected ? "selected" : ""} ${wasRejected ? "dimmed" : ""}`}
|
||||
disabled={resolved}
|
||||
onClick={() => onPermissionReply?.(perm.permissionId, replyForOption(opt.kind))}
|
||||
>
|
||||
{isSelected && (opt.kind.startsWith("allow") ? <Check size={12} /> : <X size={12} />)}
|
||||
{labelForKind(opt.kind, opt.name)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
{resolved && !selectedId && (
|
||||
<span className="permission-auto-resolved">Auto-resolved</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const agentLogos: Record<string, string> = {
|
||||
claude: `${import.meta.env.BASE_URL}logos/claude.svg`,
|
||||
codex: `${import.meta.env.BASE_URL}logos/openai.svg`,
|
||||
|
|
@ -185,7 +252,8 @@ const ChatMessages = ({
|
|||
messagesEndRef,
|
||||
onEventClick,
|
||||
isThinking,
|
||||
agentId
|
||||
agentId,
|
||||
onPermissionReply,
|
||||
}: {
|
||||
entries: TimelineEntry[];
|
||||
sessionError: string | null;
|
||||
|
|
@ -194,9 +262,10 @@ const ChatMessages = ({
|
|||
onEventClick?: (eventId: string) => void;
|
||||
isThinking?: boolean;
|
||||
agentId?: string;
|
||||
onPermissionReply?: (permissionId: string, reply: "once" | "always" | "reject") => void;
|
||||
}) => {
|
||||
// Group consecutive tool/reasoning/meta entries together
|
||||
const groupedEntries: Array<{ type: "message" | "tool-group" | "divider"; entries: TimelineEntry[] }> = [];
|
||||
const groupedEntries: Array<{ type: "message" | "tool-group" | "divider" | "permission"; entries: TimelineEntry[] }> = [];
|
||||
|
||||
let currentToolGroup: TimelineEntry[] = [];
|
||||
|
||||
|
|
@ -211,7 +280,10 @@ const ChatMessages = ({
|
|||
const isStatusDivider = entry.kind === "meta" &&
|
||||
["Session Started", "Turn Started", "Turn Ended"].includes(entry.meta?.title ?? "");
|
||||
|
||||
if (isStatusDivider) {
|
||||
if (entry.kind === "permission") {
|
||||
flushToolGroup();
|
||||
groupedEntries.push({ type: "permission", entries: [entry] });
|
||||
} else if (isStatusDivider) {
|
||||
flushToolGroup();
|
||||
groupedEntries.push({ type: "divider", entries: [entry] });
|
||||
} else if (entry.kind === "tool" || entry.kind === "reasoning" || (entry.kind === "meta" && entry.meta?.detail)) {
|
||||
|
|
@ -242,6 +314,17 @@ const ChatMessages = ({
|
|||
);
|
||||
}
|
||||
|
||||
if (group.type === "permission") {
|
||||
const entry = group.entries[0];
|
||||
return (
|
||||
<PermissionPrompt
|
||||
key={entry.id}
|
||||
entry={entry}
|
||||
onPermissionReply={onPermissionReply}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (group.type === "tool-group") {
|
||||
return <ToolGroup key={`group-${idx}`} entries={group.entries} onEventClick={onEventClick} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ const ChatPanel = ({
|
|||
isThinking,
|
||||
agentId,
|
||||
tokenUsage,
|
||||
onPermissionReply,
|
||||
}: {
|
||||
sessionId: string;
|
||||
transcriptEntries: TimelineEntry[];
|
||||
|
|
@ -87,6 +88,7 @@ const ChatPanel = ({
|
|||
isThinking?: boolean;
|
||||
agentId?: string;
|
||||
tokenUsage?: { used: number; size: number; cost?: number } | null;
|
||||
onPermissionReply?: (permissionId: string, reply: "once" | "always" | "reject") => void;
|
||||
}) => {
|
||||
const [showAgentMenu, setShowAgentMenu] = useState(false);
|
||||
const [copiedSessionId, setCopiedSessionId] = useState(false);
|
||||
|
|
@ -258,6 +260,7 @@ const ChatPanel = ({
|
|||
onEventClick={onEventClick}
|
||||
isThinking={isThinking}
|
||||
agentId={agentId}
|
||||
onPermissionReply={onPermissionReply}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,13 @@
|
|||
export type PermissionOption = {
|
||||
optionId: string;
|
||||
name: string;
|
||||
kind: string;
|
||||
};
|
||||
|
||||
export type TimelineEntry = {
|
||||
id: string;
|
||||
eventId?: string; // Links back to the original event for navigation
|
||||
kind: "message" | "tool" | "meta" | "reasoning";
|
||||
kind: "message" | "tool" | "meta" | "reasoning" | "permission";
|
||||
time: string;
|
||||
// For messages:
|
||||
role?: "user" | "assistant";
|
||||
|
|
@ -15,4 +21,13 @@ export type TimelineEntry = {
|
|||
reasoning?: { text: string; visibility?: string };
|
||||
// For meta:
|
||||
meta?: { title: string; detail?: string; severity?: "info" | "error" };
|
||||
// For permission requests:
|
||||
permission?: {
|
||||
permissionId: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
options: PermissionOption[];
|
||||
resolved?: boolean;
|
||||
selectedOptionId?: string;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
431
pnpm-lock.yaml
generated
431
pnpm-lock.yaml
generated
|
|
@ -23,7 +23,7 @@ importers:
|
|||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: ^3.0.0
|
||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.9)(jiti@1.21.7)(yaml@2.8.2)
|
||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.9)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2)
|
||||
|
||||
examples/boxlite:
|
||||
dependencies:
|
||||
|
|
@ -51,7 +51,7 @@ importers:
|
|||
dependencies:
|
||||
'@cloudflare/sandbox':
|
||||
specifier: latest
|
||||
version: 0.7.12
|
||||
version: 0.7.12(@opencode-ai/sdk@1.2.24)
|
||||
hono:
|
||||
specifier: ^4.12.2
|
||||
version: 4.12.2
|
||||
|
|
@ -474,8 +474,8 @@ importers:
|
|||
specifier: ^10.3.1
|
||||
version: 10.3.1
|
||||
rivetkit:
|
||||
specifier: link:../../../../../handoff/rivet-checkout/rivetkit-typescript/packages/rivetkit
|
||||
version: link:../../../../../handoff/rivet-checkout/rivetkit-typescript/packages/rivetkit
|
||||
specifier: 2.1.6
|
||||
version: 2.1.6(@hono/node-server@1.19.9(hono@4.12.2))(@hono/node-ws@1.3.0(@hono/node-server@1.19.9(hono@4.12.2))(hono@4.12.2))(drizzle-kit@0.31.9)(drizzle-orm@0.44.7(@cloudflare/workers-types@4.20260305.1)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.18.0)(better-sqlite3@11.10.0)(bun-types@1.3.10)(pg@8.20.0))(ws@8.19.0)
|
||||
sandbox-agent:
|
||||
specifier: workspace:*
|
||||
version: link:../../../sdks/typescript
|
||||
|
|
@ -502,8 +502,8 @@ importers:
|
|||
specifier: workspace:*
|
||||
version: link:../shared
|
||||
rivetkit:
|
||||
specifier: link:../../../../../handoff/rivet-checkout/rivetkit-typescript/packages/rivetkit
|
||||
version: link:../../../../../handoff/rivet-checkout/rivetkit-typescript/packages/rivetkit
|
||||
specifier: 2.1.6
|
||||
version: 2.1.6(@hono/node-server@1.19.9(hono@4.12.2))(@hono/node-ws@1.3.0(@hono/node-server@1.19.9(hono@4.12.2))(hono@4.12.2))(drizzle-kit@0.31.9)(drizzle-orm@0.44.7(@cloudflare/workers-types@4.20260305.1)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.18.0)(better-sqlite3@11.10.0)(bun-types@1.3.10)(pg@8.20.0))(ws@8.19.0)
|
||||
devDependencies:
|
||||
tsup:
|
||||
specifier: ^8.5.0
|
||||
|
|
@ -634,7 +634,7 @@ importers:
|
|||
version: 5.4.21(@types/node@25.4.0)
|
||||
vitest:
|
||||
specifier: ^3.0.0
|
||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.2)
|
||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@25.4.0)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2)
|
||||
|
||||
frontend/packages/website:
|
||||
dependencies:
|
||||
|
|
@ -791,7 +791,7 @@ importers:
|
|||
devDependencies:
|
||||
vitest:
|
||||
specifier: ^3.0.0
|
||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.2)
|
||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@25.4.0)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2)
|
||||
|
||||
sdks/cli-shared:
|
||||
devDependencies:
|
||||
|
|
@ -839,7 +839,7 @@ importers:
|
|||
devDependencies:
|
||||
vitest:
|
||||
specifier: ^3.0.0
|
||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.2)
|
||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@25.4.0)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2)
|
||||
|
||||
sdks/gigacode/platforms/darwin-arm64: {}
|
||||
|
||||
|
|
@ -999,6 +999,22 @@ importers:
|
|||
specifier: ^8.19.0
|
||||
version: 8.19.0
|
||||
|
||||
server/packages/sandbox-agent/tests/opencode-compat:
|
||||
dependencies:
|
||||
'@opencode-ai/sdk':
|
||||
specifier: ^1.1.21
|
||||
version: 1.2.24
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^22.0.0
|
||||
version: 22.19.7
|
||||
typescript:
|
||||
specifier: ^5.7.0
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: ^3.0.0
|
||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.7)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2)
|
||||
|
||||
packages:
|
||||
|
||||
'@agentclientprotocol/sdk@0.14.1':
|
||||
|
|
@ -1014,6 +1030,11 @@ packages:
|
|||
resolution: {integrity: sha512-FSEVWXvwroExDXUu8qV6Wqp2X3D1nJ0Li4LFymCyvCVrm7I3lNfG0zZWSWvGU1RE7891eTnFTyh31L3igOwNKQ==}
|
||||
hasBin: true
|
||||
|
||||
'@asteasolutions/zod-to-openapi@8.4.3':
|
||||
resolution: {integrity: sha512-lwfMTN7kDbFDwMniYZUebiGGHxVGBw9ZSI4IBYjm6Ey22Kd5z/fsQb2k+Okr8WMbCCC553vi/ZM9utl5/XcvuQ==}
|
||||
peerDependencies:
|
||||
zod: ^4.0.0
|
||||
|
||||
'@astrojs/compiler@2.13.0':
|
||||
resolution: {integrity: sha512-mqVORhUJViA28fwHYaWmsXSzLO9osbdZ5ImUfxBarqsYdMlPbqAqGJCxsNzvppp1BEzc1mJNjOVvQqeDN8Vspw==}
|
||||
|
||||
|
|
@ -1381,6 +1402,36 @@ packages:
|
|||
resolution: {integrity: sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@cbor-extract/cbor-extract-darwin-arm64@2.2.1':
|
||||
resolution: {integrity: sha512-ubDPxTvFufYt2wYpBIbp9AP4uKKm55AVrXJyGkLbpKyk8+01KCKDGeB66JTZCGflSsAPRIluLHWI1P8ntEZShQ==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@cbor-extract/cbor-extract-darwin-x64@2.2.1':
|
||||
resolution: {integrity: sha512-lzJAun76gwY4rgeklyNuPROeo7kYLh0lK+Jji9J7OX3UfSV7EpjDNGKT7wotzGpF0Qm36bOtlCNvE+VsUin1gQ==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@cbor-extract/cbor-extract-linux-arm64@2.2.1':
|
||||
resolution: {integrity: sha512-OFdIWOrSCSE8IRVAzHX5Js1TSvmQCcZbsB+wpXlZLFhIFbXlvF8VmXSHtBkK8pLfs9nvfsg3zz3ZIG10EKazHQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@cbor-extract/cbor-extract-linux-arm@2.2.1':
|
||||
resolution: {integrity: sha512-J5MjNKc4ecV56jOYDDMp0CfGyqQgoOgTwsEjra7YJdJic1YzGEyPedZStcpS0E1bdEyPOOJOfbFLRpgtFosZ/Q==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@cbor-extract/cbor-extract-linux-x64@2.2.1':
|
||||
resolution: {integrity: sha512-f7gB4h/CaR66hTAmooJ6uIuCNl4SmBGJAQx+ED9eIJIZtOhkyeeRrBw383cIdHiLbvr/wBVZ31RnyqFLRCmu1g==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@cbor-extract/cbor-extract-win32-x64@2.2.1':
|
||||
resolution: {integrity: sha512-diYmdTuTTNjTfmb9A+Xq5ZOtLP6JW2SuGXqV25ZbnXjuhdEO/Vn8tX3QPmKqnPOf7SyFQVpAVwU5w9B53y3P4g==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@cloudflare/containers@0.1.1':
|
||||
resolution: {integrity: sha512-YTdobRTnTlUOUPMFemufH367A9Z8pDfZ+UboYMLbGpO0VlvEXZDiioSmXPQMHld2vRtkL31mcRii3bcbQU6fdw==}
|
||||
|
||||
|
|
@ -2286,6 +2337,25 @@ packages:
|
|||
'@hono/node-server': ^1.19.2
|
||||
hono: ^4.6.0
|
||||
|
||||
'@hono/standard-validator@0.1.5':
|
||||
resolution: {integrity: sha512-EIyZPPwkyLn6XKwFj5NBEWHXhXbgmnVh2ceIFo5GO7gKI9WmzTjPDKnppQB0KrqKeAkq3kpoW4SIbu5X1dgx3w==}
|
||||
peerDependencies:
|
||||
'@standard-schema/spec': 1.0.0
|
||||
hono: '>=3.9.0'
|
||||
|
||||
'@hono/zod-openapi@1.2.2':
|
||||
resolution: {integrity: sha512-va6vsL23wCJ1d0Vd+vGL1XOt+wPwItxirYafuhlW9iC2MstYr2FvsI7mctb45eBTjZfkqB/3LYDJEppPjOEiHw==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
peerDependencies:
|
||||
hono: '>=4.3.6'
|
||||
zod: ^4.0.0
|
||||
|
||||
'@hono/zod-validator@0.7.6':
|
||||
resolution: {integrity: sha512-Io1B6d011Gj1KknV4rXYz4le5+5EubcWEU/speUjuw9XMMIaP3n78yXLhjd2A3PXaXaUwEAluOiAyLqhBEJgsw==}
|
||||
peerDependencies:
|
||||
hono: '>=3.9.0'
|
||||
zod: ^3.25.0 || ^4.0.0
|
||||
|
||||
'@iarna/toml@2.2.5':
|
||||
resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==}
|
||||
|
||||
|
|
@ -2524,6 +2594,9 @@ packages:
|
|||
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
'@opencode-ai/sdk@1.2.24':
|
||||
resolution: {integrity: sha512-MQamFkRl4B/3d6oIRLNpkYR2fcwet1V/ffKyOKJXWjtP/CT9PDJMtLpu6olVHjXKQi8zMNltwuMhv1QsNtRlZg==}
|
||||
|
||||
'@opentelemetry/api-logs@0.207.0':
|
||||
resolution: {integrity: sha512-lAb0jQRVyleQQGiuuvCOTDVspc14nx6XJjP4FspJ1sNARo3Regq4ZZbrc3rN4b1TYSuUCvgH+UXUPug4SLOqEQ==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
|
@ -2767,6 +2840,41 @@ packages:
|
|||
resolution: {integrity: sha512-imK/Bh/rxeWRhnKV5rpm6kYyT0+O+9uX8njiyG1LE8hvJFboAY+3fHuOMC816+Gm5de/Asrf8R3uzNYPmgUUzg==}
|
||||
hasBin: true
|
||||
|
||||
'@rivetkit/bare-ts@0.6.2':
|
||||
resolution: {integrity: sha512-3qndQUQXLdwafMEqfhz24hUtDPcsf1Bu3q52Kb8MqeH8JUh3h6R4HYW3ZJXiQsLcyYyFM68PuIwlLRlg1xDEpg==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
|
||||
'@rivetkit/engine-runner-protocol@2.1.6':
|
||||
resolution: {integrity: sha512-QwaWvAJN2KGae+UHKZbLiEWaWj9ycmwtrRtUq728CU+lidkaGv5yHxXb4gkXSD7rhGQcR98+XWZLb0F0BM/vAg==}
|
||||
|
||||
'@rivetkit/engine-runner@2.1.6':
|
||||
resolution: {integrity: sha512-WpiEmi/SxAVED0N/M0kvPZwq/MxMuuz/Y89ut1sTP7syPzpCauGxafOdqkTqiX1ef+N1ZlrtX+v/LwDF/jIgFw==}
|
||||
|
||||
'@rivetkit/fast-json-patch@3.1.2':
|
||||
resolution: {integrity: sha512-CtA50xgsSSzICQduF/NDShPRzvucnNvsW/lQO0WgMTT1XAj9Lfae4pm7r3llFwilgG+9iq76Hv1LUqNy72v6yw==}
|
||||
|
||||
'@rivetkit/on-change@6.0.2-rc.1':
|
||||
resolution: {integrity: sha512-5RC9Ze/wTKqSlJvopdCgr+EfyV93+iiH8Thog0QXrl8PT1unuBNw/jadXNMtwgAxrIaCJL+JLaHQH9w7rqpMDw==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
'@rivetkit/sqlite-vfs@2.1.6':
|
||||
resolution: {integrity: sha512-jbCrigzqoygZTYdZu7izaQjr77Q4BFX1HwhW4Mf0UFIaKT72AteH/w4PcktzrKcw4Utmo0zX0C6zNBRKo0IpOA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@rivetkit/sqlite@0.1.1':
|
||||
resolution: {integrity: sha512-NE7ZBy/hQhOrWzMZFjkHX9SoXxf+ILcDvVV+mNbUYPgiy/fsDzlXdK0+JDTGnko5f4Xl6/KVCoCozz9gkwkq8A==}
|
||||
|
||||
'@rivetkit/traces@2.1.6':
|
||||
resolution: {integrity: sha512-wuuGWoWWdUPbqs5u+31YodSUOsYMydaa+/cxZ7I5KaUe26fK0i1E+0ytqC1JGQm6utWeuYp8cLUX3WSEfVKJhQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@rivetkit/virtual-websocket@2.0.33':
|
||||
resolution: {integrity: sha512-sMoHZgBy9WDW76pv+ML3LPgf7TWk5vXdu3ZpPO20j6n+rB3fLacnnmzjt5xD6tZcJ/x5qINyEywGgcxA7MTMuQ==}
|
||||
|
||||
'@rivetkit/workflow-engine@2.1.6':
|
||||
resolution: {integrity: sha512-eLVFBbhOlBQKzO5lu032tOo0OEAFFp7uNcGwvB1mBFmYsm7aKBgnJl214IV39a6fRtCL2meVxiMU1GKb006zYw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-beta.27':
|
||||
resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
|
||||
|
||||
|
|
@ -3280,6 +3388,9 @@ packages:
|
|||
'@types/react@18.3.27':
|
||||
resolution: {integrity: sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==}
|
||||
|
||||
'@types/retry@0.12.2':
|
||||
resolution: {integrity: sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==}
|
||||
|
||||
'@types/sax@1.2.7':
|
||||
resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==}
|
||||
|
||||
|
|
@ -3606,6 +3717,13 @@ packages:
|
|||
card-validator@6.2.0:
|
||||
resolution: {integrity: sha512-1vYv45JaE9NmixZr4dl6ykzbFKv7imI9L7MQDs235b/a7EGbG8rrEsipeHtVvscLSUbl3RX6UP5gyOe0Y2FlHA==}
|
||||
|
||||
cbor-extract@2.2.1:
|
||||
resolution: {integrity: sha512-Vp8PLcLCC3tgOYIrgAjCa5GpPR2jUciJqNDklUbXRjsl9BlFzQyHoaZc2MjDb6AE47xQ1mID7rGuRD1ZAuE+sQ==}
|
||||
hasBin: true
|
||||
|
||||
cbor-x@1.6.3:
|
||||
resolution: {integrity: sha512-P/LrTqDtSLAj6j6MntQlLheHGGnh1tzZrbei6chGVqWJC8tvB7cZWou013zY4iPnsLMNailBrq9ajU0IKEPXyg==}
|
||||
|
||||
ccount@2.0.1:
|
||||
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
|
||||
|
||||
|
|
@ -4352,6 +4470,9 @@ packages:
|
|||
fastq@1.20.1:
|
||||
resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
|
||||
|
||||
fdb-tuple@1.0.0:
|
||||
resolution: {integrity: sha512-8jSvKPCYCgTpi9Pt87qlfTk6griyMx4Gk3Xv31Dp72Qp8b6XgIyFsMm8KzPmFJ9iJ8K4pGvRxvOS8D0XGnrkjw==}
|
||||
|
||||
fdir@6.5.0:
|
||||
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
|
@ -4473,6 +4594,10 @@ packages:
|
|||
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
get-port@7.1.0:
|
||||
resolution: {integrity: sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
get-proto@1.0.1:
|
||||
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
|
@ -4697,6 +4822,10 @@ packages:
|
|||
resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
is-network-error@1.3.1:
|
||||
resolution: {integrity: sha512-6QCxa49rQbmUWLfk0nuGqzql9U8uaV2H6279bRErPBHe/109hCzsLUBUHfbEtvLIHBd6hyXbgedBSHevm43Edw==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
is-number@7.0.0:
|
||||
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
||||
engines: {node: '>=0.12.0'}
|
||||
|
|
@ -5116,6 +5245,10 @@ packages:
|
|||
nan@2.25.0:
|
||||
resolution: {integrity: sha512-0M90Ag7Xn5KMLLZ7zliPWP3rT90P6PN+IzVFS0VqmnPktBk3700xUVv8Ikm9EUaUE5SDWdp/BIxdENzVznpm1g==}
|
||||
|
||||
nanoevents@9.1.0:
|
||||
resolution: {integrity: sha512-Jd0fILWG44a9luj8v5kED4WI+zfkkgwKyRQKItTtlPfEsh7Lznfi1kr8/iZ+XAIss4Qq5GqRB0qtWbaz9ceO/A==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
|
||||
nanoid@3.3.11:
|
||||
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
|
|
@ -5142,6 +5275,10 @@ packages:
|
|||
node-fetch-native@1.6.7:
|
||||
resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==}
|
||||
|
||||
node-gyp-build-optional-packages@5.1.1:
|
||||
resolution: {integrity: sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==}
|
||||
hasBin: true
|
||||
|
||||
node-mock-http@1.0.4:
|
||||
resolution: {integrity: sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==}
|
||||
|
||||
|
|
@ -5212,6 +5349,9 @@ packages:
|
|||
resolution: {integrity: sha512-c/hfooPx+RBIOPM09GSxABOZhYPblDoyaGhqBkD/59vtpN21jEuWKDlM0KYTvqJVlSYjKs0tBcIdeXKChlSPtw==}
|
||||
hasBin: true
|
||||
|
||||
openapi3-ts@4.5.0:
|
||||
resolution: {integrity: sha512-jaL+HgTq2Gj5jRcfdutgRGLosCy/hT8sQf6VOy+P+g36cZOjI1iukdPnijC+4CmeRzg/jEllJUboEic2FhxhtQ==}
|
||||
|
||||
ora@8.2.0:
|
||||
resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -5228,6 +5368,10 @@ packages:
|
|||
resolution: {integrity: sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
p-retry@6.2.1:
|
||||
resolution: {integrity: sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==}
|
||||
engines: {node: '>=16.17'}
|
||||
|
||||
p-timeout@6.1.4:
|
||||
resolution: {integrity: sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==}
|
||||
engines: {node: '>=14.16'}
|
||||
|
|
@ -5364,6 +5508,9 @@ packages:
|
|||
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
pino-abstract-transport@2.0.0:
|
||||
resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==}
|
||||
|
||||
pino-abstract-transport@3.0.0:
|
||||
resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==}
|
||||
|
||||
|
|
@ -5374,6 +5521,10 @@ packages:
|
|||
resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==}
|
||||
hasBin: true
|
||||
|
||||
pino@9.14.0:
|
||||
resolution: {integrity: sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==}
|
||||
hasBin: true
|
||||
|
||||
pirates@4.0.7:
|
||||
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
|
@ -5796,6 +5947,30 @@ packages:
|
|||
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
|
||||
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
||||
|
||||
rivetkit@2.1.6:
|
||||
resolution: {integrity: sha512-AFe06CTZoAYWwCltxPp1XNilfOgK53Itvbc/lv0uF+aEwFEAPIZFQPBTrVgt7NGZSuiM2l6/hjMv7yuBZQ71sw==}
|
||||
engines: {node: '>=22.0.0'}
|
||||
peerDependencies:
|
||||
'@hono/node-server': ^1.14.0
|
||||
'@hono/node-ws': ^1.1.1
|
||||
drizzle-kit: ^0.31.2
|
||||
drizzle-orm: ^0.44.2
|
||||
eventsource: ^4.0.0
|
||||
ws: ^8.0.0
|
||||
peerDependenciesMeta:
|
||||
'@hono/node-server':
|
||||
optional: true
|
||||
'@hono/node-ws':
|
||||
optional: true
|
||||
drizzle-kit:
|
||||
optional: true
|
||||
drizzle-orm:
|
||||
optional: true
|
||||
eventsource:
|
||||
optional: true
|
||||
ws:
|
||||
optional: true
|
||||
|
||||
robust-predicates@3.0.2:
|
||||
resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==}
|
||||
|
||||
|
|
@ -6104,6 +6279,9 @@ packages:
|
|||
thenify@3.3.1:
|
||||
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
|
||||
|
||||
thread-stream@3.1.0:
|
||||
resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
|
||||
|
||||
thread-stream@4.0.0:
|
||||
resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==}
|
||||
engines: {node: '>=20'}
|
||||
|
|
@ -6438,6 +6616,10 @@ packages:
|
|||
resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
|
||||
hasBin: true
|
||||
|
||||
uuid@12.0.0:
|
||||
resolution: {integrity: sha512-USe1zesMYh4fjCA8ZH5+X5WIVD0J4V1Jksm1bFTVBX2F/cwSXt0RO5w/3UXbdLKmZX65MiWV+hwhSS8p6oBTGA==}
|
||||
hasBin: true
|
||||
|
||||
uuid@13.0.0:
|
||||
resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==}
|
||||
hasBin: true
|
||||
|
|
@ -6446,6 +6628,10 @@ packages:
|
|||
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
vbare@0.0.4:
|
||||
resolution: {integrity: sha512-QsxSVw76NqYUWYPVcQmOnQPX8buIVjgn+yqldTHlWISulBTB9TJ9rnzZceDu+GZmycOtzsmuPbPN1YNxvK12fg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
vfile-location@5.0.3:
|
||||
resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==}
|
||||
|
||||
|
|
@ -6788,6 +6974,11 @@ snapshots:
|
|||
|
||||
'@antfu/ni@0.23.2': {}
|
||||
|
||||
'@asteasolutions/zod-to-openapi@8.4.3(zod@4.3.6)':
|
||||
dependencies:
|
||||
openapi3-ts: 4.5.0
|
||||
zod: 4.3.6
|
||||
|
||||
'@astrojs/compiler@2.13.0': {}
|
||||
|
||||
'@astrojs/internal-helpers@0.7.5': {}
|
||||
|
|
@ -7624,14 +7815,34 @@ snapshots:
|
|||
dependencies:
|
||||
fontkitten: 1.0.2
|
||||
|
||||
'@cbor-extract/cbor-extract-darwin-arm64@2.2.1':
|
||||
optional: true
|
||||
|
||||
'@cbor-extract/cbor-extract-darwin-x64@2.2.1':
|
||||
optional: true
|
||||
|
||||
'@cbor-extract/cbor-extract-linux-arm64@2.2.1':
|
||||
optional: true
|
||||
|
||||
'@cbor-extract/cbor-extract-linux-arm@2.2.1':
|
||||
optional: true
|
||||
|
||||
'@cbor-extract/cbor-extract-linux-x64@2.2.1':
|
||||
optional: true
|
||||
|
||||
'@cbor-extract/cbor-extract-win32-x64@2.2.1':
|
||||
optional: true
|
||||
|
||||
'@cloudflare/containers@0.1.1': {}
|
||||
|
||||
'@cloudflare/kv-asset-handler@0.4.2': {}
|
||||
|
||||
'@cloudflare/sandbox@0.7.12':
|
||||
'@cloudflare/sandbox@0.7.12(@opencode-ai/sdk@1.2.24)':
|
||||
dependencies:
|
||||
'@cloudflare/containers': 0.1.1
|
||||
aws4fetch: 1.0.20
|
||||
optionalDependencies:
|
||||
'@opencode-ai/sdk': 1.2.24
|
||||
|
||||
'@cloudflare/unenv-preset@2.15.0(unenv@2.0.0-rc.24)(workerd@1.20260301.1)':
|
||||
dependencies:
|
||||
|
|
@ -8199,6 +8410,23 @@ snapshots:
|
|||
- bufferutil
|
||||
- utf-8-validate
|
||||
|
||||
'@hono/standard-validator@0.1.5(hono@4.12.2)':
|
||||
dependencies:
|
||||
hono: 4.12.2
|
||||
|
||||
'@hono/zod-openapi@1.2.2(hono@4.12.2)(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@asteasolutions/zod-to-openapi': 8.4.3(zod@4.3.6)
|
||||
'@hono/zod-validator': 0.7.6(hono@4.12.2)(zod@4.3.6)
|
||||
hono: 4.12.2
|
||||
openapi3-ts: 4.5.0
|
||||
zod: 4.3.6
|
||||
|
||||
'@hono/zod-validator@0.7.6(hono@4.12.2)(zod@4.3.6)':
|
||||
dependencies:
|
||||
hono: 4.12.2
|
||||
zod: 4.3.6
|
||||
|
||||
'@iarna/toml@2.2.5': {}
|
||||
|
||||
'@img/colour@1.0.0': {}
|
||||
|
|
@ -8430,6 +8658,8 @@ snapshots:
|
|||
'@nodelib/fs.scandir': 2.1.5
|
||||
fastq: 1.20.1
|
||||
|
||||
'@opencode-ai/sdk@1.2.24': {}
|
||||
|
||||
'@opentelemetry/api-logs@0.207.0':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
|
|
@ -8750,6 +8980,52 @@ snapshots:
|
|||
- react
|
||||
- supports-color
|
||||
|
||||
'@rivetkit/bare-ts@0.6.2': {}
|
||||
|
||||
'@rivetkit/engine-runner-protocol@2.1.6':
|
||||
dependencies:
|
||||
'@rivetkit/bare-ts': 0.6.2
|
||||
|
||||
'@rivetkit/engine-runner@2.1.6':
|
||||
dependencies:
|
||||
'@rivetkit/engine-runner-protocol': 2.1.6
|
||||
'@rivetkit/virtual-websocket': 2.0.33
|
||||
pino: 9.14.0
|
||||
uuid: 12.0.0
|
||||
ws: 8.19.0
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- utf-8-validate
|
||||
|
||||
'@rivetkit/fast-json-patch@3.1.2': {}
|
||||
|
||||
'@rivetkit/on-change@6.0.2-rc.1': {}
|
||||
|
||||
'@rivetkit/sqlite-vfs@2.1.6':
|
||||
dependencies:
|
||||
'@rivetkit/bare-ts': 0.6.2
|
||||
'@rivetkit/sqlite': 0.1.1
|
||||
vbare: 0.0.4
|
||||
|
||||
'@rivetkit/sqlite@0.1.1': {}
|
||||
|
||||
'@rivetkit/traces@2.1.6':
|
||||
dependencies:
|
||||
'@rivetkit/bare-ts': 0.6.2
|
||||
cbor-x: 1.6.3
|
||||
fdb-tuple: 1.0.0
|
||||
vbare: 0.0.4
|
||||
|
||||
'@rivetkit/virtual-websocket@2.0.33': {}
|
||||
|
||||
'@rivetkit/workflow-engine@2.1.6':
|
||||
dependencies:
|
||||
'@rivetkit/bare-ts': 0.6.2
|
||||
cbor-x: 1.6.3
|
||||
fdb-tuple: 1.0.0
|
||||
pino: 9.14.0
|
||||
vbare: 0.0.4
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-beta.27': {}
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-rc.3': {}
|
||||
|
|
@ -9373,6 +9649,8 @@ snapshots:
|
|||
'@types/prop-types': 15.7.15
|
||||
csstype: 3.2.3
|
||||
|
||||
'@types/retry@0.12.2': {}
|
||||
|
||||
'@types/sax@1.2.7':
|
||||
dependencies:
|
||||
'@types/node': 24.10.9
|
||||
|
|
@ -9902,6 +10180,22 @@ snapshots:
|
|||
dependencies:
|
||||
credit-card-type: 8.3.0
|
||||
|
||||
cbor-extract@2.2.1:
|
||||
dependencies:
|
||||
node-gyp-build-optional-packages: 5.1.1
|
||||
optionalDependencies:
|
||||
'@cbor-extract/cbor-extract-darwin-arm64': 2.2.1
|
||||
'@cbor-extract/cbor-extract-darwin-x64': 2.2.1
|
||||
'@cbor-extract/cbor-extract-linux-arm': 2.2.1
|
||||
'@cbor-extract/cbor-extract-linux-arm64': 2.2.1
|
||||
'@cbor-extract/cbor-extract-linux-x64': 2.2.1
|
||||
'@cbor-extract/cbor-extract-win32-x64': 2.2.1
|
||||
optional: true
|
||||
|
||||
cbor-x@1.6.3:
|
||||
optionalDependencies:
|
||||
cbor-extract: 2.2.1
|
||||
|
||||
ccount@2.0.1: {}
|
||||
|
||||
chai@5.3.3:
|
||||
|
|
@ -10699,6 +10993,8 @@ snapshots:
|
|||
dependencies:
|
||||
reusify: 1.1.0
|
||||
|
||||
fdb-tuple@1.0.0: {}
|
||||
|
||||
fdir@6.5.0(picomatch@4.0.3):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.3
|
||||
|
|
@ -10813,6 +11109,8 @@ snapshots:
|
|||
hasown: 2.0.2
|
||||
math-intrinsics: 1.1.0
|
||||
|
||||
get-port@7.1.0: {}
|
||||
|
||||
get-proto@1.0.1:
|
||||
dependencies:
|
||||
dunder-proto: 1.0.1
|
||||
|
|
@ -11070,6 +11368,8 @@ snapshots:
|
|||
|
||||
is-interactive@2.0.0: {}
|
||||
|
||||
is-network-error@1.3.1: {}
|
||||
|
||||
is-number@7.0.0: {}
|
||||
|
||||
is-plain-obj@4.1.0: {}
|
||||
|
|
@ -11639,6 +11939,8 @@ snapshots:
|
|||
nan@2.25.0:
|
||||
optional: true
|
||||
|
||||
nanoevents@9.1.0: {}
|
||||
|
||||
nanoid@3.3.11: {}
|
||||
|
||||
napi-build-utils@2.0.0: {}
|
||||
|
|
@ -11657,6 +11959,11 @@ snapshots:
|
|||
|
||||
node-fetch-native@1.6.7: {}
|
||||
|
||||
node-gyp-build-optional-packages@5.1.1:
|
||||
dependencies:
|
||||
detect-libc: 2.1.2
|
||||
optional: true
|
||||
|
||||
node-mock-http@1.0.4: {}
|
||||
|
||||
node-releases@2.0.27: {}
|
||||
|
|
@ -11726,6 +12033,10 @@ snapshots:
|
|||
undici: 5.29.0
|
||||
yargs-parser: 21.1.1
|
||||
|
||||
openapi3-ts@4.5.0:
|
||||
dependencies:
|
||||
yaml: 2.8.2
|
||||
|
||||
ora@8.2.0:
|
||||
dependencies:
|
||||
chalk: 5.6.2
|
||||
|
|
@ -11749,6 +12060,12 @@ snapshots:
|
|||
eventemitter3: 5.0.4
|
||||
p-timeout: 6.1.4
|
||||
|
||||
p-retry@6.2.1:
|
||||
dependencies:
|
||||
'@types/retry': 0.12.2
|
||||
is-network-error: 1.3.1
|
||||
retry: 0.13.1
|
||||
|
||||
p-timeout@6.1.4: {}
|
||||
|
||||
package-json-from-dist@1.0.1: {}
|
||||
|
|
@ -11868,6 +12185,10 @@ snapshots:
|
|||
|
||||
pify@2.3.0: {}
|
||||
|
||||
pino-abstract-transport@2.0.0:
|
||||
dependencies:
|
||||
split2: 4.2.0
|
||||
|
||||
pino-abstract-transport@3.0.0:
|
||||
dependencies:
|
||||
split2: 4.2.0
|
||||
|
|
@ -11888,6 +12209,20 @@ snapshots:
|
|||
sonic-boom: 4.2.1
|
||||
thread-stream: 4.0.0
|
||||
|
||||
pino@9.14.0:
|
||||
dependencies:
|
||||
'@pinojs/redact': 0.4.0
|
||||
atomic-sleep: 1.0.0
|
||||
on-exit-leak-free: 2.1.2
|
||||
pino-abstract-transport: 2.0.0
|
||||
pino-std-serializers: 7.1.0
|
||||
process-warning: 5.0.0
|
||||
quick-format-unescaped: 4.0.4
|
||||
real-require: 0.2.0
|
||||
safe-stable-stringify: 2.5.0
|
||||
sonic-boom: 4.2.1
|
||||
thread-stream: 3.1.0
|
||||
|
||||
pirates@4.0.7: {}
|
||||
|
||||
pkce-challenge@5.0.1: {}
|
||||
|
|
@ -12352,6 +12687,41 @@ snapshots:
|
|||
|
||||
reusify@1.1.0: {}
|
||||
|
||||
rivetkit@2.1.6(@hono/node-server@1.19.9(hono@4.12.2))(@hono/node-ws@1.3.0(@hono/node-server@1.19.9(hono@4.12.2))(hono@4.12.2))(drizzle-kit@0.31.9)(drizzle-orm@0.44.7(@cloudflare/workers-types@4.20260305.1)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.18.0)(better-sqlite3@11.10.0)(bun-types@1.3.10)(pg@8.20.0))(ws@8.19.0):
|
||||
dependencies:
|
||||
'@hono/standard-validator': 0.1.5(hono@4.12.2)
|
||||
'@hono/zod-openapi': 1.2.2(hono@4.12.2)(zod@4.3.6)
|
||||
'@rivetkit/bare-ts': 0.6.2
|
||||
'@rivetkit/engine-runner': 2.1.6
|
||||
'@rivetkit/fast-json-patch': 3.1.2
|
||||
'@rivetkit/on-change': 6.0.2-rc.1
|
||||
'@rivetkit/sqlite': 0.1.1
|
||||
'@rivetkit/sqlite-vfs': 2.1.6
|
||||
'@rivetkit/traces': 2.1.6
|
||||
'@rivetkit/virtual-websocket': 2.0.33
|
||||
'@rivetkit/workflow-engine': 2.1.6
|
||||
cbor-x: 1.6.3
|
||||
get-port: 7.1.0
|
||||
hono: 4.12.2
|
||||
invariant: 2.2.4
|
||||
nanoevents: 9.1.0
|
||||
p-retry: 6.2.1
|
||||
pino: 9.14.0
|
||||
tar: 7.5.7
|
||||
uuid: 12.0.0
|
||||
vbare: 0.0.4
|
||||
zod: 4.3.6
|
||||
optionalDependencies:
|
||||
'@hono/node-server': 1.19.9(hono@4.12.2)
|
||||
'@hono/node-ws': 1.3.0(@hono/node-server@1.19.9(hono@4.12.2))(hono@4.12.2)
|
||||
drizzle-kit: 0.31.9
|
||||
drizzle-orm: 0.44.7(@cloudflare/workers-types@4.20260305.1)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.18.0)(better-sqlite3@11.10.0)(bun-types@1.3.10)(pg@8.20.0)
|
||||
ws: 8.19.0
|
||||
transitivePeerDependencies:
|
||||
- '@standard-schema/spec'
|
||||
- bufferutil
|
||||
- utf-8-validate
|
||||
|
||||
robust-predicates@3.0.2: {}
|
||||
|
||||
rollup@4.56.0:
|
||||
|
|
@ -12794,6 +13164,10 @@ snapshots:
|
|||
dependencies:
|
||||
any-promise: 1.3.0
|
||||
|
||||
thread-stream@3.1.0:
|
||||
dependencies:
|
||||
real-require: 0.2.0
|
||||
|
||||
thread-stream@4.0.0:
|
||||
dependencies:
|
||||
real-require: 0.2.0
|
||||
|
|
@ -13061,10 +13435,14 @@ snapshots:
|
|||
|
||||
uuid@10.0.0: {}
|
||||
|
||||
uuid@12.0.0: {}
|
||||
|
||||
uuid@13.0.0: {}
|
||||
|
||||
vary@1.1.2: {}
|
||||
|
||||
vbare@0.0.4: {}
|
||||
|
||||
vfile-location@5.0.3:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
|
|
@ -13105,13 +13483,13 @@ snapshots:
|
|||
- tsx
|
||||
- yaml
|
||||
|
||||
vite-node@3.2.4(@types/node@24.10.9)(jiti@1.21.7)(yaml@2.8.2):
|
||||
vite-node@3.2.4(@types/node@24.10.9)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2):
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
debug: 4.4.3
|
||||
es-module-lexer: 1.7.0
|
||||
pathe: 2.0.3
|
||||
vite: 6.4.1(@types/node@24.10.9)(jiti@1.21.7)(yaml@2.8.2)
|
||||
vite: 6.4.1(@types/node@24.10.9)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- jiti
|
||||
|
|
@ -13147,13 +13525,13 @@ snapshots:
|
|||
- tsx
|
||||
- yaml
|
||||
|
||||
vite-node@3.2.4(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.2):
|
||||
vite-node@3.2.4(@types/node@25.4.0)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2):
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
debug: 4.4.3
|
||||
es-module-lexer: 1.7.0
|
||||
pathe: 2.0.3
|
||||
vite: 6.4.1(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.2)
|
||||
vite: 6.4.1(@types/node@25.4.0)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- jiti
|
||||
|
|
@ -13219,7 +13597,7 @@ snapshots:
|
|||
tsx: 4.21.0
|
||||
yaml: 2.8.2
|
||||
|
||||
vite@6.4.1(@types/node@24.10.9)(jiti@1.21.7)(yaml@2.8.2):
|
||||
vite@6.4.1(@types/node@24.10.9)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2):
|
||||
dependencies:
|
||||
esbuild: 0.25.12
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
|
|
@ -13231,6 +13609,7 @@ snapshots:
|
|||
'@types/node': 24.10.9
|
||||
fsevents: 2.3.3
|
||||
jiti: 1.21.7
|
||||
tsx: 4.21.0
|
||||
yaml: 2.8.2
|
||||
|
||||
vite@6.4.1(@types/node@25.3.5)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2):
|
||||
|
|
@ -13263,20 +13642,6 @@ snapshots:
|
|||
tsx: 4.21.0
|
||||
yaml: 2.8.2
|
||||
|
||||
vite@6.4.1(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.2):
|
||||
dependencies:
|
||||
esbuild: 0.25.12
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
picomatch: 4.0.3
|
||||
postcss: 8.5.6
|
||||
rollup: 4.56.0
|
||||
tinyglobby: 0.2.15
|
||||
optionalDependencies:
|
||||
'@types/node': 25.4.0
|
||||
fsevents: 2.3.3
|
||||
jiti: 1.21.7
|
||||
yaml: 2.8.2
|
||||
|
||||
vite@7.3.1(@types/node@25.4.0)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2):
|
||||
dependencies:
|
||||
esbuild: 0.27.3
|
||||
|
|
@ -13338,7 +13703,7 @@ snapshots:
|
|||
- tsx
|
||||
- yaml
|
||||
|
||||
vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.9)(jiti@1.21.7)(yaml@2.8.2):
|
||||
vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.9)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2):
|
||||
dependencies:
|
||||
'@types/chai': 5.2.3
|
||||
'@vitest/expect': 3.2.4
|
||||
|
|
@ -13361,7 +13726,7 @@ snapshots:
|
|||
tinypool: 1.1.1
|
||||
tinyrainbow: 2.0.0
|
||||
vite: 5.4.21(@types/node@24.10.9)
|
||||
vite-node: 3.2.4(@types/node@24.10.9)(jiti@1.21.7)(yaml@2.8.2)
|
||||
vite-node: 3.2.4(@types/node@24.10.9)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2)
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@types/debug': 4.1.12
|
||||
|
|
@ -13422,7 +13787,7 @@ snapshots:
|
|||
- tsx
|
||||
- yaml
|
||||
|
||||
vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.2):
|
||||
vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.4.0)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2):
|
||||
dependencies:
|
||||
'@types/chai': 5.2.3
|
||||
'@vitest/expect': 3.2.4
|
||||
|
|
@ -13445,7 +13810,7 @@ snapshots:
|
|||
tinypool: 1.1.1
|
||||
tinyrainbow: 2.0.0
|
||||
vite: 5.4.21(@types/node@25.4.0)
|
||||
vite-node: 3.2.4(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.2)
|
||||
vite-node: 3.2.4(@types/node@25.4.0)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2)
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@types/debug': 4.1.12
|
||||
|
|
|
|||
|
|
@ -11,3 +11,4 @@ packages:
|
|||
- "scripts/release"
|
||||
- "scripts/sandbox-testing"
|
||||
- "examples/*"
|
||||
- "server/packages/sandbox-agent/tests/opencode-compat"
|
||||
|
|
|
|||
|
|
@ -129,7 +129,6 @@ export interface SessionCreateRequest {
|
|||
sessionInit?: Omit<NewSessionRequest, "_meta">;
|
||||
model?: string;
|
||||
mode?: string;
|
||||
permissionMode?: string;
|
||||
thoughtLevel?: string;
|
||||
}
|
||||
|
||||
|
|
@ -139,7 +138,6 @@ export interface SessionResumeOrCreateRequest {
|
|||
sessionInit?: Omit<NewSessionRequest, "_meta">;
|
||||
model?: string;
|
||||
mode?: string;
|
||||
permissionMode?: string;
|
||||
thoughtLevel?: string;
|
||||
}
|
||||
|
||||
|
|
@ -321,14 +319,14 @@ export class Session {
|
|||
return this;
|
||||
}
|
||||
|
||||
async send(method: string, params: Record<string, unknown> = {}, options: SessionSendOptions = {}): Promise<unknown> {
|
||||
const updated = await this.sandbox.sendSessionMethod(this.id, method, params, options);
|
||||
async rawSend(method: string, params: Record<string, unknown> = {}, options: SessionSendOptions = {}): Promise<unknown> {
|
||||
const updated = await this.sandbox.rawSendSessionMethod(this.id, method, params, options);
|
||||
this.apply(updated.session.toRecord());
|
||||
return updated.response;
|
||||
}
|
||||
|
||||
async prompt(prompt: PromptRequest["prompt"]): Promise<PromptResponse> {
|
||||
const response = await this.send("session/prompt", { prompt });
|
||||
const response = await this.rawSend("session/prompt", { prompt });
|
||||
return response as PromptResponse;
|
||||
}
|
||||
|
||||
|
|
@ -338,14 +336,6 @@ export class Session {
|
|||
return updated.response;
|
||||
}
|
||||
|
||||
async setPermissionMode(
|
||||
permissionMode: string,
|
||||
): Promise<SetSessionModeResponse | SetSessionConfigOptionResponse | void> {
|
||||
const updated = await this.sandbox.setSessionPermissionMode(this.id, permissionMode);
|
||||
this.apply(updated.session.toRecord());
|
||||
return updated.response;
|
||||
}
|
||||
|
||||
async setConfigOption(configId: string, value: string): Promise<SetSessionConfigOptionResponse> {
|
||||
const updated = await this.sandbox.setSessionConfigOption(this.id, configId, value);
|
||||
this.apply(updated.session.toRecord());
|
||||
|
|
@ -380,12 +370,12 @@ export class Session {
|
|||
return this.sandbox.onPermissionRequest(this.id, listener);
|
||||
}
|
||||
|
||||
async replyPermission(permissionId: string, reply: PermissionReply): Promise<void> {
|
||||
await this.sandbox.replyPermission(permissionId, reply);
|
||||
async respondPermission(permissionId: string, reply: PermissionReply): Promise<void> {
|
||||
await this.sandbox.respondPermission(permissionId, reply);
|
||||
}
|
||||
|
||||
async respondToPermission(permissionId: string, response: RequestPermissionResponse): Promise<void> {
|
||||
await this.sandbox.respondToPermission(permissionId, response);
|
||||
async rawRespondPermission(permissionId: string, response: RequestPermissionResponse): Promise<void> {
|
||||
await this.sandbox.rawRespondPermission(permissionId, response);
|
||||
}
|
||||
|
||||
toRecord(): SessionRecord {
|
||||
|
|
@ -1026,15 +1016,11 @@ export class SandboxAgent {
|
|||
this.nextSessionEventIndexBySession.set(record.id, 1);
|
||||
live.bindSession(record.id, record.agentSessionId);
|
||||
let session = this.upsertSessionHandle(record);
|
||||
assertNoConflictingPermissionMode(request.mode, request.permissionMode);
|
||||
|
||||
try {
|
||||
if (request.mode) {
|
||||
session = (await this.setSessionMode(session.id, request.mode)).session;
|
||||
}
|
||||
if (request.permissionMode) {
|
||||
session = (await this.setSessionPermissionMode(session.id, request.permissionMode)).session;
|
||||
}
|
||||
if (request.model) {
|
||||
session = (await this.setSessionModel(session.id, request.model)).session;
|
||||
}
|
||||
|
|
@ -1089,13 +1075,9 @@ export class SandboxAgent {
|
|||
const existing = await this.persist.getSession(request.id);
|
||||
if (existing) {
|
||||
let session = await this.resumeSession(existing.id);
|
||||
assertNoConflictingPermissionMode(request.mode, request.permissionMode);
|
||||
if (request.mode) {
|
||||
session = (await this.setSessionMode(session.id, request.mode)).session;
|
||||
}
|
||||
if (request.permissionMode) {
|
||||
session = (await this.setSessionPermissionMode(session.id, request.permissionMode)).session;
|
||||
}
|
||||
if (request.model) {
|
||||
session = (await this.setSessionModel(session.id, request.model)).session;
|
||||
}
|
||||
|
|
@ -1211,24 +1193,6 @@ export class SandboxAgent {
|
|||
return this.setSessionCategoryValue(sessionId, "model", model);
|
||||
}
|
||||
|
||||
async setSessionPermissionMode(
|
||||
sessionId: string,
|
||||
permissionMode: string,
|
||||
): Promise<{ session: Session; response: SetSessionModeResponse | SetSessionConfigOptionResponse | void }> {
|
||||
const resolvedValue = permissionMode.trim();
|
||||
if (!resolvedValue) {
|
||||
throw new Error("setSessionPermissionMode requires a non-empty permissionMode");
|
||||
}
|
||||
|
||||
const options = await this.getSessionConfigOptions(sessionId);
|
||||
const permissionOption = findConfigOptionByCategory(options, "permission_mode");
|
||||
if (permissionOption) {
|
||||
return this.setSessionConfigOption(sessionId, permissionOption.id, resolvedValue);
|
||||
}
|
||||
|
||||
return this.setSessionMode(sessionId, resolvedValue);
|
||||
}
|
||||
|
||||
async setSessionThoughtLevel(
|
||||
sessionId: string,
|
||||
thoughtLevel: string,
|
||||
|
|
@ -1318,7 +1282,7 @@ export class SandboxAgent {
|
|||
return updated;
|
||||
}
|
||||
|
||||
async sendSessionMethod(
|
||||
async rawSendSessionMethod(
|
||||
sessionId: string,
|
||||
method: string,
|
||||
params: Record<string, unknown>,
|
||||
|
|
@ -1470,7 +1434,7 @@ export class SandboxAgent {
|
|||
};
|
||||
}
|
||||
|
||||
async replyPermission(permissionId: string, reply: PermissionReply): Promise<void> {
|
||||
async respondPermission(permissionId: string, reply: PermissionReply): Promise<void> {
|
||||
const pending = this.pendingPermissionRequests.get(permissionId);
|
||||
if (!pending) {
|
||||
throw new Error(`permission '${permissionId}' not found`);
|
||||
|
|
@ -1480,7 +1444,7 @@ export class SandboxAgent {
|
|||
this.resolvePendingPermission(permissionId, response);
|
||||
}
|
||||
|
||||
async respondToPermission(permissionId: string, response: RequestPermissionResponse): Promise<void> {
|
||||
async rawRespondPermission(permissionId: string, response: RequestPermissionResponse): Promise<void> {
|
||||
if (!this.pendingPermissionRequests.has(permissionId)) {
|
||||
throw new Error(`permission '${permissionId}' not found`);
|
||||
}
|
||||
|
|
@ -2719,16 +2683,6 @@ function cloneModes(value: SessionModeState | null | undefined): SessionModeStat
|
|||
return JSON.parse(JSON.stringify(value)) as SessionModeState;
|
||||
}
|
||||
|
||||
function assertNoConflictingPermissionMode(mode: string | undefined, permissionMode: string | undefined): void {
|
||||
if (!mode || !permissionMode) {
|
||||
return;
|
||||
}
|
||||
if (mode.trim() === permissionMode.trim()) {
|
||||
return;
|
||||
}
|
||||
throw new Error("createSession/resumeOrCreate received conflicting values for mode and permissionMode");
|
||||
}
|
||||
|
||||
function availablePermissionReplies(options: PermissionOption[]): PermissionReply[] {
|
||||
const replies = new Set<PermissionReply>();
|
||||
for (const option of options) {
|
||||
|
|
|
|||
|
|
@ -515,7 +515,7 @@ describe("Integration: TypeScript SDK flat session API", () => {
|
|||
await expect(session.send("session/cancel")).rejects.toThrow(
|
||||
"Use destroySession(sessionId) instead.",
|
||||
);
|
||||
await expect(sdk.sendSessionMethod(session.id, "session/cancel", {})).rejects.toThrow(
|
||||
await expect(sdk.rawSendSessionMethod(session.id, "session/cancel", {})).rejects.toThrow(
|
||||
"Use destroySession(sessionId) instead.",
|
||||
);
|
||||
|
||||
|
|
@ -578,42 +578,6 @@ describe("Integration: TypeScript SDK flat session API", () => {
|
|||
await sdk.dispose();
|
||||
});
|
||||
|
||||
it("supports permissionMode as a first-class session helper", async () => {
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl,
|
||||
token,
|
||||
});
|
||||
|
||||
const session = await sdk.createSession({
|
||||
agent: "mock",
|
||||
permissionMode: "plan",
|
||||
});
|
||||
|
||||
expect((await session.getModes())?.currentModeId).toBe("plan");
|
||||
|
||||
await session.setPermissionMode("normal");
|
||||
expect((await session.getModes())?.currentModeId).toBe("normal");
|
||||
|
||||
await sdk.dispose();
|
||||
});
|
||||
|
||||
it("rejects conflicting mode and permissionMode values", async () => {
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl,
|
||||
token,
|
||||
});
|
||||
|
||||
await expect(
|
||||
sdk.createSession({
|
||||
agent: "mock",
|
||||
mode: "normal",
|
||||
permissionMode: "plan",
|
||||
}),
|
||||
).rejects.toThrow("conflicting values");
|
||||
|
||||
await sdk.dispose();
|
||||
});
|
||||
|
||||
it("setThoughtLevel happy path switches to a valid thought level", async () => {
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl,
|
||||
|
|
@ -674,7 +638,7 @@ describe("Integration: TypeScript SDK flat session API", () => {
|
|||
const offPermissions = session.onPermissionRequest((request) => {
|
||||
permissionIds.push(request.id);
|
||||
const reply = permissionIds.length === 1 ? "reject" : "always";
|
||||
void session.replyPermission(request.id, reply);
|
||||
void session.respondPermission(request.id, reply);
|
||||
});
|
||||
|
||||
const offEvents = session.onEvent((event) => {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
*/
|
||||
|
||||
import { describe, it, expect, beforeAll, beforeEach, afterEach } from "vitest";
|
||||
import { createOpencodeClient, type OpencodeClient } from "@opencode-ai/sdk/v1";
|
||||
import { createOpencodeClient, type OpencodeClient } from "@opencode-ai/sdk";
|
||||
import { spawnSandboxAgent, buildSandboxAgent, type SandboxAgentHandle } from "./helpers/spawn";
|
||||
|
||||
describe("OpenCode-compatible Permission API", () => {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*/
|
||||
|
||||
import { describe, it, expect, beforeAll, beforeEach, afterEach } from "vitest";
|
||||
import { createOpencodeClient, type OpencodeClient } from "@opencode-ai/sdk/v1";
|
||||
import { createOpencodeClient, type OpencodeClient } from "@opencode-ai/sdk";
|
||||
import { spawnSandboxAgent, buildSandboxAgent, type SandboxAgentHandle } from "./helpers/spawn";
|
||||
|
||||
describe("OpenCode-compatible Question API", () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue