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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 62 additions & 1 deletion extensions/cate.aisession/src/_kit/cate-host.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,46 @@ export interface CateHostStorage {
export interface CatePanel {
readonly id: string
setTitle(title: string): Promise<void>
/** List panels across this workspace's windows (requires the `panel` scope).
* THE single enumeration surface: the
* focused entry answers "what is the user looking at", and browser panels
* carry their `url` (there is no separate browser list). */
list(): Promise<CatePanelInfo[]>
/** Reveal/focus a panel by id (requires the `panel` scope). */
focus(panelId: string): Promise<unknown>
/** Close a panel through its normal dirty/running confirmation path. Does not
* reveal or focus the panel first (requires the `panel` scope). */
close(panelId: string): Promise<unknown>
}

/** One open panel, as reported by `cate.panel.list()`. `filePath` is the bare
* runtime path (same form as `workspace.get().rootPath`), present for panels
* backed by a file (editors, documents). `url` is present for browser panels
* (empty while on the start page). */
export interface CatePanelInfo {
panelId: string
type: string
title: string
focused: boolean
filePath?: string
url?: string
}

/** One interactable element in an accessibility `snapshot()`. `ref` is an opaque
* handle to pass back to `click`/`type`/`press`; it is only valid for the
* snapshot it came from (re-snapshot after a navigation or mutation). */
export interface CateBrowserRef {
ref: string
role: string
name: string
value?: string
}

/** Accessibility snapshot of a browser panel, from `cate.browser.snapshot()`. */
export interface CateBrowserSnapshot {
url: string
title: string
refs: CateBrowserRef[]
}

/** A file the user dragged onto this panel, delivered to `cate.files.onDrop`. */
Expand Down Expand Up @@ -86,8 +126,29 @@ export interface CateHost {
open(opts?: { resume?: string }): Promise<{ sessionId: string } | { error: string }>
send(sessionId: string, prompt: string): Promise<AgentTurnResult | { error: string }>
dispose(sessionId: string): Promise<unknown>
run(prompt: string): Promise<AgentTurnResult | { error: string }>
cancel(): Promise<unknown>
}
/** Drive Cate's browser panels (requires the `browser` scope + first-use user
* consent). These panels hold the user's real, logged-in session — treat it
* accordingly. `panelId` picks a target; omitted, the focused (or only)
* browser panel is used. */
browser: {
/** To enumerate open browser panels, use `cate.panel.list()`. */
open(opts: { url: string; panelId?: string }): Promise<{ panelId: string; url: string }>
reload(opts?: { panelId?: string }): Promise<{ ok: true }>
/** Capture a screenshot; returns a host filesystem path in the OS temp dir
* (a webview guest can't read it directly; a server-backed extension can). */
screenshot(opts?: { panelId?: string }): Promise<{ path: string }>
snapshot(opts?: { panelId?: string }): Promise<CateBrowserSnapshot>
click(opts: { ref: string; panelId?: string }): Promise<{ ok: true }>
type(opts: { ref: string; text: string; panelId?: string }): Promise<{ ok: true }>
/** Resolve once the panel stops loading (`timeoutMs` defaults to 5000,
* capped at 8000). Rejects in-band with `still-loading`. */
wait(opts?: { panelId?: string; timeoutMs?: number }): Promise<{ url: string; title: string; loading: false }>
/** Press a named key (Enter, Tab, Escape, Backspace, Delete, Space, arrows,
* PageUp/PageDown, Home, End) as TRUSTED input, so Enter submits forms.
* With `ref` the element is focused first. */
press(opts: { key: string; ref?: string; panelId?: string }): Promise<{ ok: true }>
}
storage: CateHostStorage
}
3 changes: 1 addition & 2 deletions extensions/cate.kitchensink/src/public/cate-host.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ interface CateHostTheme {
terminal: Record<string, string>
}

