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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

## Unreleased

- Reworked "Windows after restart" into one "Agent sessions after restart" model: **Don't save**, **Reopen windows** (new conversations), or **Continue conversations** (the old "on" migrates here). Claude Code and OpenCode now resume their own conversation by the id their lifecycle hook reported, as Codex does (`claude --resume`, `opencode --session`); two cards of one CLI in one folder no longer continue the same conversation. Finished agents come back stopped with Restart / Continue instead of rerunning, the card options menu has **Don't restore this card**, and session records (v2, read-compatible with v1) keep no scrollback, prompts or secrets.
- Made the agent orchestration endpoint an explicit setting (Settings → Agents → "Agent orchestration endpoint", `agentControlEnabled`, off by default; `--agent-control` / `CANVASTTY_AGENT_CONTROL=1` still force it on for one launch) that starts and stops the endpoint at runtime, and added an **Orchestrator** role to the launch dialog next to the normal/YOLO profile: the session keeps the provider you opened the dialog for, gets `CANVASTTY_CONTROL_CONNECTION` and `CANVASTTY_CONTROL_CLI` in its environment so the bundled CLI works without setup, shows an "Orchestrator" badge, keeps its role across restore, and the dialog offers to enable the endpoint first when it is off instead of enabling anything silently. The endpoint's `create` now accepts every agent provider (`codex, claude, qwen, kimi, opencode, hermes, grok, omp, pi`) and reports `capabilities { result, menus }` per worker on `create` and `list`: both are `true` for Codex only; other providers' `screen` has no menu interaction, `choose`/`dismiss` fail with `NOT_SUPPORTED`, `send` relies on the idle status alone, and `result` completes as `no_result`.
- Added a native Codex orchestration CLI (`agent-control/canvastty-control.mjs`, documented in `agent/orchestrator/SKILL.md`) behind `--agent-control` or `CANVASTTY_AGENT_CONTROL=1`: a local controller creates Codex sessions in a project directory, sends work, observes bounded terminal output against a screen revision, and collects the final answer. Each controller sees only the sessions it created, grants are bound to the session generation, mutation IDs are deduplicated, and only controlled sessions opt into authenticated Stop-hook result capture. No automatic approval or terminal deletion endpoint is included.
- Added the opt-in Even G2 companion (Settings → Controls → Even G2, with the Even App companion under `integrations/even-g2`): Bonjour discovery, short-lived SRP-6a pairing with a six-digit code and explicit device approval, encrypted local requests and audio, per-session grants, bounded terminal presentation on the glasses HUD, local speech recognition through the pinned transcribe.cpp helper (bundled on macOS only), and session creation through the existing desktop launcher. The final answer of a Codex turn reaches the companion only for sessions spawned while the companion is enabled: the runtime hook reports it under a separate per-session grant, bounded to 4000 characters, and the gateway refuses it for any other session.
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

## Unreleased

