Skip to content

fix(t27os): CTL verbs match whole words; escaped keys and expect; agent check table - #11015

Merged
gHashTag merged 43 commits into
masterfrom
claude/t27os-ctl-verbs
Oct 23, 2026
Merged

gHashTag merged 43 commits into
masterfrom
claude/t27os-ctl-verbs

Conversation

@gHashTag

Copy link
Copy Markdown
Collaborator

What

Follow-up to the T27 OS PTY server (epic #10782, stacked on feat/t27os-petal-menu). The real agent check on the Mac found that the T27OS_CTL client could not be used to drive the agents. This PR fixes that in .t27 only (no Rust touched).

  • ctl_verb() matches the whole word. Before, the client dispatched on the first letter, so keys ran kill and took down six panes during the check. Unknown words print a usage line.
  • keys (alias write) decodes \e \r \n \t \\ \xHH, so arrow keys and dialogs can be answered. T27OS_RAW=1 skips the final CR.
  • New expect verb: polls a pane every 500 ms, strips ANSI, looks for T27OS_EXPECT for up to T27OS_WAIT seconds (default 30). Exit 0 on found, 1 on timeout.

Verified

  • Module tests for specs/t27os/pty_logic.t27 on the Railway lab: 17 passed, 0 failed (4 new: whole-word verbs, escape decoding, ANSI stripping, substring search).
  • Negative control: making ctl_verb("keys") return CTL_KILL on a lab copy fails ctl_verbs_match_the_whole_word (16 passed, 1 failed).
  • pty_server.t27 builds to an arm64 Mach-O object with a newer lab t27b.

Not verified

  • The new keys / expect verbs were not run live against a server on the Mac: no link step was available for the freshly built object. Only the pure logic is covered by module tests.
  • Resume ids are still not recorded for Gemini, OpenCode, Cursor and Copilot (finding 3 below); that is a separate change.

Agent check on the Mac, 2026-10-19 (epic #10782, part C)

Method: an isolated PTY server (copy of the binary, separate HOME with symlinked agent configs), so the live server and its panes were never touched. Agents 0-8 spawned, driven, then kill -9 of the server and a restart. No key value was read or printed. A missing CLI is reported, not installed.

# Agent Installed Starts Authenticates Answers 1-line prompt Resumes after kill -9
0 Claude Code yes yes (trust dialog) not reached: trust dialog needs an arrow key not reached yes: claude --resume <sid>
1 Codex yes exits at once NO: missing OpenAI API key no listed, STOPPED
2 Gemini yes yes yes (reached "Thinking") no reply seen in 90 s relaunched fresh, no resume flag
3 OpenCode yes yes (splash) unknown no reply seen relaunched fresh
4 Queen NO (exec: queen: not found) no n/a n/a STOPPED
5 Crush yes yes (init dialog) unknown not reached relaunched fresh
6 Cursor yes yes NO: asks to log in no relaunched fresh
7 Aider yes yes NO: no LLM keys no yes: aider --restore-chat-history
8 Copilot yes yes (trust dialog) prompt box reached no reply seen relaunched fresh

Findings: (1) keys ran kill -- fixed here. (2) the CTL could not send arrow keys -- fixed here. (3) only Claude and Aider resume; no session id is recorded for Gemini, OpenCode, Cursor, Copilot -- open. (4) read-back was raw ANSI so no answer could be confirmed -- expect strips ANSI, but a rerun with it is still owed. (5) only Queen is missing.

Refs #10782

🤖 Generated with Claude Code

gHashTag and others added 30 commits October 19, 2026 17:05
… menu fade in .t27 (Refs #10782)

specs/t27f/petal_glass.t27 (module T27fPetalGlass) draws the T27 OS home as trios
does: blackGlass tint 0.30, darkWash 0.42 gradient, three ambient blooms at 0.055,
0.5 px white 0.14 border; petals, fills, outline, hit test and state come from
petal_menu.t27 by use, nothing copied. LogoAnimation is ported exactly (800-unit
fly-in, phase 1 at 4.5/s, spring k 28 damping 0.86); the card menu fades in with
g_wave_transition (10 frames). main prints 270 RGB24 frames of 960x540 for ffmpeg.

Lab: 6/6 tests, 11498 runtime asserts (the anti-aliased distance agrees with the
ray-cast hit test); rendered to a 9 s mp4 in 29 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…0782)

specs/t27os/glass_window.t27 is the T27 OS home as a real AppKit window:
NSVisualEffectView frosted glass under a black 0.30 tint, the 27 petals from
petal_menu.t27 with hover and the 800-unit fly-in, and the widget mode -- a
tap opens the 3x3 card panel, the logo fades out, cards cascade in and light
under the mouse, Esc / back control / tap outside go home. Full screen via the
green button or F, scene aspect kept. Every system call is a t27b @extern
import (#8946); t27b build --macho + ld -framework AppKit gives the binary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e scene (Refs #10782)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- top bar right of the traffic lights: logo icon (home), sidebar toggle
  (also the S key), back chevron; the back control no longer sits under
  the close/minimise/zoom buttons
- sidebar mode: the logo shrinks into a 200 pt left sidebar and stays live
  (hover and tap switch the open petal), the menu panel fills the rest
- realm header line is clipped to the panel's rounded top edge
- Dock icon: the logo white on a black squircle, drawn at launch and set
  with setApplicationIconImage: -- no foreign .icns file
- tests for the top-bar layout, the sidebar geometry and the icon centring

Refs #10782

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hat workspace, menu shortcuts

Rewrites specs/t27os/glass_window.t27 to the trios layout read from
TriosTabView / QueenUI MainView:

- 34 pt title bar: white template logo + TRINITY (home), fullscreen control,
  white 0.14 divider; text through CoreText lines coloured by the context.
- home: the petal_menu-style logo at 0.63 of the area, trios tooltip pill
  (name + Cmd/Option/Ctrl+N) 30 pt above the hovered petal.
- screen: 0.25 s opacity crossfade; 32 pt nav header with green "< TRINITY",
  centred world name and the refresh circle; 272 pt sidebar with New task and
  the queen card; 44 pt content header with the sidebar toggle; 3x3 cards in a
  readable column of at most 900 pt.
- keys as QueenMenuShortcutPolicy: Cmd/Option/Control+1..9 open petals 0..26,
  Escape and Cmd+0 go home, Cmd+Shift+S collapses the sidebar.

Drops the old panel, scrim and logo-to-sidebar morph. Silicon check on the
Mac: home, Escape crossfade, Cmd+5 -> REVIEW, Cmd+Shift+S collapse.

Refs #10782

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…logo 20% smaller in full screen

- The title-bar logo drops the clear-blend seams (read as a black outline)
  and is drawn in petal_menu's own style: idle fill, 0.5 pt white outline,
  14 pt tall.
- Frames render into a 4x bitmap (3840 x 2160) so full screen stays sharp.
- In full screen (NSWindowStyleMaskFullScreen) the home logo is drawn at
  0.8 of its windowed size; the hit test follows the same factor. New test
  covers the size, centring and mouse mapping.

Refs #10782

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The glass window now does what trios WindowManager + ChatWorkspaceLayout do:

- a sidebar.right control left of fullscreen docks the window as the 400 pt
  panel on the right edge, full visible height, animated; again = restore;
- below 760 pt the workspace is compact: no sidebar, an OPEN DASHBOARD bar
  over the cards; tapping it forces the dashboard, whose header gains the
  Close Dashboard control right of the toggle;
- the narrow scene renders point for point into its own bitmap, cropped to
  the view; the home logo fits the panel width;
- the 3 x 3 slots keep their wide-window shape instead of stretching down
  the narrow column;
- clicks hit-test at the event's locationInWindow, not the frame-start mouse.

New test: the_window_folds_into_the_trios_compact_panel_below_760.
Built with t27b on the lab, linked and checked on the Mac.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Refs #10782)

Full screen draws at native resolution on black glass (tint 0.86) instead of
scaling the 960x540 stage; nav, sidebar, header and cards use 0.5 pt hairlines,
muted system type and a 4 pt radius. New test
full_screen_draws_point_for_point_on_black_glass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The logo draws as the Trinity S3AI mark: white petals cut by black lines, the
centre left black; hover and open petals keep their realm colour. The nav
header drops its duplicate TRINITY label; the back chevron sits on the sidebar
text column. New test logo_is_the_brand_mark_and_title_says_trinity_once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ps (Refs #10782)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The navigation header is gone: the back chevron, the logo and TRINITY,
the refresh arc, the dock and fullscreen controls share the title bar,
and the workspace starts right under it. The world name is said once,
in the content header. Cards are honeycomb cells; the brand mark has a
large and a small variant (finer cut, centre hole kept). In full screen
the logo sits on the chevron line and the glass tint drops to 0.55.

Refs #10782

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Full screen has no desktop behind the window for the behind-window blur,
so the material went flat grey. The window now draws its own frost
there: a black wash and three soft white glows (stacked discs). The
screen is black and white only: the queen crown is white.

New specs/t27os/layout.t27 (module T27osLayout) computes row placement:
row_next, centre_top, baseline_for/cap_mid, snap, same_line. The title
bar uses it: the back chevron and the logo share one icon height and
centre line, the gaps chevron-logo-TRINITY are equal, and the text
baseline puts the capitals on the same line. Thinner petal cuts on both
logo sizes.

Closes #10831
Refs #10782

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The frame is filled opaque black (BACK_BLACK 1.0) before anything else, so
neither the wallpaper behind the glass nor the full-screen glows show. The
glow frost from #10831 is removed; the owner asked for #000000.

Refs #10782

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…paque in full screen

The background fill stays pure black (R = G = B = 0) but is BACK_GLASS 0.60 in
a window, so the behind-window blur shows through as frost with no grey or
coloured wash; full screen has nothing behind to blur and stays opaque.

Refs #10782

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… restore plan

specs/t27os/agents.t27 (new): the nine agents of the comb (Claude Code, Codex,
Gemini, OpenCode, Queen in the centre, Crush, Cursor, Aider, Copilot), their
launch commands, how each resumes (by id, by history, or unknown -> relaunch),
the session state machine after herdr.dev (working, blocked, idle, done, plus
stopped) and what a restart does with each saved session. Tests cover the
transitions and the restore decisions.

glass_window.t27: every cell draws its agent mark (vector, white only) and its
name, scaled with the cell and left out where it would be unreadable.

Host side not yet written: the terminal owner (spawn + pty), the on-disk
session store and the state reader. Tests are unverified here: t27b test runs
on arm64 only.

Refs #10782

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…task button

The logo + TRINITY sit in the middle of the title bar as one group, the
logo drawn 1.2x the row height; the back chevron keeps the left edge with
a 40 pt hit box. The New task row is gone from the sidebar: tapping a
honeycomb cell opens a task with that agent, lights the cell and lists it
under AGENTS. Spec asserts updated; the t27b runner is arm64-only, so they
are unverified here.

Refs #10782

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ew binary (Refs #10782)

specs/t27os/hot_reload.t27 decides when to swap: the window polls its own
executable's modification date twice a second, waits for two quiet checks
(ld and then codesign each write the file), saves the open screen, task,
sidebar and dashboard flags to NSUserDefaults under t27os.hot, and execs
itself. The new process restores that state with no fade-in. Exec rather
than a reloaded dylib because t27b has no call through a function pointer.

Checked by hand: screen open with Gemini selected, relinked and re-signed
with no relaunch, the window came back on the same screen with Gemini
still selected. The spec's tests are not run here: the t27b runner is
arm64-only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…10782)

New specs/t27os/windows.t27 holds the window model: one session per agent,
packed as one base-4 digit per agent in a u64 (0 = none, d = space d-1),
state-to-dot-shape mapping (black and white, FR-006), and sidebar geometry
(group_top / row_top / row_at).

glass_window.t27 draws the three groups in the expanded sidebar, opens a
session in the selected space on a honeycomb cell tap, selects the Queen,
a space or an agent row on click, and saves space + where across hot
reloads.

Seed data: the space names and branches are fixed until the host side reads
git worktrees; every session shows "stopped" because no agent process runs
yet. No scrolling: more than 7 sessions in one space overflow the window.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ript three (Refs #10782)

The header label next to the logo and the NSWindow title now show
"TRINITY S" U+00B3 "AI". Source stays ASCII (L3): t27b string literals do
not decode \x escapes (a "\xC2\xB3" literal reached the object file as
eight ASCII characters), so brand() writes the two UTF-8 bytes C2 B3 into
a buffer that CFStringCreateWithBytes reads. A test pins the bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The line width was set once before the comb loop, and draw_agent_mark
changes it, so cell 0 (Claude Code) was stroked at HAIR and every later
cell at the previous agent mark's 0.12*s or 0.18*s width. The width is now
set before each cell's stroke; cell_line and cell_edge hold the rule, and a
test checks that every cell gets the same width and the same edge alpha for
plain, hover and selected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a session (Refs #10782)

- windows.t27: the one-session-per-agent `where` word becomes an ordered
  list of up to 7 sessions packed one byte each (agent, space); add,
  remove (compacting), per-space rows, chat numbering, selection fix-up
  after a close; two new tests.
- glass_window.t27: "+" at the right end of the CHAT header adds a chat
  of the selected agent (Claude Code by default) in the current space;
  a second chat of the same agent reads "Claude Code 2"; a hovered
  AGENTS row trades its dot for an x that closes that chat. Sessions and
  the selected one survive hot reload (t27os.sessions, t27os.cur).
- Checked on the running app: three chats opened with +, the middle one
  closed, the rest renumbered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tions, GOD MODE (Refs #10782)

specs/t27os/queen.t27 (new): the Queen's model of what the user is doing.
- Every control pressed is packed into a u64 event (view, control,
  argument, unix second) and kept in a ring of the last eight.
- From the sidebar she derives one suggestion: open a chat in an empty
  space, or select the first chat when none is selected.
- GOD MODE runs that suggestion herself, only while her panel is open and
  after the user and she have both been still for 3 s.
- Geometry and hit tests for the panel; seven tests.

specs/t27os/glass_window.t27:
- Cmd+J or the QUEEN row opens her 300 pt panel over any view: LOOKING AT
  (view, space, chat), SUGGESTION with Confirm / Dismiss, RECENT (newest
  first; filled dot = her action, ring = the user's, age in words).
- GOD MODE box: empty = off, filled + bold border = on. Cmd+J, Escape,
  the box, or closing the panel stops it.
- Panel open, GOD MODE and the ring persist across hot reload.

Checked on the running app: the QUEEN row opened the panel, Confirm
selected the chat, GOD MODE opened a Claude Code chat in two empty spaces
by itself and logged both, the box stopped it. Cmd+J and Escape were not
exercised (keys cannot reach a background window from the test harness).
Spec tests were not run: the t27b test runner is arm64-only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rwise (Refs #10782)

- queen.t27 records the one exception to FR-006 next to the black-and-white
  rule: a green edge glow (GLOW_R/G/B, defined only there) while GOD MODE is
  on. glow_on(god), glow_level (fade over about 1/3 s) and glow_alpha
  (12 strips over 34 pt, brightest at the edge), with a test.
- glass_window.t27 draws the glow on top of every view and fades it each
  frame. The backdrop is now opaque black in a window too: the 0.60 glass
  let the blurred wallpaper read as a warm glow along the edges.

Checked on the running app: GOD MODE on shows the green edges, off leaves
them pure black. Spec tests not run (the t27b runner is arm64-only).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- windows.t27: open-space bitmask (SPACES_SEED, new_space/add_space/
  remove_space, drop_sessions_in, after_drop); session byte now 1+k*8+s;
  REPO_LINK_TEXT/REPO_URL/FOOT_H foot constants; tests for both.
- queen.t27: C_NEW_SPACE, C_CLOSE_SPACE, C_REPO_LINK controls.
- glass_window.t27: "+" on the SPACES label, hover x on space rows,
  black foot strip with the repo link (opens github.com/gHashTag/t27);
  spaces persist under t27os.spaces, sessions under t27os.sessions2;
  layout test corrected to five chat rows above the foot.

Refs #10782

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- queen.t27: FLAME_CORE/MID/BASE stops (bright cyan cone at the edge,
  vivid blue body, deep violet-blue base), 30 bands over 44 pt with a
  cubic falloff, flame_wave/flicker (3 s breathing plus tongues that
  travel along the bands, stronger at the outer tips), core_alpha for
  the blurred cone hairline; test rewritten for the flame.
- glass_window.t27: draw_edge_glow colours each strip by flame_rgb and
  flicker on the frame count, then draws the cone hairline with a
  22 pt blue CGContextSetShadowWithColor halo.

Refs #10782

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
doctor.t27 holds every decision: walk roots and depth, skipped names,
agent roots, the per-repo checks, the flags (duplicate clone, agent copy,
made several times, worktree, dirty, unpushed), keep reasons, row groups,
the two-step remove/confirm click, auto-bind ranks and panel layout,
with tests. glass_window.t27 does only the host plumbing: the paced
directory walk and rescan, .git vanish check, NSTask jobs for git/du,
the defaults-persisted slots and hidden list, the DOCTOR foot link and
panel, and the move to the Trash after a per-row confirm. A repo with
uncommitted changes or unpushed commits is never offered for removal.

Desktop, Documents, Downloads, Dropbox and OrbStack are skipped: a stat
on the iCloud-backed Desktop blocked the main thread for minutes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ty_client lands)

Clicking a Claude Code (AGENT 0) row under AGENTS opens a terminal pane in the
chat's content area: a cell grid of u32 codepoints from specs/t27os/vt.t27,
drawn monospace, black and white, with a white block cursor that stays solid
while typing and blinks when idle.

- term_pane.t27: grid sizing from the content area, cell geometry, cursor
  blink, key-to-bytes (Return/Tab/Delete/Esc, arrows, Home/End, PgUp/PgDn,
  forward delete, ctrl letters, UTF-8), and the offline echo. 6 tests, 0 vacuous.
- vt.t27: copied verbatim from the pty-server worktree (6066dfb) so this
  branch builds alone; identical add/add merges cleanly.
- glass_window.t27: draw_term, key forwarding (every non-Cmd key goes to the
  pane), per-frame open/resize/read, and stand-ins term_connect/term_spawn/
  term_keys/term_read/term_resize for specs/t27os/pty_client.t27. Until that
  lands they answer "not connected" and the pane echoes keys into the grid.
  cwd is the space's bound folder, home when unbound.

Refs #10782

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner request: the sidebar footer link text changes from I LOVE OPEN SOURCE
to OPEN SOURCE T27. Same repository link, same style. The glass_window.t27
header line that describes the foot is updated, and the formerly vacuous
foot test in windows.t27 now checks the words at runtime.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e PTY server (Refs #10782)

Pure logic with run-time asserted tests: the four pane states from hooks and
output quietness, the session manifest line and restore plan, the socket frame
format and scrollback ring, and a minimal VT cell grid.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… the UI and restore after a server kill (Refs #10782)

pty_server owns one PTY per pane, serves pty_proto frames on ~/.t27os/pty.sock,
keeps a 64 KiB ring per pane, writes a record per pane under ~/.t27os/sessions
and restores by the RESUME_* rules on start (a resume that dies at once relaunches
fresh). Claude hooks drive pane_state; T27OS_HOOK / T27OS_CTL / T27OS_INSTALL modes
cover the hook, a tiny client and the launchd plist. Needs @extern (t27b host
boundary, 865b4ee). Decisions live in pty_logic.t27 with run-time tests.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@gHashTag
gHashTag changed the base branch from feat/t27os-petal-menu to master October 22, 2026 19:58
@gHashTag
gHashTag enabled auto-merge (squash) October 22, 2026 19:59
@tri-claw

tri-claw Bot commented Oct 22, 2026

Copy link
Copy Markdown

Summary by t27-bees

Checked by the t27 compiler

t27c parse, parse-complete and typecheck on each changed .t27 file at 530924b. Nothing was generated, built or run. A pass means the file is well formed, not that it is right.

  • specs/t27os/pty_logic.t27: parses and typechecks.
  • specs/t27os/pty_server.t27: parses and typechecks.

Read by the model

The PR fixes the T27OS_CTL client so verbs match whole words (preventing keys from triggering kill), adds escape decoding for keys/write (\e, \r, \n, \t, \\, \xHH), and introduces an expect verb that polls a pane, strips ANSI, and searches for a substring. The changes are confined to .t27 logic and its server wiring.

Worth a look

  • specs/t27os/pty_logic.t27:2450 keys_decode writes to out without checking its capacity. The caller in pty_server.t27:1334 provides a fixed 256-byte buffer at O_TMP2; if T27OS_KEYS decodes to more than 256 bytes (e.g., many \xHH sequences that expand to one byte each, so input would need >256 chars), it overflows.
  • specs/t27os/pty_logic.t27:2495 strip_ansi OSC handling: when the sequence lacks a BEL (7), it assumes ESC \ (27 92) and unconditionally skips two bytes (i = i + 2). A truncated OSC (e.g., ESC ]0;title at end of buffer) causes the parser to jump past the buffer end or swallow subsequent text.
  • specs/t27os/pty_server.t27:1355 CTL_EXPECT calls client_read(fd, pane, 0, ...) with offset 0 on every poll. If the server returns cumulative output, has_text will match content from earlier reads, causing immediate "found" even if the expected string appeared long before the wait started.
  • specs/t27os/pty_server.t27:1363 sys_poll(mem[O_MISC+340..], 0, 500) passes zero file descriptors. This sleeps 500 ms but does not wait on the pane's fd; data arriving during the sleep is only noticed after the full interval, adding up to 500 ms latency per loop.
  • specs/t27os/pty_server.t27:1295 CTL_LIST output format changed: it now prints raw state and flags bytes (out[at+2], out[at+3]) instead of the decoded state name, light name, low-nibble flags, and key number. Existing scripts parsing list output will break.

Read: 3 files, +261 -20, the whole diff. Head 530924b. Model: openai-compatible/nvidia/nemotron-3-ultra-550b-a55b. Commands: @t27-bees review | summary | help | pause | resume. Free for every repository.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-auto-merge Opt out of the issue gate's default squash auto-merge (issue-gate.yml)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants