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
70 changes: 68 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,68 @@
# AGENTS.md

Chat removal remains pending during the four-second Undo toast; the backend
delete is sent only on toast close. Window shutdown before close cancels that
uncommitted grace. A refused delete restores the Chat and shows an error,
matching History's existing rollback. The toast is not proof of durable deletion.

Enabled skills are offered by name enum in the model's skill tool schema;
disabled skills and empty catalogs are not offered. Content stays loaded only
through the skill tool. Native Code watch uses SDK reconnect and refreshes
durable state on each accepted connection; owner abort closes that subscription.

File tools canonicalize existing paths and the nearest existing parent of new
paths before external_directory checks. An in-project symlink cannot bypass
external read/write permission. The tool uses the checked canonical path.

Pending native call starts permit hangup only from that initiating window;
pending ownership uses request identity so an older same-window completion
cannot erase the newer pending owner. Active IPC ownership commits after start.

Native logout accepts the producer's 200 or 204 without opening logout_url.
Failed keyring reads preserve stored bytes. A rotated pair whose owner identity
read fails transiently is saved under the previously verified owner, while the
session stays signed out until a later successful identity verification.

Renderer bus reconnects after transport errors or unexpected EOF, with a
one-second retry owned by the active listeners. Last listener cancels stream
and retry. Query/message caches resync on interruption; no wire replay claim.
Automatic missing-ID Chat redirect replaces its history entry; Back never
returns to that invalid redirect entry.

Connected MCP tools default to ask by exact exposed tool name before explicit
user/agent/Bot rules. Saved grants remain effective for non-computer-input calls;
computer-use inputs keep mandatory per-action consent.

Subagents inherit parent-agent deny rules after their own rules, so plan's
read-only restriction cannot escape through task delegation or saved grants.
Computer-use observation is identified by a leading observation verb, never
an app/window/element noun anywhere in the action name.

Bot/task PATCH parsers leave omitted fields absent: rename preserves persona,
tool filter, permission rules, mascot and routine enabled state. Create defaults
remain unchanged. Locale parity compares keys in both directions; generated
browser-connector screenshots use per-test output, never tracked evidence.

Remote restore failures cannot block window creation: boot continues signed
out, retaining unreadable credential bytes. Credential-store set/delete still
refuse invalid stores; no automatic replacement or erasure is authorized.

Native call starts fence pending authentication by generation; superseding
start/end cannot create an orphan session or acquire IPC ownership late.
Browser-host request bodies accumulate bytes before UTF-8 decoding once;
the two-million-byte cap applies to bytes, not decoded characters.

SDK 0.4.9 / api-types 0.3.8 archives carry pending-image reconnect and visible
failure on live server-error frames. Relative consumer paths and lock hashes
must match producer archives; shared tests do not prove deployed voice service.
Resampling retains the previous input sample across 128-sample worklet chunks;
chunked 44.1/48 kHz signal matches contiguous interpolation exactly.

Local `glob` and `grep` enforce `read` permission on their search root and each
discovered file before returning names or reading contents. A per-file deny
cannot be bypassed through search. Regression: core session tests with denied
secret.txt on both tools.

Signed-in Work/Bot Channels manages owned group metadata through the approved exact
channel SDK pair. List/filter/create/get/rename/full member replacement/delete only.
Rename omits membership until explicitly edited; unlisted stored members remain
Expand Down Expand Up @@ -235,6 +298,7 @@ preview fixtures. Only visible gallery iframes load; offscreen previews unload t
| `CORTEX_STAGING_API_ORIGIN` | same | Required non-production HTTPS origin for staging; embedded in main only, invalid or ambiguous configuration fails build |
| `CORTEX_DATA_DIR` | `packages/desktop/src/main.ts`, `scripts/dev-api.ts` | Engine data dir (default `<userData>/engine`) |
| `CORTEX_CATALOG_URL` | same | Override `https://models.dev/api.json` |
| `CORTEX_UPDATE_FEED_URL` | `packages/desktop/src/main.ts`, `updater.ts` | Optional native update feed; HTTPS or literal loopback only |
| `CORTEX_LOCALE` | `main.ts` | Native menu locale and the app default when Settings has no saved locale (fallback: first supported OS language, else English) |
| `CORTEX_START_HASH` | `main.ts` | Initial route hash, e.g. `#/settings?section=providers` |
| `CORTEX_RENDERER_URL` | `main.ts` | Load the renderer from a dev server |
Expand Down Expand Up @@ -626,8 +690,10 @@ missing glyphs. See [`docs/i18n.md`](./docs/i18n.md).