- «Окна после перезапуска» стали единой моделью «Сессии агентов после перезапуска»: **Не сохранять**, **Открыть окна** (новые разговоры) или **Продолжить разговоры** (прежнее «включено» переходит сюда). Claude Code и OpenCode теперь, как и Codex, продолжают свой разговор по id, который сообщил их lifecycle hook (`claude --resume`, `opencode --session`); две карточки одного CLI в одной папке больше не продолжают один и тот же разговор. Завершённые агенты возвращаются остановленными с кнопками «Перезапустить» / «Продолжить», в меню карточки есть **Не восстанавливать это окно**, а записи сессий (v2, совместимы с v1 при чтении) не хранят буфер, промпты и секреты.
- Эндпоинт оркестрации агентов стал явной настройкой (Настройки → Агенты → «Эндпоинт оркестрации агентов», `agentControlEnabled`, по умолчанию выключен; `--agent-control` / `CANVASTTY_AGENT_CONTROL=1` по-прежнему принудительно включают его на один запуск), которая запускает и останавливает эндпоинт на лету, а в диалог запуска рядом с профилем normal/YOLO добавлена роль **Оркестратор**: сессия сохраняет провайдера, для которого открыт диалог, получает в окружении `CANVASTTY_CONTROL_CONNECTION` и `CANVASTTY_CONTROL_CLI`, чтобы встроенный CLI работал без настройки, показывает бейдж «Оркестратор», сохраняет роль при восстановлении, а при выключенном эндпоинте диалог предлагает сначала включить его, ничего не включая молча. `create` эндпоинта теперь принимает любого провайдера-агента (`codex, claude, qwen, kimi, opencode, hermes, grok, omp, pi`) и в ответах `create` и `list` сообщает `capabilities { result, menus }` для каждого воркера: оба значения `true` только для Codex; у остальных провайдеров `screen` не содержит взаимодействия с меню, `choose`/`dismiss` завершаются ошибкой `NOT_SUPPORTED`, `send` опирается только на статус idle, а `result` завершается как `no_result`.
- Добавлен нативный CLI оркестрации Codex (`agent-control/canvastty-control.mjs`, описан в `agent/orchestrator/SKILL.md`), включаемый флагом `--agent-control` или `CANVASTTY_AGENT_CONTROL=1`: локальный контроллер создаёт сессии Codex в каталоге проекта, отправляет задачи, наблюдает ограниченный вывод терминала относительно ревизии экрана и получает итоговый ответ. Контроллер видит только созданные им сессии, гранты привязаны к поколению сессии, идентификаторы мутаций дедуплицируются, и только управляемые сессии включают аутентифицированный захват результата через Stop-hook. Автоматического одобрения и удаления терминалов нет.
- Добавлен опциональный компаньон Even G2 (Настройки → Управление → Even G2, приложение-компаньон в `integrations/even-g2`): обнаружение через Bonjour, короткоживущее сопряжение SRP-6a с шестизначным кодом и явным подтверждением устройства, шифрованные локальные запросы и аудио, гранты на сессию, ограниченное отображение терминала на HUD очков, локальное распознавание речи через закреплённый helper transcribe.cpp (поставляется только для macOS) и создание сессий через обычный лаунчер. Итоговый ответ хода Codex попадает в компаньон только для сессий, запущенных при включённом компаньоне: runtime-hook передаёт его по отдельному гранту сессии с ограничением 4000 символов, а gateway отклоняет его для любой другой сессии.
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

## Unreleased

