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
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,11 @@ docs-website/.astro/
social-media-post.md
Work/
promo-video
packaging/arch/pkg/
packaging/arch/src/
packaging/arch/*.deb
packaging/arch/*.pkg.tar.*
tester/
test-workspace/
.commandcode/
examples/VPS_SETUP.md
30 changes: 29 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,24 @@ The self-hosted Hub is a separate `crewcode hub` process, not Electron renderer

The design system lives in `.design/crewcode-design-system/`. The canonical CSS tokens are in `src/renderer/src/styles/colors_and_type.css`.

Renderer components may use Tailwind v4 utilities through the utilities-only integration in `src/renderer/src/styles/tailwind.css`. Preflight must stay disabled so incremental conversions do not reset unrelated app surfaces. Use the `cc-*` semantic Tailwind colors, which map to the canonical live CSS tokens; see `docs/tailwind-renderer.md`.

The Prompt/Skills Studio phone list is an edge-to-edge surface, not a centered percentage-width card. Keep the `.pb` → `.pb-left` → `.pb-inner` container chain at `width: 100%`, `max-width: 100%`, and `min-width: 0`. Do not render the category-chip scroller on phones; retain only its compact management/favorite/layout toolbar. Phone cards must be non-shrinking children of the scrollable flex list, grow to fit their wrapped title and description, and contain overflow without line clamps. The phone detail editor must not offer or render Split mode: resolve a stored desktop Split state to Source, retain explicit Source/Preview choices, and let `.pd-source` fill the remaining body height. Actionable controls remain at least 36px and text inputs remain at the iOS-safe 16px.

The composer PromptPicker has separate Prompts and Skills tabs backed by the shared prompt library. Prompt selection inserts into the visible composer (using variable fill when required); Skill selection toggles only the resolved session's `enabledSkillIds`, remains open for multi-select, and never inserts the skill body or mutates a global enable flag. Keep enabled state visible and phone tabs/rows at least 44px/48px respectively.

On phones, Code Editor keeps the code canvas primary and opens its file tree as a dismissible right overlay. Git Sidebar must remain the same stateful surface on desktop and mobile, becoming an off-canvas panel with backdrop/close controls instead of being hidden or squeezing chat/editor content. Changes by turn is a full-screen mobile review: its catalogue stacks above the diff when open, while a targeted changed-file route keeps the catalogue closed. Keep all JS/CSS decisions aligned at `≤768px`; see `docs/mobile-responsive-pages.md`.

Git Workspace phone layout keeps the shared `useGitSidebar` state/actions, a compact two-column overview, changed files stacked above a bounded diff, and the remaining Git tools in a bounded scroll panel. Use the canonical `≤768px` breakpoint, ≥36px actionable controls, and 16px text inputs; do not restore fixed desktop-width columns or unbounded stacked panels.

The final work-log changed-file chips and Turn Changes drawer must use the same turn-change aggregation. A chip targets its exact turn/file in the drawer with the agent-summary/list sidebar closed; do not route it through the ordinary editor file-open action or rebuild a second, lossy file list.

Normalize multi-file provider output into one unified patch per file before rendering. Repeated edits to one file must merge their hunks under one canonical `diff --git` header so `PierreDiff` receives a single-file patch and does not drop to the raw fallback.

Git Sidebar `gs-changes-list` rows open the active worktree's diff in Code Editor's existing `PierreDiff` surface. Git Workspace and Git Sidebar share `useGitSidebar` comparison state and use the workspace-scoped Settings default branch as a read-only base; never checkout that branch implicitly, diff the primary workspace when a worktree is active, or expose staging actions for committed comparison-only rows.

Drawer thread rows may be dragged onto a Solo Chat `.chat-pane-row` or a terminal pane to join the window split group. Do not re-key the session or mount it into Workbench; same-tab splits use a viewport tab (`sessionOwnerTabId` + `pinnedSessionId`) and must not `ensureTab` the viewport id. Disable drag on phones. See `docs/workspace-session-split.md`.

**Hard rules:**

- Background: `#0f120f` (dark), never pure black
Expand All @@ -175,7 +193,17 @@ Three tsconfigs compose via project references:

## Current state

Read this file only when working on any of the features below and need the Current state of them `CrewCoder provider`, `ACP Grok Build`, `Sidebar Folder Creation`, `Crew Supervisor`, `Delegated Threads`,`Chat Archiving`, `Hide work Logs`, `Realtime Voice Orb`, `Notifcation Sound`, `Agent Messages`, `Agent Task Activity`, `Cusromization Panel`, `Queued Messages`, `Composer Execution Modes & reasoning`, `Claude SDK Global skills isolation`, `Provider Switch Handoff & Compact`, `Chat`, `Markdown Editor`, `Code Editor`, `Workbench Mode`, `Git Workspace/Sidebar`, [Current State](docs/current-state.md)
Read this file only when working on any of the features below and need the Current state of them `CrewCoder provider`, `ACP Grok Build`, `Sidebar Folder Creation`, `Crew Supervisor`, `Delegated Threads`,`Chat Archiving`, `Hide work Logs`, `Realtime Voice Orb`, `Notifcation Sound`, `Agent Messages`, `Agent Task Activity`, `Cusromization Panel`, `Queued Messages`, `Composer Execution Modes & reasoning`, `Claude SDK Global skills isolation`, `Provider Switch Handoff & Compact`, `Chat`, `Drawer session split`, `Markdown Editor`, `Code Editor`, `Workbench Mode`, `Git Workspace/Sidebar`, `Mobile-responsive Pages`, [Current State](docs/current-state.md)

Agent activity must not depend on prompt instructions or provider tool compliance. Every bridge-backed solo, crew-lane, or supervisor dispatch creates a dedicated CrewCode-owned `activity` transcript record for that turn; raw PTY agents are excluded because their terminal outcome is not observable. Advance it only from observed bridge events: `turn_start` begins work, tool categories may update its deterministic phase, and normal `turn_end` completes it. Prompt rejection, abort, stop, bridge error/closure, custody halt, or lost runtime becomes cancelled/interrupted, never success. Terminal activity is immutable, and a persisted running record from another app runtime projects as interrupted. Provider-native todo/plan/task snapshots may replace the generic row only while the CrewCode lifecycle is active; the CrewCode terminal outcome wins over stale native pending/in-progress state.

CrewCoder `crew-tasks` activity remains provider-owned and optional. Preserve the exact Task* tool name from ACP `_meta["crewcoder/tool"].name` even when ACP `name`/`kind` is generic (`think`/`other`). Treat `rawOutput.todos` as the authoritative session snapshot, and fold newer running Task* mutations over the current turn's last completed snapshot so live status is not masked. Merge a matching full `rawOutput.task` record without letting the lossy snapshot erase its stable id, session display number, description, owner/scope, metadata, dependency edges, or timestamps; keep provider-local generic todo ids out of that richer CrewCoder contract. Render `activeForm`, blocked pending state, completed state, owner, and display number consistently with the CrewCoder TUI. Every user message starts a fresh activity scope. Accept `TaskList` as native activity only with explicit `sessionOnly: true`; project-wide/default lists mix unrelated sessions and must be ignored. Use label-identified incremental TaskCreate/TaskUpdate/TaskDelete reconstruction as a compatibility fallback, never generic argument-shape guessing. Grok `todo_write` completion state comes from `result.TodosUpdated.state.todos` (the full session map); `TodosUpdated.todos` and merge arguments are a subset and must fold, not replace. CrewCode must not prompt providers to manufacture activity, fabricate tool events, or implicitly enable CrewCoder `crew-tasks`. CrewCoder-mode clarify and plan-approval cards are independent of `crew-tasks` and of tool-permission Allow/Deny; they stay visible when Todo activity is hidden. See `docs/agent-activity-overlay.md`.

YuHeard PTY integration must remain bundle-safe. `PtyService` receives the active YuHeard server through an injected accessor and statically imports its shell-wrapper helpers; do not use runtime relative `require('./yuheard-*')` calls from PTY code because electron-vite can move that code into a chunk without emitting the required sibling modules. CLI launch, initial TUI paint, and prompt submission are not completed turns. Codex must use only its exact `approval-requested` and `agent-turn-complete` hook events—never generic PTY idle/BEL heuristics—while output fallback detection remains available for agents without an exact hook. Suppress every YuHeard surface only when the exact completing terminal owns keyboard focus in the focused CrewCode window; a different pane must still alert. See `docs/yuheard.md`.

CrewCoder agent profiles are separate from CrewCode execution modes. Show the desktop model-row profile picker only when the installed CrewCoder provider is active; disable it during a running turn, persist the optional session-scoped `crewcoderMode`, omit `--mode` for Configured default, and pass only `general | crewcoder | plugin | extension` to `crewcoder acp --mode`. A concrete profile locks the underlying CrewCode permission policy to Build and disables Ask/Plan/Build/Full on desktop and phone; Configured default re-enables those controls. Never retain a hidden prior Ask, Plan, or Full Access policy under a concrete profile. A profile change is a launch-flag change, so drop only the idle CrewCoder bridge and native-resume it on the next prompt. Never route Ask/Plan/Build/Full into CrewCoder's `--mode`. The `crewcoder` profile's plan gate is CrewCoder-owned: project `crewcoder_clarify` / `crewcoder_propose_plan` into the activity overlay and send `/approve-plan` as a prompt, never as a tool-permission Allow/Deny. See `docs/crewcoder-provider.md`.

Provider context handoff is initiated from the Solo Chat header or `/handoff`. Its Used chats tab mirrors the current workspace's live Sessions catalogue across chat tabs; starting either a new or used destination closes the card immediately and moves progress/failure feedback to the destination meter. Preserve each selected destination's owner tab/worktree, existing provider/model/effort locking, and disposable destination-provider summary flow documented in `docs/provider-context-handoff.md`.

## Plugin platform notes

Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
> Run, supervise, and review multiple AI coding agents across git worktrees without losing control of your repo.

[![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](./LICENSE)
[![Website](https://img.shields.io/badge/Website-crewcode.logixhub.icu-0b7285.svg)](https://crewcode.logixhub.icu)
[![X](https://img.shields.io/badge/X-@OnPointTools-000000.svg)](https://x.com/OnPointTools)
[![YouTube](https://img.shields.io/badge/YouTube-@CjWisdom-FF0000.svg)](https://www.youtube.com/@CjWisdom)

</div>

Expand Down
12 changes: 8 additions & 4 deletions bin/crewcode-server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,22 @@ import { fileURLToPath } from 'url'
import { spawnSync } from 'child_process'

const root = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const entry = join(root, 'out', 'main', 'headless.js')
const args = process.argv.slice(2)
const requested = args[0]
const command = requested === 'hub' || requested === 'enroll' || requested === 'brain' ? requested : 'serve'
const entry = join(root, 'out', 'main', command === 'hub' ? 'hub.js' : command === 'serve' ? 'headless.js' : 'brain.js')
if (!existsSync(entry)) {
console.error('CrewCode server build is missing. Run `npm run build` before starting from this checkout.')
console.error(`CrewCode ${command} build is missing. Run \`npm run build\` before starting from this checkout.`)
process.exit(1)
}

// The Electron package sets ELECTRON_RUN_AS_NODE in some development shells.
// A headless server must always execute in ordinary Node.js.
// Headless services must always execute in ordinary Node.js.
delete process.env.ELECTRON_RUN_AS_NODE
const require = createRequire(import.meta.url)
const module = require(entry)
module.runHeadless(process.argv.slice(2)).catch(error => {
const run = command === 'hub' ? module.runHub : command === 'serve' ? module.runHeadless : (argv => module.runBrainCommand(command, argv))
run(command !== 'serve' || requested === 'serve' ? args.slice(1) : args).catch(error => {
console.error(error?.message || String(error))
process.exitCode = 1
})
151 changes: 151 additions & 0 deletions bin/yuheard.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#!/usr/bin/env node
// yuheard — CLI for the YuHeard terminal agent-done channel.
//
// Subcommands:
// pane-id Print $YUHEARD_PANE_ID (or "(unset)").
// socket Print the YuHeard socket path.
// running [message] Report 'running' for the calling pane.
// complete [message] Report 'complete' for the calling pane.
// --help Show this help.
//
// Env:
// YUHEARD_PANE_ID Set by CrewCode when it spawns a pty pane.
// The CLI sends this verbatim.
// YUHEARD_SOCKET Override the default socket path
// (~/.crewcode/yuheard.sock).
//
// Exit codes:
// 0 ok
// 1 socket error / no pane for cwd
// 2 bad arguments
//
// See docs/yuheard.md for the protocol and integration patterns.

import net from 'node:net'
import os from 'node:os'
import path from 'node:path'

const args = process.argv.slice(2)
const sub = args[0]
const SOCKET = process.env.YUHEARD_SOCKET
?? path.join(os.homedir(), '.crewcode', 'yuheard.sock')

function send(line, { timeoutMs = 1000 } = {}) {
return new Promise((resolve, reject) => {
const sock = net.createConnection(SOCKET)
let buf = ''
let settled = false
const t = setTimeout(() => {
if (settled) return
settled = true
sock.destroy()
reject(new Error(`socket timeout after ${timeoutMs}ms`))
}, timeoutMs)
sock.on('data', d => { buf += d.toString('utf8') })
sock.on('end', () => {
if (settled) return
settled = true
clearTimeout(t)
resolve(buf.trim())
})
sock.on('error', e => {
if (settled) return
settled = true
clearTimeout(t)
reject(e)
})
sock.write(line + '\n')
sock.end()
})
}

function reportLine(state, message) {
return JSON.stringify({
pane_id: process.env.YUHEARD_PANE_ID ?? '',
state,
source: 'yuheard-cli',
message,
ts: Date.now(),
})
}

async function resolvePaneId(firstArg) {
if (process.env.YUHEARD_PANE_ID) return process.env.YUHEARD_PANE_ID
if (firstArg) {
try {
const lookedUp = await lookupPaneIdByCwd()
if (lookedUp) return lookedUp
} catch { /* fall through and treat firstArg as the pane id */ }
return firstArg
}
const lookedUp = await lookupPaneIdByCwd()
if (!lookedUp) throw new Error('lookup failed')
return lookedUp
}