/** Result of one agent turn (`cate.agent.send` / `cate.agent.run`): the flattened
/** Result of one agent turn (`cate.agent.send`): the flattened
* `text` for convenience plus the raw final assistant `message` from pi (its role
* and content blocks — text, tool calls, etc.), or null if the turn produced none. */
interface AgentTurnResult {
Expand Down Expand Up @@ -92,7 +92,6 @@ interface CateHost {
/** Tear down the live session (pi's jsonl stays; reopen via `resume`). */
dispose(sessionId: string): Promise<unknown>
/** One-shot sugar over open -> send -> dispose. */
run(prompt: string): Promise<AgentTurnResult | { error: string }>
/** Abort the in-flight turn of this extension's session. */
cancel(): Promise<unknown>
}
Expand Down
33 changes: 25 additions & 8 deletions extensions/cate.kitchensink/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// POST /api/echo echoes the JSON body
// GET /ws WebSocket echo
// POST /api/cate-roundtrip server calls back into Cate over CATE_API
// POST /api/agent-run server runs one agent turn via cate.agent.run
// POST /api/agent-run server runs one agent turn (agent.open -> send -> dispose)

import http from 'http'
import crypto from 'crypto'
Expand Down Expand Up @@ -259,8 +259,9 @@ const server = http.createServer(async (req, res) => {
return
}

// The server runs one agent turn via cate.agent.run (needs the `agent` scope
// and first-use consent). It resolves only when the agent finishes.
// The server runs one agent turn by composing open -> send -> dispose (needs
// the `agent` scope and first-use consent; there is no one-shot host method).
// It resolves only when the agent finishes.
if (pathname === '/api/agent-run' && req.method === 'POST') {
const raw = await readBody(req)
let prompt = ''
Expand All @@ -274,14 +275,30 @@ const server = http.createServer(async (req, res) => {
return
}
try {
const result = unwrap(await callCateApi('cate.agent.run', { prompt })) as
| { text?: string; error?: string }
const opened = unwrap(await callCateApi('cate.agent.open', {})) as
| { sessionId?: string; error?: string }
| undefined
if (result && typeof result.error === 'string') {
sendJson(res, 200, { ok: false, error: result.error })
if (!opened || typeof opened.sessionId !== 'string') {
sendJson(res, 200, { ok: false, error: opened?.error ?? 'open-failed' })
return
}
sendJson(res, 200, { ok: true, text: result?.text ?? '' })
try {
const result = unwrap(
await callCateApi('cate.agent.send', { sessionId: opened.sessionId, prompt }),
) as { text?: string; error?: string } | undefined
if (result && typeof result.error === 'string') {
sendJson(res, 200, { ok: false, error: result.error })
return
}
sendJson(res, 200, { ok: true, text: result?.text ?? '' })
} finally {
// Best effort: the session must not outlive the one-shot turn.
try {
await callCateApi('cate.agent.dispose', { sessionId: opened.sessionId })
} catch {
/* noop */
}
}
} catch (err) {
const message = err instanceof Error ? err.message : String(err)
sendJson(res, 500, { ok: false, error: message })
Expand Down
63 changes: 62 additions & 1 deletion extensions/cate.mcp/src/_kit/cate-host.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,46 @@ export interface CateHostStorage {
export interface CatePanel {
readonly id: string
setTitle(title: string): Promise<void>
/** List panels across this workspace's windows (requires the `panel` scope).
* THE single enumeration surface: the
* focused entry answers "what is the user looking at", and browser panels
* carry their `url` (there is no separate browser list). */
list(): Promise<CatePanelInfo[]>
/** Reveal/focus a panel by id (requires the `panel` scope). */
focus(panelId: string): Promise<unknown>
/** Close a panel through its normal dirty/running confirmation path. Does not
* reveal or focus the panel first (requires the `panel` scope). */
close(panelId: string): Promise<unknown>
}

/** One open panel, as reported by `cate.panel.list()`. `filePath` is the bare
* runtime path (same form as `workspace.get().rootPath`), present for panels
* backed by a file (editors, documents). `url` is present for browser panels
* (empty while on the start page). */
export interface CatePanelInfo {
panelId: string
type: string
title: string
focused: boolean
filePath?: string
url?: string
}

/** One interactable element in an accessibility `snapshot()`. `ref` is an opaque
* handle to pass back to `click`/`type`/`press`; it is only valid for the
* snapshot it came from (re-snapshot after a navigation or mutation). */
export interface CateBrowserRef {
ref: string
role: string
name: string
value?: string
}

/** Accessibility snapshot of a browser panel, from `cate.browser.snapshot()`. */
export interface CateBrowserSnapshot {
url: string
title: string
refs: CateBrowserRef[]
}

/** A file the user dragged onto this panel, delivered to `cate.files.onDrop`. */
Expand Down Expand Up @@ -86,8 +126,29 @@ export interface CateHost {
open(opts?: { resume?: string }): Promise<{ sessionId: string } | { error: string }>
send(sessionId: string, prompt: string): Promise<AgentTurnResult | { error: string }>
dispose(sessionId: string): Promise<unknown>
run(prompt: string): Promise<AgentTurnResult | { error: string }>
cancel(): Promise<unknown>
}
/** Drive Cate's browser panels (requires the `browser` scope + first-use user
* consent). These panels hold the user's real, logged-in session — treat it
* accordingly. `panelId` picks a target; omitted, the focused (or only)
* browser panel is used. */
browser: {
/** To enumerate open browser panels, use `cate.panel.list()`. */
open(opts: { url: string; panelId?: string }): Promise<{ panelId: string; url: string }>
reload(opts?: { panelId?: string }): Promise<{ ok: true }>
/** Capture a screenshot; returns a host filesystem path in the OS temp dir
* (a webview guest can't read it directly; a server-backed extension can). */
screenshot(opts?: { panelId?: string }): Promise<{ path: string }>
snapshot(opts?: { panelId?: string }): Promise<CateBrowserSnapshot>
click(opts: { ref: string; panelId?: string }): Promise<{ ok: true }>
type(opts: { ref: string; text: string; panelId?: string }): Promise<{ ok: true }>
/** Resolve once the panel stops loading (`timeoutMs` defaults to 5000,
* capped at 8000). Rejects in-band with `still-loading`. */
wait(opts?: { panelId?: string; timeoutMs?: number }): Promise<{ url: string; title: string; loading: false }>
/** Press a named key (Enter, Tab, Escape, Backspace, Delete, Space, arrows,
* PageUp/PageDown, Home, End) as TRUSTED input, so Enter submits forms.
* With `ref` the element is focused first. */
press(opts: { key: string; ref?: string; panelId?: string }): Promise<{ ok: true }>
}
storage: CateHostStorage
}
63 changes: 62 additions & 1 deletion extensions/cate.mermaid/src/_kit/cate-host.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,46 @@ export interface CateHostStorage {
export interface CatePanel {
readonly id: string
setTitle(title: string): Promise<void>
/** List panels across this workspace's windows (requires the `panel` scope).
* THE single enumeration surface: the
* focused entry answers "what is the user looking at", and browser panels
* carry their `url` (there is no separate browser list). */
list(): Promise<CatePanelInfo[]>
/** Reveal/focus a panel by id (requires the `panel` scope). */
focus(panelId: string): Promise<unknown>
/** Close a panel through its normal dirty/running confirmation path. Does not
* reveal or focus the panel first (requires the `panel` scope). */
close(panelId: string): Promise<unknown>
}

/** One open panel, as reported by `cate.panel.list()`. `filePath` is the bare
* runtime path (same form as `workspace.get().rootPath`), present for panels
* backed by a file (editors, documents). `url` is present for browser panels
* (empty while on the start page). */
export interface CatePanelInfo {
panelId: string
type: string
title: string
focused: boolean
filePath?: string
url?: string
}

/** One interactable element in an accessibility `snapshot()`. `ref` is an opaque
* handle to pass back to `click`/`type`/`press`; it is only valid for the
* snapshot it came from (re-snapshot after a navigation or mutation). */
export interface CateBrowserRef {
ref: string
role: string
name: string
value?: string
}

/** Accessibility snapshot of a browser panel, from `cate.browser.snapshot()`. */
export interface CateBrowserSnapshot {
url: string
title: string
refs: CateBrowserRef[]
}

/** A file the user dragged onto this panel, delivered to `cate.files.onDrop`. */
Expand Down Expand Up @@ -86,8 +126,29 @@ export interface CateHost {
open(opts?: { resume?: string }): Promise<{ sessionId: string } | { error: string }>
send(sessionId: string, prompt: string): Promise<AgentTurnResult | { error: string }>
dispose(sessionId: string): Promise<unknown>
run(prompt: string): Promise<AgentTurnResult | { error: string }>
cancel(): Promise<unknown>
}
/** Drive Cate's browser panels (requires the `browser` scope + first-use user
* consent). These panels hold the user's real, logged-in session — treat it
* accordingly. `panelId` picks a target; omitted, the focused (or only)
* browser panel is used. */
browser: {
/** To enumerate open browser panels, use `cate.panel.list()`. */
open(opts: { url: string; panelId?: string }): Promise<{ panelId: string; url: string }>
reload(opts?: { panelId?: string }): Promise<{ ok: true }>
/** Capture a screenshot; returns a host filesystem path in the OS temp dir
* (a webview guest can't read it directly; a server-backed extension can). */
screenshot(opts?: { panelId?: string }): Promise<{ path: string }>
snapshot(opts?: { panelId?: string }): Promise<CateBrowserSnapshot>
click(opts: { ref: string; panelId?: string }): Promise<{ ok: true }>
type(opts: { ref: string; text: string; panelId?: string }): Promise<{ ok: true }>
/** Resolve once the panel stops loading (`timeoutMs` defaults to 5000,
* capped at 8000). Rejects in-band with `still-loading`. */
wait(opts?: { panelId?: string; timeoutMs?: number }): Promise<{ url: string; title: string; loading: false }>
/** Press a named key (Enter, Tab, Escape, Backspace, Delete, Space, arrows,
* PageUp/PageDown, Home, End) as TRUSTED input, so Enter submits forms.
* With `ref` the element is focused first. */
press(opts: { key: string; ref?: string; panelId?: string }): Promise<{ ok: true }>
}
storage: CateHostStorage
}
63 changes: 62 additions & 1 deletion extensions/cate.sqlite/src/_kit/cate-host.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,46 @@ export interface CateHostStorage {
export interface CatePanel {
readonly id: string
setTitle(title: string): Promise<void>
/** List panels across this workspace's windows (requires the `panel` scope).
* THE single enumeration surface: the
* focused entry answers "what is the user looking at", and browser panels
* carry their `url` (there is no separate browser list). */
list(): Promise<CatePanelInfo[]>
/** Reveal/focus a panel by id (requires the `panel` scope). */
focus(panelId: string): Promise<unknown>
/** Close a panel through its normal dirty/running confirmation path. Does not
* reveal or focus the panel first (requires the `panel` scope). */
close(panelId: string): Promise<unknown>
}

/** One open panel, as reported by `cate.panel.list()`. `filePath` is the bare
* runtime path (same form as `workspace.get().rootPath`), present for panels
* backed by a file (editors, documents). `url` is present for browser panels
* (empty while on the start page). */
export interface CatePanelInfo {
panelId: string
type: string
title: string
focused: boolean
filePath?: string
url?: string
}

/** One interactable element in an accessibility `snapshot()`. `ref` is an opaque
* handle to pass back to `click`/`type`/`press`; it is only valid for the
* snapshot it came from (re-snapshot after a navigation or mutation). */
export interface CateBrowserRef {
ref: string
role: string
name: string
value?: string
}

/** Accessibility snapshot of a browser panel, from `cate.browser.snapshot()`. */
export interface CateBrowserSnapshot {
url: string
title: string
refs: CateBrowserRef[]
}

/** A file the user dragged onto this panel, delivered to `cate.files.onDrop`. */
Expand Down Expand Up @@ -86,8 +126,29 @@ export interface CateHost {
open(opts?: { resume?: string }): Promise<{ sessionId: string } | { error: string }>
send(sessionId: string, prompt: string): Promise<AgentTurnResult | { error: string }>
dispose(sessionId: string): Promise<unknown>
run(prompt: string): Promise<AgentTurnResult | { error: string }>
cancel(): Promise<unknown>
}
/** Drive Cate's browser panels (requires the `browser` scope + first-use user
* consent). These panels hold the user's real, logged-in session — treat it
* accordingly. `panelId` picks a target; omitted, the focused (or only)
* browser panel is used. */
browser: {
/** To enumerate open browser panels, use `cate.panel.list()`. */
open(opts: { url: string; panelId?: string }): Promise<{ panelId: string; url: string }>
reload(opts?: { panelId?: string }): Promise<{ ok: true }>
/** Capture a screenshot; returns a host filesystem path in the OS temp dir
* (a webview guest can't read it directly; a server-backed extension can). */
screenshot(opts?: { panelId?: string }): Promise<{ path: string }>
snapshot(opts?: { panelId?: string }): Promise<CateBrowserSnapshot>
click(opts: { ref: string; panelId?: string }): Promise<{ ok: true }>
type(opts: { ref: string; text: string; panelId?: string }): Promise<{ ok: true }>
/** Resolve once the panel stops loading (`timeoutMs` defaults to 5000,
* capped at 8000). Rejects in-band with `still-loading`. */
wait(opts?: { panelId?: string; timeoutMs?: number }): Promise<{ url: string; title: string; loading: false }>
/** Press a named key (Enter, Tab, Escape, Backspace, Delete, Space, arrows,
* PageUp/PageDown, Home, End) as TRUSTED input, so Enter submits forms.
* With `ref` the element is focused first. */
press(opts: { key: string; ref?: string; panelId?: string }): Promise<{ ok: true }>
}
storage: CateHostStorage
}
Loading
Loading