Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
cb3d61a
plan: MCP tool doors — progressive disclosure for both tool surfaces
Jul 2, 2026
8c2e376
doors: instrument tool-surface size
Jul 2, 2026
d9c21bc
doors: taxonomy + DoorState
Jul 2, 2026
0b20240
doors: ghost loop progressive disclosure (flagged)
Jul 2, 2026
fdfc756
fix(ghost): max_completion_tokens for api.openai.com (o-series/gpt-5.…
Jul 2, 2026
effaec3
doors: auto mode for small models, 8k budget
Jul 2, 2026
9f7e155
fix(web-pane): survive React remounts — delayed destroy + reuse on cr…
Jul 2, 2026
eb22144
fix(web-pane): adopt live view across group re-keying; ferricula off …
Jul 2, 2026
1680285
doors: sailfish provider + config pane wiring (Phase 6)
Jul 2, 2026
df058ce
fix(ghost): consent-exempt own agent by token; add terminal_set_windo…
Jul 2, 2026
71f40ce
fix(ghost): trusted own-agent token (no consent prompts); resize tool…
Jul 2, 2026
7486e10
fix(ghost): stop tool loops (split spam) + pretty full-JSON tool rows…
Jul 2, 2026
31b338d
fix(shell): boot banner prints once + detects sailfish
Jul 2, 2026
041cad3
feat: window pixel size in terminal_status; tok/s HUD; click-to-focus…
Jul 2, 2026
4de188a
fix: send WindowBounds on focus + delayed retries (WS-connect race)
Jul 2, 2026
e0efbb1
fix(status): broadcast window bounds on sidecar WS connect
Jul 2, 2026
557f733
chore(status): drop WindowBounds debug log
Jul 2, 2026
14bad6e
feat(context-menu): add 'Open Hyperia' to web-pane + terminal right-c…
Jul 2, 2026
757a508
fix(context-menu): 'Open Hyperia' on the terminal-body right-click too
Jul 2, 2026
34d9152
feat(shell): model lights + max/avg TPS; fix provider mislabel + cont…
Jul 2, 2026
4fb23a7
feat(ghost): runaway-pane guard + t/s counts thinking + /api/ghost/de…
Jul 2, 2026
f2b5a1e
feat(status): pre-rendered window `size` string in terminal_status
Jul 2, 2026
8d17d3f
feat(provider): OpenAI /v1/responses support for gpt-5-codex / *-pro …
Jul 2, 2026
fc78e1b
chore(agent): add ornith:latest to the curated Ollama model list for …
Jul 2, 2026
e7eb1b3
feat(models): central model registry + small-model payload slim + tru…
Jul 2, 2026
4cc1299
test(doors): compute cap test from live taxonomy
Jul 2, 2026
d9c2994
fix(ollama): require `reply` in structured output + thought fallback
Jul 2, 2026
a94606b
fix(ollama): silence tool-turn narration — reply only speaks on the a…
Jul 2, 2026
4953220
fix(stickys): open at default size, not 25% of it
Jul 2, 2026
874cefa
feat(ollama): live thinking streaming + fast Escape + door-callable e…
Jul 2, 2026
e30238c
fix(dev): end the launch restart-storm + duplicate-instance squash fo…
Jul 2, 2026
861ee70
fix(dev): dev.ps1 pure ASCII - PS5.1 misparses UTF-8 em-dashes withou…
Jul 2, 2026
282cf38
feat(shell): Hyperia Agent gets its own labeled tab + hyperia.local i…
Jul 2, 2026
375afd7
feat(agent): auto-offer all installed ollama cloud-tag models in the …
Jul 2, 2026
86bcea3
feat: Token Maximus config + /guide start page + W/S/A picker with ve…
Jul 2, 2026
05a18a5
fix(picker+shell): W/S/A replace enter badges, sanitize version, shel…
Jul 2, 2026
e3e16f8
fix(picker): drop the W/S/A chips after labels — the in-box letter ba…
Jul 2, 2026
9795c7e
chore(menus): rename 'Open Hyperia' to 'Hyperia Agent' in all right-c…
Jul 2, 2026
bcb92bf
feat(shell): HYPERIA SHELL title, gear by reset, badge metrics with b…
Jul 2, 2026
92e533c
feat: shell HUD rework + cheap/fast provider defaults
Jul 2, 2026
5eff5b3
feat: shell HUD rework, cheap/fast defaults, clickable config.agent -…
Jul 2, 2026
6d7db78
fix(stickys): resizing a note no longer rewrites the global default size
Jul 3, 2026
160ae7d
feat(config): API key [set] tag + in-box remove button + Open config …
Jul 3, 2026
8b1bd0b
feat(config): key [set] + in-box remove, Open config button, terse sa…
Jul 3, 2026
a05fe64
feat(config): edit-config sticky endpoint, services cleanup, ollama p…
Jul 3, 2026
77bdee7
fix(main): register /api/agent/config/edit on the AppState router; v0…
Jul 3, 2026
e4de95d
fix(omnibox): bare words search DuckDuckGo instead of https://<word>
Jul 3, 2026
9d72f8b
feat(menus): 'Picker' on the web-pane band menu; 'Switch Shell' renam…
Jul 3, 2026
6f0f655
chore: v0.15.14 installer (omnibox DDG fallback + Picker menus included)
Jul 3, 2026
d4ad2ff
fix(config): remove button right-justified inside the key box, no wrap
Jul 3, 2026
e047671
fix(config): square the select's bottom corners while its dropdown is…
Jul 3, 2026
b372149
docs(plan): per-pane multi-agent code editor spec (planner input)
Jul 3, 2026
9eeabac
feat(doors): contract forbids asking the human to open doors — act in…
Jul 3, 2026
3579a9c
fix(shell): HUD badge flash — subtle yellow, throttled (no more strob…
Jul 3, 2026
0a678b0
feat(ghost): full-send rules + web-pane routing + CR submit; plan age…
Jul 3, 2026
9490d75
fix(ghost): escape CR/LF mentions in SYSTEM_PROMPT (unescaped quotes …
Jul 3, 2026
d707381
chore: v0.15.15 version bump (installer shipped)
Jul 3, 2026
87db76b
fix(shell+config): anti-fabrication rule, TPS labels + OpenAI TPS, HU…
Jul 3, 2026
cb84fc0
fix(web-pane+config): live-uid resolution (invisible context menu), s…
Jul 3, 2026
31af0eb
refactor(web-pane): Phase 6 — remove the legacy <webview> path
Jul 3, 2026
6f04398
feat(doors): gate external MCP tool surface behind opt-in doors (#129)
Jul 3, 2026
45093a3
fix(web-pane): dwell-gate the header freeze-swap; bell background tab…
Jul 3, 2026
7e2f897
feat(shell): title tick per completed turn — drives the standard tab …
Jul 3, 2026
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
3 changes: 3 additions & 0 deletions .claude/agent-memory/rust-sidecar-engineer/MEMORY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Memory Index

- [Build exe-lock workaround](build_exe_lock_workaround.md) — rename the running sidecar exe (don't kill Electron) so `cargo build --release` can write the binary while `yarn start` holds it
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
name: build-exe-lock-workaround
description: How to complete `cargo build --release` when a live `yarn start` Electron holds the sidecar exe
metadata:
type: project
---

`cargo build --release --manifest-path sidecar/Cargo.toml` compiles fine but fails at the final link/copy step with `error: failed to remove file ... hyperia-sidecar.exe / Access is denied (os error 5)` when a Hyperia dev instance is running. The Electron dev process (`yarn start`) auto-respawns the sidecar, so `Stop-Process` on the sidecar alone loses the race — a new PID re-locks the exe within a second.

**Why:** the output binary IS the running process image; Windows won't let cargo delete-in-place a file backing a live process, and the bridge respawns it.

**How to apply:** Windows allows *renaming* a running exe even while locked. Before building, rename it out of the way so cargo writes a fresh one; the live process keeps using the renamed file:
`Rename-Item sidecar/target/release/hyperia-sidecar.exe hyperia-sidecar.exe.old` then build. Clean up the `.old` afterward (may still be locked until the old process exits — best-effort `rm -f`). Do NOT kill the whole Electron/`yarn start` session to build — it's the human's live workspace. This matches the user's auto-memory note about the sidecar port-9800 clash / closing the installed copy, but the rename trick avoids disrupting the dev session entirely.
28 changes: 27 additions & 1 deletion app/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@
for (const [uid, tracked] of trackedSessions) {
sendSessionRegister(uid, tracked);
}
// Replay window bounds now that the socket is live. The per-window
// create/focus/resize sends can fire before the sidecar WS connects
// (connect happens last at launch), so send() no-ops silently — this
// guarantees the sidecar learns every window's pixel size on connect.
try {
const winList: BrowserWindow[] = Array.from((app as any).getWindows?.() || []);

Check failure on line 118 in app/bridge.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Unsafe call of an `any` typed value

Check failure on line 118 in app/bridge.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Unsafe argument of type `any` assigned to a parameter of type `Iterable<BrowserWindow> | ArrayLike<BrowserWindow>`

Check failure on line 118 in app/bridge.ts

View workflow job for this annotation

GitHub Actions / build (macos-latest)

Unsafe call of an `any` typed value

Check failure on line 118 in app/bridge.ts

View workflow job for this annotation

GitHub Actions / build (macos-latest)

Unsafe argument of type `any` assigned to a parameter of type `Iterable<BrowserWindow> | ArrayLike<BrowserWindow>`
for (const w of winList) updateWindowBounds(w);
} catch { /* best-effort */ }

Check failure on line 120 in app/bridge.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Replace `·/*·best-effort·*/` with `⏎······/*·best-effort·*/⏎···`

Check failure on line 120 in app/bridge.ts

View workflow job for this annotation

GitHub Actions / build (macos-latest)

Replace `·/*·best-effort·*/` with `⏎······/*·best-effort·*/⏎···`
startHeartbeat();
});

Expand Down Expand Up @@ -360,11 +368,11 @@

case 'SaveLayoutState': {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
const windows: any[] = Array.from((app as any).getWindows?.() || []);

Check failure on line 371 in app/bridge.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Unsafe argument of type `any` assigned to a parameter of type `Iterable<any> | ArrayLike<any>`

Check failure on line 371 in app/bridge.ts

View workflow job for this annotation

GitHub Actions / build (macos-latest)

Unsafe argument of type `any` assigned to a parameter of type `Iterable<any> | ArrayLike<any>`
if (windows.length > 0) {
for (const w of windows) {
if (w?.rpc) {
w.rpc.emit('get-layout-state-req', undefined);

Check failure on line 375 in app/bridge.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Unsafe call of an `any` typed value

Check failure on line 375 in app/bridge.ts

View workflow job for this annotation

GitHub Actions / build (macos-latest)

Unsafe call of an `any` typed value
}
}
sendResult(seq, 'ok');
Expand Down Expand Up @@ -398,12 +406,21 @@
shell: p.config?.shell as string | undefined,
isDefault: p.name === (getConfig() as any).defaultProfile
}));
// Per-window OS pixel size so the agent can answer "how big is the window"
// and resize relative to it (terminal_set_window_size).
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
const winList: BrowserWindow[] = Array.from((app as any).getWindows?.() || []);

Check failure on line 412 in app/bridge.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Unsafe argument of type `any` assigned to a parameter of type `Iterable<BrowserWindow> | ArrayLike<BrowserWindow>`

Check failure on line 412 in app/bridge.ts

View workflow job for this annotation

GitHub Actions / build (macos-latest)

Unsafe argument of type `any` assigned to a parameter of type `Iterable<BrowserWindow> | ArrayLike<BrowserWindow>`
const windowSizes = winList.map((w) => {
const b = w.getBounds();
return {id: w.id, width: b.width, height: b.height, x: b.x, y: b.y, focused: w.isFocused()};
});
sendResult(
seq,
JSON.stringify({
panes,
platform: process.platform,
profiles
profiles,
windowSizes
})
);
break;
Expand Down Expand Up @@ -504,8 +521,8 @@
targetPane: msg.targetPane as string,
purpose: (msg.purpose as string) || ''
};
for (const w of (app as any).getWindows?.() || []) {

Check failure on line 524 in app/bridge.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Unsafe call of an `any` typed value

Check failure on line 524 in app/bridge.ts

View workflow job for this annotation

GitHub Actions / build (macos-latest)

Unsafe call of an `any` typed value
if (w?.rpc) w.rpc.emit('permission request', payload);

Check failure on line 525 in app/bridge.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Unsafe call of an `any` typed value

Check failure on line 525 in app/bridge.ts

View workflow job for this annotation

GitHub Actions / build (macos-latest)

Unsafe call of an `any` typed value
}
break;
}
Expand All @@ -516,8 +533,8 @@
// the bar — but never move the human's view (focus-never-steal). The
// renderer's markTabBell keys off the paneId to find the owning tab.
const uid = msg.uid as string;
for (const w of (app as any).getWindows?.() || []) {

Check failure on line 536 in app/bridge.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Unsafe call of an `any` typed value

Check failure on line 536 in app/bridge.ts

View workflow job for this annotation

GitHub Actions / build (macos-latest)

Unsafe call of an `any` typed value
if (w?.rpc) w.rpc.emit('tab bell', {uid});

Check failure on line 537 in app/bridge.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Unsafe call of an `any` typed value

Check failure on line 537 in app/bridge.ts

View workflow job for this annotation

GitHub Actions / build (macos-latest)

Unsafe call of an `any` typed value
}
break;
}
Expand Down Expand Up @@ -1159,6 +1176,15 @@
send({type: 'WindowFocus', windowId});
}