- 将“重启后的窗口”改为统一的“重启后的智能体会话”模型:**不保存**、**重新打开窗口**(新会话)或 **继续会话**(原先的“开启”迁移到此项)。Claude Code 和 OpenCode 现在与 Codex 一样,按其 lifecycle hook 报告的 id 继续自己的会话(`claude --resume`、`opencode --session`);同一文件夹中同一 CLI 的两张卡片不再继续同一个会话。已结束的智能体恢复为停止状态,提供“重启”/“继续”,卡片选项菜单提供 **不恢复此卡片**,会话记录(v2,可读取 v1)不保存 scrollback、提示词或密钥。
- 将代理编排端点改为显式设置(设置 → 代理 → “代理编排端点”,`agentControlEnabled`,默认关闭;`--agent-control` / `CANVASTTY_AGENT_CONTROL=1` 仍可为单次启动强制开启),并在运行时按设置启动和停止端点;启动对话框在 normal/YOLO 配置旁新增 **Orchestrator(编排器)** 角色:会话保留打开对话框时的提供方,环境中携带 `CANVASTTY_CONTROL_CONNECTION` 和 `CANVASTTY_CONTROL_CLI`,使内置 CLI 无需配置即可工作,卡片显示 “Orchestrator” 徽标,恢复会话时保留角色;端点关闭时对话框会先提示并提供开启按钮,而不会静默开启任何内容。端点的 `create` 现在接受所有代理提供方(`codex, claude, qwen, kimi, opencode, hermes, grok, omp, pi`),并在 `create` 和 `list` 响应中为每个工作会话报告 `capabilities { result, menus }`:仅 Codex 两者都为 `true`;其他提供方的 `screen` 没有菜单交互,`choose`/`dismiss` 返回 `NOT_SUPPORTED`,`send` 仅依据 idle 状态,`result` 以 `no_result` 结束。
- 新增原生 Codex 编排 CLI(`agent-control/canvastty-control.mjs`,文档见 `agent/orchestrator/SKILL.md`),通过 `--agent-control` 或 `CANVASTTY_AGENT_CONTROL=1` 启用:本地控制器在项目目录中创建 Codex 会话、发送任务、按屏幕修订号观察有界的终端输出并收集最终回答。每个控制器只能看到自己创建的会话,授权绑定到会话代次,变更 ID 去重,且只有受控会话会启用经过认证的 Stop-hook 结果捕获。不包含自动批准或删除终端的端点。
- 新增可选的 Even G2 伴侣(设置 → 控制 → Even G2,伴侣应用位于 `integrations/even-g2`):Bonjour 发现、带六位码和显式设备批准的短期 SRP-6a 配对、加密的本地请求与音频、按会话授权、眼镜 HUD 上的有界终端展示、通过固定版本的 transcribe.cpp helper 进行本地语音识别(仅 macOS 随包提供),以及通过现有桌面启动器创建会话。Codex 一轮的最终回答只会送达在伴侣启用期间启动的会话:runtime hook 以单独的会话授权上报,限制为 4000 个字符,gateway 会拒绝任何其他会话的该字段。
Expand Down
4 changes: 2 additions & 2 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Electron main process
- `src/preload/index.ts` exposes only the typed capabilities the renderer needs. Node integration stays disabled; context isolation and sandbox stay enabled.
- Terminal file drops resolve native `File` objects through preload's `webUtils.getPathForFile`, format paths for the host's default shell, and paste through xterm without submitting. File contents are not read and no new main-process IPC is exposed.
- `src/main/ipc/registerIpc.ts` owns native side effects and validates access to persisted media.
- `src/main/services/TerminalManager.ts` is the source of truth for live session state and PTY buffers. It keeps scrollback in a bounded chunk buffer and coalesces PTY data into 16ms IPC batches so clear/redraw sequences reach xterm together. A plain terminal starts `idle`; an agent stays `unavailable` until its provider emits a machine-readable lifecycle signal. Codex, Claude Code, Qwen Code, Kimi Code, OpenCode, Hermes, and Grok Build then transition through `idle`, `working`, and `needs_approval` from provider hooks; exact Claude/Qwen OSC 0/2 markers remain a compatibility fallback. Human-readable terminal text and PTY existence are never treated as activity. Process exit provides only `done` or `failed`. An exited PTY may be restarted under the same session ID while preserving its card, bounds, title, and scrollback. Optional restart persistence writes only provider/profile/title/cwd/bounds descriptors through `TerminalSessionStore`; it never writes PTY scrollback, child environment, or capabilities. Restored agents use each provider's native project-scoped continue mode, while plain terminals reopen as fresh shells in their saved folder.
- `src/main/services/TerminalManager.ts` is the source of truth for live session state and PTY buffers. It keeps scrollback in a bounded chunk buffer and coalesces PTY data into 16ms IPC batches so clear/redraw sequences reach xterm together. A plain terminal starts `idle`; an agent stays `unavailable` until its provider emits a machine-readable lifecycle signal. Codex, Claude Code, Qwen Code, Kimi Code, OpenCode, Hermes, and Grok Build then transition through `idle`, `working`, and `needs_approval` from provider hooks; exact Claude/Qwen OSC 0/2 markers remain a compatibility fallback. Human-readable terminal text and PTY existence are never treated as activity. Process exit provides only `done` or `failed`. An exited PTY may be restarted under the same session ID while preserving its card, bounds, title, and scrollback. Optional restart persistence (Settings → General, "Agent sessions after restart": Don't save / Reopen windows / Continue conversations) writes session records v2 through `TerminalSessionStore`: the card descriptor, the state at quit or exit, the provider conversation id a lifecycle hook reported (`threadId`: Codex thread or Claude session UUID, OpenCode `ses_` id), the per-card restore flag, and two opaque plugin slots (launch options and an environment reference, at most 4 KB each). It never writes PTY scrollback, prompts, child environment, secrets, or capabilities. Plain terminals reopen as fresh shells in their saved folder.
- `src/main/services/LimitsService.ts` reads Codex through the installed CLI's app-server protocol and Claude, Kimi, OpenCode Go, and Grok Build through their provider usage or billing endpoints. Qwen Code is multi-provider and exposes no provider-neutral read-only quota protocol, so its adapter reports `cli-not-found` or `unsupported-protocol` and never invents percentages. Provider credentials are read only inside the trusted main process, sent only to the matching provider over HTTPS, and never logged or exposed over IPC. The service owns timeout, structural normalization, caching, stale fallback, and subprocess cleanup; raw provider responses never cross IPC.
- `src/main/services/SettingsStore.ts` normalizes every update and persists through a serialized atomic write. Canvas regions and sticky notes have independent persistence gates: disabling one keeps its live objects for the current process but omits that collection from the disk snapshot and therefore from the next launch. The configurable canvas launcher and UI scale use the same boundary; transient window stacking does not.
- `src/main/services/PluginManager.ts` installs ready-to-run repositories without executing package scripts during install/update, rejects symlinks and oversized packages, persists the enabled registry, serves only contained package files, and enforces per-plugin permissions/storage quotas. Optional native agent-hook entries remain off by default; explicit per-hook trust is persisted in the plugin registry and compiled into a separate private atomic runtime registry. Update, module replacement, plugin disable, and uninstall revoke that trust before executable files change.
Expand Down Expand Up @@ -91,7 +91,7 @@ Keep domain decisions in pure selectors such as `homeModel.ts`, orchestration in

## Session flow

When terminal restore is enabled, startup loads validated window descriptors before the renderer and relaunches each saved agent through its provider's native continue mode. Stable CanvasTTY session IDs preserve card identity, while region membership remains spatial and requires the complete card bounds to be inside the region at region-drag start. Grok restoration still waits for the renderer-measured xterm grid before spawning. Turning restore off clears the descriptor store immediately; it remains off by default.
When session restore is on, startup loads validated records before the renderer and restores parents before children (`sessionRestorePlan.ts`). "Continue conversations" resumes the conversation id the card's lifecycle hook reported (`codex resume <id>`, `claude --resume <id>`, `opencode --session <id>`); without one Codex opens its own resume picker, and other CLIs use their "latest in this folder" flag only when that CLI has exactly one card in the folder, and otherwise start fresh and say so on the card. A plain Restart starts a new conversation and forgets the id; Continue on a stopped card resumes it. Cards that had already exited come back stopped with Restart / Continue, cards marked "Don't restore this card" do not come back, and a card placed in an environment that is unavailable comes back stopped with the reason and is never started locally. Stable CanvasTTY session IDs preserve card identity, while region membership remains spatial and requires the complete card bounds to be inside the region at region-drag start. Grok restoration still waits for the renderer-measured xterm grid before spawning. Turning restore off clears the descriptor store immediately; it remains off by default.

1. Home requests a terminal or opens a provider-specific launch card.
2. `App` sends a typed `terminal:create` request.
Expand Down
10 changes: 10 additions & 0 deletions src/agent-runtime/hook-helper.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ const turnId = firstString(
input?.prompt_id,
input?.promptId
);
// The provider's own conversation id; runtime-client keeps it only in a shape that provider issues.
const threadId = firstString(
input?.session_id,
input?.sessionId,
input?.thread_id,
input?.threadId,
input?.conversation_id,
input?.conversationId
);
const finalAnswer = state === "idle" && event === "Stop" && typeof input?.last_assistant_message === "string"
? input.last_assistant_message
: null;
Expand All @@ -55,6 +64,7 @@ await reportLifecycle({
state,
event,
turnId,
...(threadId ? { threadId } : {}),
...(result === undefined ? {} : { result }),
...(lastAssistantMessage === undefined ? {} : { lastAssistantMessage })
});
Expand Down
4 changes: 3 additions & 1 deletion src/agent-runtime/opencode-plugin.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ export const CanvasTTYLifecycle = async () => ({
rootSessionId = stringField(session.id, sessionId);
rootWorking = false;
if (!rootSessionId) return;
if (lifecycleEnabled) await reportLifecycle({ state: "idle", event: event.type, turnId: rootSessionId });
if (lifecycleEnabled) {
await reportLifecycle({ state: "idle", event: event.type, turnId: rootSessionId, threadId: rootSessionId });
}
runPluginHooks("session-start", event.type, event);
return;
}
Expand Down
Loading
Loading