async function lookupPaneIdByCwd() {
const cwd = process.cwd()
const lookupLine = JSON.stringify({ method: 'pane-id-lookup', cwd })
const reply = await send(lookupLine)
let parsed
try { parsed = JSON.parse(reply) } catch { throw new Error(`invalid server reply: ${reply}`) }
if (!parsed.ok) throw new Error(parsed.error ?? 'lookup failed')
return parsed.paneId
}

function help() {
process.stdout.write(`Usage: yuheard <subcommand> [args]

pane-id Print $YUHEARD_PANE_ID (or "(unset)").
socket Print the YuHeard socket path.
running [message] Report running for the calling shell's pane.
complete [message] Report complete for the calling shell's pane.
--help Show this help.

Env:
YUHEARD_PANE_ID Set by CrewCode when it spawns a pty pane.
YUHEARD_SOCKET Override default (~/.crewcode/yuheard.sock).

Exit codes:
0 ok 1 error 2 bad arguments
`)
}

async function main() {
switch (sub) {
case 'pane-id':
process.stdout.write(`${process.env.YUHEARD_PANE_ID ?? '(unset)'}\n`)
return
case 'socket':
process.stdout.write(`${SOCKET}\n`)
return
case '--help':
case '-h':
case undefined:
help()
return
case 'running':
case 'complete': {
const extra = args.slice(1)
const paneId = await resolvePaneId(extra[0])
const messageParts = extra[0] === paneId ? extra.slice(1) : extra
const message = messageParts.join(' ').trim() || undefined
const line = reportLine(sub, message).replace(
'"pane_id":""',
`"pane_id":"${paneId.replace(/"/g, '\\"')}"`,
)
const reply = await send(line)
process.stdout.write(`${reply}\n`)
return
}
default:
process.stderr.write(`yuheard: unknown subcommand "${sub}"\n`)
help()
process.exitCode = 2
}
}