/** Report a window's OS pixel bounds to the sidecar so terminal_status can
* answer "how big is the window" and resize relative to it. Sent on window
* create + resize/move. */
export function updateWindowBounds(win: BrowserWindow) {
if (!win || win.isDestroyed()) return;
const b = win.getBounds();
send({type: 'WindowBounds', windowId: win.id, width: b.width, height: b.height, x: b.x, y: b.y});
}

/** Update the description for a session. */
export function updateSessionDescription(uid: string, description: string) {
let tracked = trackedSessions.get(uid);
Expand Down
10 changes: 8 additions & 2 deletions app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -469,10 +469,16 @@ async function installDevExtensions(isDev_: boolean) {
);
}

// Single instance lock — prevent duplicate tray icons and sidecar spawns
// Single instance lock — prevent duplicate tray icons and sidecar spawns.
// HARD exit for the loser: app.quit() is async and the losing instance kept
// EXECUTING its init — including spawnSidecar's taskkill of the WINNER's
// sidecar — before actually quitting. That mutual sidecar-squash is what
// made duplicate launches flash/restart/rotate panes. app.exit() is
// synchronous and runs nothing further.
const gotLock = app.requestSingleInstanceLock();
if (!gotLock) {
app.quit();
app.exit(0);
process.exit(0); // belt-and-suspenders: nothing below may run in the loser
}
app.on('second-instance', () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
Expand Down
2 changes: 1 addition & 1 deletion app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "hyperia",
"productName": "Hyperia",
"description": "A modern agentic terminal.",
"version": "0.15.11",
"version": "0.15.15",
"license": "BSD-2-Clause",
"author": {
"name": "Deep Blue Dynamics",
Expand Down
19 changes: 12 additions & 7 deletions app/sticky.ts
Original file line number Diff line number Diff line change
Expand Up @@ -717,13 +717,12 @@ function createStickyNote(
savedNote.source.path = translateContainerPath(savedNote.source.path);
}

// Resolve default size (explicit > persisted manual size > default). A brand-new
// sticky opens at 25% of the default size (quick small notes); explicit sizes
// and restored notes keep their exact dimensions.
const NEW_STICKY_SCALE = 0.25;
// Resolve default size (explicit > persisted manual size > default). New
// stickys open at the user's default size — the old 25% "quick note" scale
// made them ridiculously small (~117×104) and unusable.
const defaultSize = getStickyDefaultSize();
let width = options.width || savedNote?.width || Math.round(defaultSize.width * NEW_STICKY_SCALE);
let height = options.height || savedNote?.height || Math.round(defaultSize.height * NEW_STICKY_SCALE);
let width = options.width || savedNote?.width || defaultSize.width;
let height = options.height || savedNote?.height || defaultSize.height;

// Candidate position: explicit > persisted > centered on the cursor.
const hasPlacedPos = options.x != null || options.y != null || savedNote?.x != null || savedNote?.y != null;
Expand All @@ -738,6 +737,10 @@ function createStickyNote(
const wa = targetDisplay.workArea;
width = Math.min(width, wa.width);
height = Math.min(height, wa.height);
// Hard floor: never open a sticky too small to read/use, whatever the
// source (corrupt persisted geometry, tiny explicit sizes).
width = Math.max(width, 220);
height = Math.max(height, 170);
x = Math.max(wa.x, Math.min(x, wa.x + wa.width - width));
y = Math.max(wa.y, Math.min(y, wa.y + wa.height - height));

Expand Down Expand Up @@ -862,8 +865,10 @@ function createStickyNote(
const bounds = win.getBounds();
const note = getNote(noteId);
if (note) {
// Persist THIS note's geometry only. Resizing a note must NOT rewrite
// the global default — one huge note poisoned every future sticky
// ("how many times do I have to ask for a normal sized sticky?").
upsertNote({...note, x: bounds.x, y: bounds.y, width: bounds.width, height: bounds.height});
saveStickyDefaultSize(bounds.width, bounds.height);
}
};
win.on('moved', saveGeom);
Expand Down
1 change: 1 addition & 0 deletions app/ui/contextmenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const contextMenuTemplate = (
menu.push(separator);
menu.push({label: 'New Tab', accelerator: commandKeys['tab:new'], click: cmd('tab:new')});
menu.push({label: 'New Window', click: () => createWindow()});
menu.push({label: 'Hyperia Agent', click: cmd('pane:openShellPane')});

menu.push(separator);
menu.push({label: 'New Stickys', click: () => ipcMain.emit('new-sticky', {})});
Expand Down
Loading
Loading