**Production release and signing are not configured.** The old build/publish workflows were removed;
CI packages with `--publish never`, `-c.mac.identity=null`, `-c.mac.notarize=false`.
The old publish actions, `publish:` block and workflow README are removed. No auto-updater
is wired in main. Workflow configuration is not a passing run.
The old publish actions, `publish:` block and workflow README are removed. Main wires
the native updater only when `CORTEX_UPDATE_FEED_URL` supplies a validated HTTPS
or literal loopback feed. Production signing/publishing remains unconfigured.
Workflow configuration is not a passing run.

`publish-staging.yml` is an explicit main-only dispatch gated by exact-SHA successful
CI, including Windows build/package/launch. It defines unsigned Linux x64 AppImage/deb
Expand Down
10 changes: 5 additions & 5 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

65 changes: 65 additions & 0 deletions docs/audit-2026-10-08.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Desktop architecture and interaction audit

Fresh GitHub baseline: `e6028e5c`. One desktop PR accompanies the backend audit
PR. No merge or deployment is part of this work.

## Confirmed corrections

- Glob/grep enforce read permission on roots and discovered files. Canonical
paths and nearest existing parents prevent symlink escapes from bypassing
external-directory permission.
- Child sessions inherit parent-agent deny rules; plan delegation cannot turn
into a write. Connected MCP tools default to ask before explicit overrides.
Computer-use observation requires a leading observation verb, not an action
name containing an app/window noun. Enabled skills expose selectable names.
- Bot/task PATCH leaves omitted defaults absent, preserving permission,
persona, tool filter, mascot and paused scheduled-task state.
- Native call starts fence pending authentication and ownership. Hangup can
cancel a pending start without acquiring another window's call controls.
- Browser loopback bodies count bytes and decode UTF-8 once, preserving split
multibyte sequences and the existing browser-read output limit.
- Unreadable credentials cannot prevent window creation or erase ciphertext.
A rotated pair survives transient identity reads without promoting an
unverified account. Native logout accepts the producer's 200 response.
- Renderer event subscriptions reconnect and release their resources; cached
queries and transcripts resync. Code watches use the SDK reconnect policy
and refresh on each accepted connection. Missing-ID Chat redirects replace
history rather than trapping Back.
- Refused deferred Chat deletion restores the route and shows an error. The
four-second Undo grace remains uncommitted until the toast closes.
- Locale key parity is bidirectional; tests no longer overwrite tracked browser
connector screenshots. SDK 0.4.9/API types 0.3.8 adopt backend stream/audio fixes.

## Verification

Actual built Electron passed 239 end-to-end scenarios under Xvfb, including
owner transitions, auth, live chat, streaming and work flows. Subsequent targeted
checks covered later changes. The source integration suite passed 509 tests with
one optional skip, types/lint clean and no i18n audit problems. The final
forced-DELETE-500 Electron case restored the Chat and showed an error, with
types/lint clean. CI evidence must match the latest pushed SHA.

Native auth used real loopback transport. Corrupt-store boot opened an actual
Electron window signed out, preserving the corrupt bytes. Shared resampling
passed exact 44.1/48 kHz segmented-versus-contiguous signal tests; Chromium
verified actual microphone capture, worklet setup and playback credit.

Current rendered Chat redirects were inspected at 960x640 and 1440x900 in
light/dark themes after fonts and finite animations settled. No stable blank,
overlap or horizontal overflow appeared. Older early captures were rejected
as readiness failures, not counted as product failures.

## Decisions and acceptance boundaries

Undo deletion follows the existing History contract: DELETE occurs on toast
close. Window shutdown during the grace cancels an uncommitted deletion. The
toast alone never establishes durable deletion. The alleged pending Code-title
defect had no current source/probe evidence and was not retained.

Foreign tracked pairing screenshots are excluded from this change. Unselected
intermediate SDK archives are excluded; previous tracked archives remain for
provenance, while manifests select only the current pair.

Linux Electron checks are not signed macOS/Windows package or real-user provider
acceptance. Actual deployed Bot voice/device/farm availability remains outside
the controlled test evidence. A finite audit cannot prove no unknown bugs remain.
4 changes: 4 additions & 0 deletions docs/browser-connector.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Chrome connector

Loopback bodies enforce the two-million-byte cap before decoding UTF-8 once;
split multibyte sequences survive transport. `browser_read` retains its existing
50,000-character limit independently of the HTTP body cap.

`packages/chrome-extension` (Manifest V3, load unpacked from the folder the **Install extension** button opens) talks to the
desktop app over a loopback HTTP endpoint (`127.0.0.1`, ports 47821-47825) owned by `packages/desktop/src/browser-host.ts`.
Chrome native messaging was not used: it needs a per-OS manifest registration and an extension ID that only exists after store publishing.
Expand Down
5 changes: 5 additions & 0 deletions docs/connection-modes.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Connection modes

Native logout accepts 200/204, ignoring logout_url. Keyring read failures keep
the ciphertext. After token rotation, transient identity-read failures preserve
the new pair under the previously verified owner but do not promote a signed-in
identity. Revoked tokens and owner mismatches still clear the stored pair.

## Task8 bounded Work/Bot

Work/Bot -> Channels uses the exact e5e-approved SDK pair for owned group metadata.
Expand Down
30 changes: 29 additions & 1 deletion docs/engine.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
# Engine

Chat deletion waits for the four-second Undo grace; a closed window cancels
an uncommitted grace, not a persisted delete. Refused deletes restore the Chat
and display an error. Enabled skill names appear in the model tool's enum,
while SKILL.md content loads only when selected.

File-path permission checks use canonical targets, including existing parent
directories of files to create. Symlink escapes require external_directory
approval before read/write or discovered-file content reaches the model.

The client reports unexpected event-stream EOF; the renderer owns reconnect
and releases its stream/retry when the last subscriber leaves. Cached local
queries and transcript snapshots re-read after interruption, without replay.

Child sessions inherit parent-agent deny rules; plan delegation cannot write,
edit or execute shell commands. Saved grants never override those denials.

Partial Bot updates preserve omitted configuration and permission fields;
partial scheduled-task updates preserve pause state. Create defaults never
apply implicitly to a PATCH.

Native boot opens the window signed out when persisted remote restore fails;
invalid credential-store bytes remain untouched. Calls fence pending starts
before authentication resolves and before acquiring IPC ownership.

