Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
30ef140
feat(frontend): rebuild Electron desktop UI as a React + Vite renderer
yyovil Jun 10, 2026
9418402
feat(terminal): port yyork's terminal rendering architecture
yyovil Jun 10, 2026
2e0446c
fix(spawn): don't send base branch; surface real API errors
yyovil Jun 10, 2026
26deb0c
feat(terminal): Nerd Font glyph support via --font-mono (yyork pattern)
yyovil Jun 10, 2026
2bdb0d3
chore(frontend): set up shadcn/ui foundation
yyovil Jun 10, 2026
361e580
refactor(renderer): persistent _shell layout + per-route pages (proje…
yyovil Jun 10, 2026
39daa00
feat(board): attention-zone kanban home
yyovil Jun 10, 2026
d752e1a
feat(settings): project settings form (Phase 2)
yyovil Jun 10, 2026
3f71677
feat(session): PR inspector in the session rail (Phase 2)
yyovil Jun 10, 2026
122607e
feat(prs): pull-requests board (Phase 3)
yyovil Jun 10, 2026
8852a93
feat(reviews): code-review API + dashboard (Phase 4)
yyovil Jun 10, 2026
bd4ab90
chore(renderer): Phase 5 polish — route prefetch + restored spawn cov…
yyovil Jun 10, 2026
4490dfd
feat(board): match agent-orchestrator's board verbatim
yyovil Jun 10, 2026
aec18e7
feat(theme): clone agent-orchestrator's dark palette globally
yyovil Jun 10, 2026
6864e45
feat(renderer): clone agent-orchestrator ProjectSidebar verbatim
yyovil Jun 10, 2026
90addb4
feat(renderer): clone agent-orchestrator session topbar
yyovil Jun 10, 2026
1cfbfe8
feat(renderer): unify board/review/PR/settings chrome verbatim
yyovil Jun 10, 2026
82ae9b1
docs: record agent-orchestrator-verbatim design direction
yyovil Jun 10, 2026
25b8d58
feat(renderer): clone agent-orchestrator shell and inspector
yyovil Jun 11, 2026
7663803
chore: format with prettier [skip ci]
github-actions[bot] Jun 11, 2026
5921061
Merge remote-tracking branch 'upstream/main' into split/frontend-ui-c…
yyovil Jun 11, 2026
243cc65
Merge remote-tracking branch 'origin/split/frontend-ui-clone' into sp…
yyovil Jun 11, 2026
1265e14
fix: repair UI PR CI drift
yyovil Jun 11, 2026
ce36847
Merge remote-tracking branch 'upstream/main' into split/frontend-ui-c…
yyovil Jun 11, 2026
73c0e2a
refactor(terminal): per-client zellij attach replaces shared PTY + re…
yyovil Jun 12, 2026
e311361
fix(renderer): re-assert settled terminal resize; align docs with per…
yyovil Jun 12, 2026
cfea1c0
feat(renderer): full-width shell topbar; retire per-view topbars and …
yyovil Jun 12, 2026
f04a880
docs: document Electron app dev quick start
yyovil Jun 12, 2026
e3b5062
chore(fork): ignore local agent session dirs
yyovil Jun 12, 2026
afb7314
Merge remote-tracking branch 'upstream/main' into split/frontend-ui-c…
yyovil Jun 12, 2026
5706c5a
chore: format with prettier [skip ci]
github-actions[bot] Jun 12, 2026
06e9bf4
feat: align backend session lifecycle with workspace runtime updates
yyovil Jun 13, 2026
70409c1
refactor: replace spawn modal with shell-native worker controls
yyovil Jun 13, 2026
fe23509
chore: add shared daemon launch helper and docs updates
yyovil Jun 13, 2026
d7e5ab0
Merge remote-tracking branch 'upstream/main' into split/frontend-ui-c…
yyovil Jun 13, 2026
4b95ee9
chore: format with prettier [skip ci]
github-actions[bot] Jun 13, 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
8 changes: 7 additions & 1 deletion backend/internal/adapters/agent/claudecode/claudecode.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,9 +234,15 @@ func (p *Plugin) GetRestoreCommand(ctx context.Context, cfg ports.RestoreConfig)
if err != nil {
return nil, false, err
}
cmd = make([]string, 0, 5)
cmd = make([]string, 0, 7)
cmd = append(cmd, binary)
appendPermissionFlags(&cmd, cfg.Permissions)
if cfg.SystemPrompt != "" {
// --resume rebuilds the system prompt from the current flags (it is
// not stored in the transcript), so standing instructions must be
// re-appended or a restored orchestrator loses its role.
cmd = append(cmd, "--append-system-prompt", cfg.SystemPrompt)
}
cmd = append(cmd, "--resume", sessionID)
return cmd, true, nil
}
Expand Down
20 changes: 20 additions & 0 deletions backend/internal/adapters/agent/claudecode/claudecode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,26 @@ func TestGetRestoreCommandReadsAgentSessionID(t *testing.T) {
}
}