main().catch(err => {
process.stderr.write(`yuheard: ${err?.message ?? String(err)}\n`)
process.exit(1)
})
3 changes: 3 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ internal references).
| [security-model.md](./security-model.md) | Authority-boundary audit: untrusted content -> agent -> MCP/plugin -> exec -> Git/SSH, per-hop gates, tests, and residual risk |
| [execution-custody.md](./execution-custody.md) | Withdrawing authority after it was granted: invariant tripwire, custody journal, halt/contain/preserve/report, explicit reauthorization |
| [remote-ssh-workspaces.md](./remote-ssh-workspaces.md) | Opening projects over SSH: setup, auth, host pinning, what works remotely |
| [web-remote-access.md](./web-remote-access.md) | Browser client, `crewcode serve`, self-hosted Hub relay, and `hub --local-brain` |
| [canvas-mode.md](./canvas-mode.md) | Workbench: multiple chats and terminals side by side on one worktree _(nav label: "Workbench")_ |
| [workspace-session-split.md](./workspace-session-split.md) | Drag a drawer thread onto Solo Chat or a terminal pane to split that view |
| [chat-archiving.md](./chat-archiving.md) | Right-click a chat to archive it; the Archive page, restoring, and retention flagging |

### Git
Expand Down Expand Up @@ -77,6 +79,7 @@ internal references).
| [agent-message-markdown.md](./agent-message-markdown.md) | Markdown/Shiki rendering rules for agent replies |
| [tool-calling-breakdown.md](./tool-calling-breakdown.md) | Per-tool rendering spec for tool-call rows |
| [notifications.md](./notifications.md) | Notification bar architecture and `useNotifications()` API reference |
| [yuheard.md](./yuheard.md) | Terminal-agent completion protocol, shell wrappers, Codex hook, alert settings, and troubleshooting |
| [crew-orchestrator.md](./crew-orchestrator.md) | Crew orchestrator design spec: state machine, layering, modes |
| [plugins-v0.md](./plugins-v0.md) | Plugin platform v0 implementation snapshot and pre-v1 gates |

Expand Down
Loading
Loading