`@cortex/core`, created with `createCore(opts)` (`packages/core/src/index.ts`). Nothing
touches the network or starts timers until `start()`, which registers the computer-use
preset (when a driver was found), scans plugins, connects enabled MCP servers and starts
Expand Down Expand Up @@ -260,13 +284,17 @@ Agents (`agent.ts`): `build` (default), `plan` (read-only; write/edit/bash denie
`general` and `explore` (subagents). Tools (`tool.ts`): `read write edit list glob grep
bash webfetch todowrite task skill`. File tools exist only when the session has a
`directory`; paths outside it ask `external_directory`. `task` spawns a child session;
children never get `task`. MCP tools are exposed as `<server>_<tool>` and always ask.
children never get `task`. MCP tools are exposed as `<server>_<tool>` and default
to ask before user/agent/Bot rules; explicit allow and saved grants still apply.
Computer-use inputs always ask even under allow and never save Always.

## Permissions

Rules `(tool, pattern, allow|ask|deny)`, last match wins, default ask. Order: defaults
(`bash`, `write`, `edit`, `external_directory` ask) → user rules
(`/api/permissions/rules`) → agent → bot. A configured deny beats saved approvals.
`glob` and `grep` check `read` permission on the search root and every discovered
file before returning names or reading contents; per-file denials remain effective.
Replies: `once`, `always` (saved per project directory), `reject` (stops the loop and
cancels the session's other pending asks). Computer-use input tools never save `always`
([computer-use.md](./computer-use.md)).
Expand Down
2 changes: 1 addition & 1 deletion packages/app/package.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{ "name": "@cortex/app", "version": "0.2.0", "private": true, "type": "module", "dependencies": { "@cortex/api-types": "file:../../vendor/trunk/cortex-api-types-0.3.5.tgz", "@cortex/client": "workspace:*", "@cortex/schema": "workspace:*", "@cortex/i18n": "workspace:*" } }
{ "name": "@cortex/app", "version": "0.2.0", "private": true, "type": "module", "dependencies": { "@cortex/api-types": "file:../../vendor/trunk/cortex-api-types-0.3.8.tgz", "@cortex/client": "workspace:*", "@cortex/schema": "workspace:*", "@cortex/i18n": "workspace:*" } }
4 changes: 2 additions & 2 deletions packages/app/src/kit/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ html[data-vt="theme"]::view-transition-group(content), html[data-vt="theme"]::vi
.dock .hint { font-size: 11px; color: var(--t3); }
.code { border-radius: 12px; background: var(--code-bg); border: 1px solid var(--line); overflow: hidden; margin: 6px 0 10px; }
.code-head { height: 32px; display: flex; align-items: center; padding: 0 6px 0 12px; border-bottom: 1px solid var(--line); color: var(--t2); font-size: 11px; }
.code-head span { flex: 1; font-family: var(--mono); }
.code-head span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: var(--mono); }
.code pre { margin: 0; padding: 10px 12px; font-family: var(--mono); font-size: 12px; line-height: 19px; overflow: auto; }
.typing { display: inline-flex; gap: 3px; } .typing i { width: 5px; height: 5px; border-radius: 3px; background: var(--t3); animation: blink 1s infinite both; }
.typing i:nth-child(2) { animation-delay: .15s; } .typing i:nth-child(3) { animation-delay: .3s; }
Expand Down Expand Up @@ -409,7 +409,7 @@ textarea.input { height: 80px; padding: 8px 10px; resize: none; }
.diff { margin: 0 14px 14px; border-radius: 12px; border: 1px solid var(--line); background: var(--code-bg); overflow: hidden; animation: rise 280ms var(--ease-out) both; }
.split-r:has(> .diff) { overflow: auto; }
/* Header (32px), one code line (20px), pre padding (16px), borders (2px). */
.split-r > .diff { display: grid; grid-template-rows: auto minmax(0, 1fr); min-height: 70px; }
.split-r > .diff { display: grid; grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); min-height: 70px; }
.diff pre, .term { margin: 0; padding: 8px 0; font-family: var(--mono); font-size: 12px; line-height: 20px; overflow: auto; }
.diff pre div { padding: 0 14px; white-space: pre; }
.diff .add { background: color-mix(in srgb, var(--green) 14%, transparent); }
Expand Down
4 changes: 2 additions & 2 deletions packages/app/src/screens/chat/live-chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ function LocalHome() {
export function Chat() {
const { params, go, entryKey } = useNav();
const id = params.get("id");
React.useEffect(() => { if (!id && !isPreview()) go("home"); }, [id]); // eslint-disable-line react-hooks/exhaustive-deps
React.useEffect(() => { if (!id && !isPreview()) go("home", undefined, null, true); }, [id]); // eslint-disable-line react-hooks/exhaustive-deps
if (!isPreview() && params.has("source")) return <RemoteBoundary key={entryKey} explicit />;
if (id) return <LiveChat key={id} id={id} />;
const start = previewChatStart();
Expand Down Expand Up @@ -348,7 +348,7 @@ function LiveChat({ id }: { id: string }) {
if (opening.current) return;
let undone = false;
toast.add({ title: t("chat.toast.deleted"), description: title, data: { undo: true, icon: "trash", onUndo: () => { undone = true; go("chat", { id }); } },
onClose: () => { if (!undone) api.sessions.delete(id).catch(() => {}); } });
onClose: () => { if (!undone) api.sessions.delete(id).catch(() => { if (readHash().route === "home") go("chat", { id }); toast.add({ title: t("chat.err.generic.title"), data: { icon: "alert-triangle" } }); }); } });
go("home");
};
const movable = session.state === "ready" && session.data.kind === "chat" && !session.data.parentID;
Expand Down
Loading
Loading