func TestGetRestoreCommandReappendsSystemPrompt(t *testing.T) {
// --resume rebuilds the system prompt from flags, so standing instructions
// (e.g. the orchestrator role) must be re-appended on restore.
cmd, ok, err := (&Plugin{resolvedBinary: "claude"}).GetRestoreCommand(context.Background(), ports.RestoreConfig{
Permissions: ports.PermissionModeBypassPermissions,
SystemPrompt: "You are an orchestrator.",
Session: ports.SessionRef{
ID: "sess-r",
Metadata: map[string]string{ports.MetadataKeyAgentSessionID: "claude-native-1"},
},
})
if err != nil || !ok {
t.Fatalf("restore = (ok=%v, err=%v), want ok", ok, err)
}
want := []string{"claude", "--permission-mode", "bypassPermissions", "--append-system-prompt", "You are an orchestrator.", "--resume", "claude-native-1"}
if !reflect.DeepEqual(cmd, want) {
t.Fatalf("restore cmd\nwant: %#v\n got: %#v", want, cmd)
}
}

func TestGetRestoreCommandFallsBackToDerivedUUID(t *testing.T) {
// No agentSessionId captured (pre-hook session) → derive deterministically
// from the AO session id, the explicit fallback.
Expand Down
8 changes: 6 additions & 2 deletions backend/internal/adapters/runtime/zellij/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@ func deleteSessionArgs(id string) []string {
}

func attachArgs(id string) []string {
return []string{"attach", id}
return []string{
"attach", id,
"options",
"--pane-frames", "false",
}
}

func handleIDValue(sessionID, paneID string) string {
Expand Down Expand Up @@ -96,7 +100,7 @@ func shellLaunchSpecFor(shellPath string) shellLaunchSpec {
func layoutString(workspacePath, shellPath string, shellArgs []string, shellCommand string) string {
return "layout {\n" +
" cwd " + kdlQuote(workspacePath) + "\n" +
" pane command=" + kdlQuote(shellPath) + " name=" + kdlQuote(agentPaneName) + " {\n" +
" pane command=" + kdlQuote(shellPath) + " name=" + kdlQuote(agentPaneName) + " borderless=true {\n" +
" args " + kdlJoin(shellArgs) + " " + kdlQuote(shellCommand) + "\n" +
" }\n" +
"}\n"
Expand Down
100 changes: 82 additions & 18 deletions backend/internal/adapters/runtime/zellij/zellij.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ import (
)

const (
defaultTimeout = 5 * time.Second
minMajor = 0
minMinor = 44
minPatch = 3
defaultTimeout = 5 * time.Second
defaultZellijTerm = "xterm-256color"
defaultZellijColor = "truecolor"
minMajor = 0
minMinor = 44
minPatch = 3
)

var sessionIDPattern = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)
Expand Down Expand Up @@ -79,9 +81,7 @@ type execRunner struct{}

func (execRunner) Run(ctx context.Context, env []string, name string, args ...string) ([]byte, error) {
cmd := exec.CommandContext(ctx, name, args...)
if len(env) > 0 {
cmd.Env = append(os.Environ(), env...)
}
cmd.Env = zellijCommandEnv(os.Environ(), env)
return cmd.CombinedOutput()
}

Expand Down Expand Up @@ -134,6 +134,13 @@ func (r *Runtime) Create(ctx context.Context, cfg ports.RuntimeConfig) (ports.Ru
if err := r.ensureSupportedVersion(ctx); err != nil {
return ports.RuntimeHandle{}, err
}
// Zellij keeps exited sessions in a resurrection cache. A previous partial
// spawn can therefore make `attach --create-background` fail with "Session
// already exists" even though AO has no usable runtime handle. Clear any
// same-name runtime state before creating the new AO-owned session.
if err := r.Destroy(ctx, ports.RuntimeHandle{ID: id}); err != nil {
return ports.RuntimeHandle{}, err
}

layoutPath, err := r.writeLayout(cfg)
if err != nil {
Expand Down Expand Up @@ -243,9 +250,6 @@ func (r *Runtime) AttachCommand(handle ports.RuntimeHandle) ([]string, error) {
}
args := append([]string{}, r.baseArgs()...)
args = append(args, attachArgs(id)...)
if r.socketDir == "" {
return append([]string{r.binary}, args...), nil
}
return attachCommandWithEnv(r.binary, r.socketDir, args...), nil
}

Expand Down Expand Up @@ -326,29 +330,89 @@ func (r *Runtime) baseArgs() []string {
}

func (r *Runtime) env() []string {
env := zellijColorEnv(nil)
if r.socketDir == "" {
return nil
return env
}
return []string{"ZELLIJ_SOCKET_DIR=" + r.socketDir}
return append(env, "ZELLIJ_SOCKET_DIR="+r.socketDir)
}

func attachCommandWithEnv(binary, socketDir string, args ...string) []string {
if socketDir == "" {
return append([]string{binary}, args...)
env := zellijColorEnv(nil)
if socketDir != "" {
env = append(env, "ZELLIJ_SOCKET_DIR="+socketDir)
}
if runtime.GOOS == "windows" {
command := strings.Builder{}
command.WriteString("$env:ZELLIJ_SOCKET_DIR = ")
command.WriteString(psQuote(socketDir))
command.WriteString("; & ")
command.WriteString("Remove-Item Env:NO_COLOR -ErrorAction SilentlyContinue; ")
for _, pair := range env {
key, value, ok := strings.Cut(pair, "=")
if !ok {
continue
}
command.WriteString("$env:")
command.WriteString(key)
command.WriteString(" = ")
command.WriteString(psQuote(value))
command.WriteString("; ")
}
command.WriteString("& ")
command.WriteString(psQuote(binary))
for _, arg := range args {
command.WriteByte(' ')
command.WriteString(psQuote(arg))
}
return []string{"powershell.exe", "-NoLogo", "-NoProfile", "-Command", command.String()}
}
return append([]string{"env", "ZELLIJ_SOCKET_DIR=" + socketDir, binary}, args...)
argv := []string{"env", "-u", "NO_COLOR"}
argv = append(argv, env...)
argv = append(argv, binary)
return append(argv, args...)
}

func zellijCommandEnv(base, overrides []string) []string {
env := zellijColorEnv(append([]string(nil), base...))
for _, pair := range overrides {
env = upsertEnv(env, pair)
}
return env
}

func zellijColorEnv(env []string) []string {
if runtime.GOOS == "windows" {
return env
}
env = removeEnv(env, "NO_COLOR")
env = upsertEnv(env, "TERM="+defaultZellijTerm)
env = upsertEnv(env, "COLORTERM="+defaultZellijColor)
return env
}

func upsertEnv(env []string, pair string) []string {
key, _, ok := strings.Cut(pair, "=")
if !ok {
return env
}
prefix := key + "="
for i, current := range env {
if strings.HasPrefix(current, prefix) {
env[i] = pair
return env
}
}
return append(env, pair)
}

func removeEnv(env []string, key string) []string {
prefix := key + "="
out := env[:0]
for _, current := range env {
if strings.HasPrefix(current, prefix) {
continue
}
out = append(out, current)
}
return out
}

func zellijSessionName(id domain.SessionID) (string, error) {
Expand Down
Loading