Commit Graph

29 Commits

Author SHA1 Message Date
alex 10955dfe8e fix(tool): stop defaulting manifest working_dir to /workspace
The /workspace compatibility symlink was removed from the manifest
compiler/entrypoint in 3e59a25. Tool definitions that still set
runtime.working_dir to /workspace therefore start in an empty directory
instead of /home/user/{repo_name}.

- manifest-editor.tsx: default working_dir to empty instead of /workspace;
  update startup-script placeholder to reference /home/alex/.
- Add Alembic migration 2026_06_19_113000 that clears the stale
  runtime.working_dir = /workspace from the built-in pi-agent manifest.
- Add migration import test.

Quality gates: pytest tests/api tests/services/test_terminal_manager_multi.py tests/unit (248 passed), ruff check (clean), npx tsc --noEmit (clean), eslint (clean).
2026-06-19 12:10:42 +02:00
alex b32fea671f fix(terminal): avoid lazy-loading tool manifest in async websocket handler
The container-user resolver introduced in 9f72093 accessed
'tool_type.manifest', which triggers a SQLAlchemy lazy load inside the
async WebSocket coroutine and raises MissingGreenlet. Fetch the manifest
explicitly with db_session.get() instead, matching the pattern used in
instance_service.py.

- Replace relationship access with explicit async loads in
  _resolve_container_user().
- Add unit tests covering manifest, base-definition, legacy, and missing
  manifest cases.
- Update project map artifacts.

Quality gates: pytest tests/api tests/services/test_terminal_manager_multi.py tests/unit (247 passed), ruff check (clean).
2026-06-19 11:48:53 +02:00
Developer 9f720930ea fix: run tool terminal sessions as container user instead of root
- Remove compose-level user: 0:0 override from manifest_compiler.py so the
  entrypoint can start as root, fix mount ownership, and drop privileges to
  the container user internally.
- Add get_manifest_container_user() helper to resolve the manifest-declared
  container user (with uid:gid fallback).
- Pass container user through TerminalSession, TerminalManager, and the
  terminal WebSocket handler so docker exec is invoked with --user <user>.
- Update and add unit tests for the manifest compiler and terminal session.
- Record the additional root-user fix in the fix-pi-container-mount-permissions
  OpenSpec change/tasks.

Quality gates: pytest tests/unit/ (226 passed), pytest tests/services/test_terminal_manager_multi.py (7 passed), ruff check on changed files (clean), mypy on changed files (clean)
2026-06-17 20:51:46 +00:00
Developer 93da9b42a2 chore(project-map): regenerate .pi-map artifacts across the repo 2026-06-17 11:31:24 +00:00
Developer 29e48cdb65 fix: force interactive login shell for bash containers
Detached containers with tty: true still exited immediately because a
plain /bin/bash invocation exits with code 0 when stdin is not connected.

- Detect when the container CMD is /bin/bash or bash and exec an
  interactive login shell () after dropping privileges
- Keep the generic  path for non-shell commands
- Bump compiler_version to v4 to force a fresh image build

Quality gates:
- pytest tests/unit: 219 passed
- ruff: clean on changed files
- mypy: clean on changed files
2026-06-15 11:18:28 +00:00
Developer 1d345eba32 fix: use runuser for privilege drop and capture container stderr in logs
The container still exited immediately after the su-based fix.  can
interfere with TTY/stdin handling for interactive shells. Switch to
, which is root-only, skips PAM, and preserves file descriptors so
bash stays interactive.

Also improve container failure diagnostics:
-  now combines stdout and stderr
- This helps surface the real reason when a container exits with code 0

Quality gates:
- pytest tests/unit: 219 passed
- ruff: clean on changed files
- mypy: clean on changed files
2026-06-15 10:41:37 +00:00
Developer 94137c6586 fix: use login shell when dropping privileges in manifest entrypoint
The generated entrypoint used a brittle  invocation that could
spawn a non-interactive bash and exit immediately, causing containers to
stop with exit code 0 right after startup.

- Switch to  so the
  container user gets a login shell and stdin/tty are preserved
- Update the unit test assertion for the new drop-privileges command

Quality gates:
- pytest tests/unit: 219 passed
- ruff: clean on changed files
- mypy: clean on changed files
2026-06-15 10:18:52 +00:00
Developer 1658767cf4 fix: clone workspace into repo-named subdirectory directly
Git clone with an explicit destination puts the working copy directly into
that directory; it does not create a repo-named subdirectory. The previous
change assumed the opposite, so workspaces ended up at
/data/working-copies/{workspace_id}/ with the repo contents mixed in,
causing a 500 when the expected repo-named subdirectory was missing.

- Build the target path as /data/working-copies/{workspace_id}/{repo_name}/
  and pass it directly to GitService.clone
- Remove stale directory detection and fallback logic that is no longer
  needed
- Keep diagnostic logging around git clone failures

Quality gates:
- pytest tests/unit: 219 passed
- mypy: clean on changed files
2026-06-15 10:09:30 +00:00
Developer 83928d0f02 chore: add diagnostic logging around workspace git clone
The workspace creation endpoint returns 500 but the actual error is not
visible. Add explicit error logging when GitService.clone fails and info
logging when git creates a directory name different from the one derived
from the remote URL.

Quality gates:
- pytest tests/unit: 219 passed
- mypy: clean on changed files
2026-06-15 10:04:34 +00:00
Developer 8f648264f1 fix: surface real workspace creation errors instead of generic 409
The create-workspace endpoints caught every exception and returned 409
"Workspace name already exists", hiding the actual failure (e.g. git
clone errors, remote URL problems, or filesystem issues).

- Distinguish ValueError -> 400, unexpected exceptions -> 500 with detail
- Preserve HTTPException re-raise for existing FastAPI error paths

Quality gates:
- pytest tests/unit: 219 passed
- mypy: clean on changed files
2026-06-15 09:46:55 +00:00
Developer b26ed7c3e4 refactor: store workspaces as {workspace_id}/{repo_name} for natural git clone layout
Working copies were stored as /data/working-copies/{repo_id}/{workspace_name}/,
so git clone was forced into a user-named directory. That meant the container
mount basename was the workspace name (e.g. main) instead of the repo name.

- Generate the workspace UUID before cloning and clone into
  /data/working-copies/{workspace_id}/ so git creates {repo_name}/ naturally
- Set workspace.path to /data/working-copies/{workspace_id}/{repo_name}/
- Update _migrate_clone_into_workspace() to use the same layout
- _get_repository_mount_name() now prefers workspace.path basename and only
  falls back to remote URL / repo.name for legacy repo-only instances
- Update unit tests to assert workspace path basename is used for mounts

Quality gates:
- pytest tests/unit: 219 passed
- ruff: clean on changed files
- mypy: clean on changed files
2026-06-15 09:40:59 +00:00
Developer 6e33e8e4e9 fix: remove explicit repo mount from pi-agent manifest and derive workspace name from remote URL
The pi-agent manifest still declared an explicit repo mount with
{{WORKSPACE_NAME}}, making the mount target dependent on tool config. The
instance service now synthesizes the repo mount, so the manifest no longer
needs the explicit mount.

- Add Alembic migration 2026_06_15_090500 to remove the source_type: repo
  mount from the built-in pi-agent manifest
- Add _get_repository_mount_name() helper to derive the workspace directory
  name from the repository remote URL (matching git clone behavior) and
  fall back to the user-provided repository name
- Use the helper for WORKSPACE_NAME/REPO_NAME in manifest, legacy dockerfile,
  and legacy compose template paths
- Update unit tests for the new migration and helper

Quality gates:
- pytest tests/unit: 218 passed
- ruff: clean on changed files
- mypy: clean on changed files
- alembic heads: single head
2026-06-15 09:10:05 +00:00
Developer f0ae9483f3 fix: use repository name for workspace mount target
WORKSPACE_NAME was computed from os.path.basename(repo_path), so when a
workspace path ended in a directory like 'main', the container mount target
became /home/user/main instead of /home/user/{repo-name}.

- Use GitRepository.name for WORKSPACE_NAME/REPO_NAME in manifest and
  legacy dockerfile flows
- Add unit test verifying prepare_manifest_instance uses repo.name even
  when the workspace path basename differs

Quality gates:
- pytest tests/unit: 213 passed
- ruff: clean on changed files
- mypy: clean on changed files
2026-06-15 08:54:01 +00:00
Developer 90992e46a8 fix: remove stale {{WORKSPACE_NAME}} directory from container home
Older cached images still contain a literal /home/user/{{WORKSPACE_NAME}}
directory baked in by the previous Dockerfile generation. Even though new
images no longer create it, existing images leave the placeholder folder
alongside the real repo-named mount.

- Add entrypoint cleanup that removes /{{WORKSPACE_NAME}} if it
  exists before creating the real workspace target and /workspace symlink
- Update unit tests to assert the stale placeholder removal

Quality gates:
- pytest tests/unit: 212 passed
- ruff: clean on changed files
- mypy: clean on changed files
2026-06-15 08:41:27 +00:00
Developer 41f9427224 fix: avoid literal {{WORKSPACE_NAME}} directories in built images
When a manifest mount target uses ~/{{WORKSPACE_NAME}}, the Dockerfile was
building a literal directory named {{WORKSPACE_NAME}} into the image and
creating a broken /workspace symlink. The runtime mount then created the
correct repo-named folder alongside the placeholder folder.

- Only create static mount target directories in the Dockerfile; skip any
  target containing {{WORKSPACE_NAME}}
- Only create the /workspace compatibility symlink at image-build time when
  the workspace name is known; otherwise let the entrypoint create it from
  the WORKSPACE_NAME environment variable
- Update unit tests to cover both build-time workspace names and runtime
  placeholders

Quality gates:
- pytest tests/unit: 211 passed
- ruff: clean on changed files
- mypy: clean on changed files
2026-06-15 08:29:03 +00:00
Developer 089d802f1d fix: prevent failed containers from showing as running on dashboard
- Add final get_container_status check in start_tool_instance before
  writing status=running; mark as error and return logs if container stopped
- Treat restarting as error in HealthMonitor when DB status was already
  running, so crash loops are surfaced instead of preserved
- Disable auto-restart (restart: unless-stopped -> restart: no) for tool
  instances in manifest compiler, legacy dockerfile path, and built-in seeds

Quality gates:
- pytest tests/unit: 210 passed
- ruff: clean on changed files
- mypy: clean on changed files
2026-06-14 21:52:02 +00:00
Developer a4e6c46a47 fix: run manifest containers as root and drop privileges in entrypoint
The compose file was forcing the container to run as uid 1001, so the
entrypoint could not create /workspace even with sudo configured.

- Remove Dockerfile USER directive so containers start as root
- Make compile_compose use user: 0:0 when the manifest declares a user
- Make the entrypoint drop to the container user via  after setup,
  preserving environment variables and command arguments
- Update unit tests to assert root startup and privilege drop

Quality gates:
- pytest tests/unit: 210 passed
- ruff: clean on changed files
- mypy: clean on changed files
2026-06-14 21:32:27 +00:00
Developer 47de2a0133 fix: check root before sudo when creating /workspace symlink
The previous ordering checked SUDO before checking if the process was
already running as root. When Docker starts the container with a
non-root user, SUDO may be empty, but the real fix is that the
entrypoint should try root first (e.g. when the image is started as
root) and only then fall back to sudo.

- Reorder symlink creation logic: root first, then sudo, then best-effort
- Update unit test to assert root is checked before sudo

Quality gates:
- pytest tests/unit: 208 passed
- ruff: clean on changed files
- mypy: clean on changed files
2026-06-14 21:25:41 +00:00
Developer bd94cc9bbf fix: use sudo/root to create /workspace symlink in manifest entrypoint
The previous commit moved the pi-agent repo mount from /workspace to
/home/user/{repo_name}. This exposed a permission bug: the Dockerfile
creates /workspace as a root-owned symlink in the image, and the
non-root entrypoint could not replace it because / is owned by root.

- Update compile_entrypoint to recreate /workspace via sudo when running
  as the container user, or directly when running as root
- Add unit test covering sudo/root symlink creation
- Update OpenSpec change docs with the additional root cause

Quality gates:
- pytest tests/unit: 208 passed
- ruff: clean on changed files
- mypy: clean on changed files
- alembic heads: single head
2026-06-14 20:29:03 +00:00
Developer fe82a248ec fix: pi container repo mount target and npm update permissions
- Add Alembic migration to update built-in pi-agent manifest:
  * repo mount target from /workspace to ~/{{WORKSPACE_NAME}}
  * keep /workspace as compatibility symlink via working_dir
  * update startup chown target to $HOME/$WORKSPACE_NAME
- Pass REPO_NAME and WORKSPACE_NAME to compile_compose from instance_service
- Substitute {{WORKSPACE_NAME}} in manifest mount targets and expose it as
  a container env var so the entrypoint can create the /workspace symlink
- Generate entrypoint workspace symlink from runtime WORKSPACE_NAME env var
- Install npm_global packages into {home_dir}/.npm-global with PATH so the
  non-root container user can update global packages
- Update manifest compiler unit tests for the new behavior

Quality gates:
- pytest tests/unit: 207 passed
- ruff: clean on changed files
- mypy: clean on changed files
- alembic heads: single head
2026-06-14 18:45:32 +00:00
Developer ddd92e3dd4 feat: implement configurable tool container home directory
- Add ToolType.home_directory column with default /home/user
- Add Alembic migration to add column, set existing rows, and rewrite
  /workspace to /home/user/{{WORKSPACE_NAME}} in legacy templates
- Add merge migration fc8f1a20cbf6 to resolve Alembic multiple heads
- Update manifest compiler to honor manifest.home_directory for HOME,
  WORKDIR, /workspace symlink, and default repo mount target
- Update legacy dockerfile/compose instance generation to use
  tool_type.home_directory
- Thread resolved home_dir through config profile and git mount expansion
- Generate entrypoint permission fixer to chown home/mounts at startup
- Update base.dockerfile with sudo/passwordless sudo for permission fixer
- Add unit tests for manifest compiler, instance service, and migrations
- Add placeholder integration test for container lifecycle
- Update openspec/tasks/home-path-expansion.md task checkboxes
- Update project maps for modified files

Quality gates: py_compile, ruff, mypy, pytest tests/unit (205 passed),
pytest tests/integration (110 passed, 35 skipped). Alembic round-trip
and container lifecycle integration tests require Docker/PostgreSQL.
2026-06-14 13:09:41 +00:00
Developer 4c14966ae3 fix: write valid multi-line tmux config in Pi Agent images
The Pi Agent dockerfile templates created ~/.tmux.conf with a literal
\n because the RUN command used single-quoted echo. Tmux never parsed
the malformed line, so mouse mode stayed off. Without tmux mouse mode,
mouse-wheel events in xterm.js fell back to Up/Down arrow keys and
cycled shell command history instead of scrolling the terminal buffer.

- Use printf '%s\n' to write real newlines in .tmux.conf.
- Apply the same fix to the ranger rc.conf where the same bug existed.
- Update tool-images/pi-agent.dockerfile and both affected alembic
  migration dockerfile strings.

Quality gates: npm run typecheck, npm run lint, npm test -- --run (87 passed),
py_compile on changed migrations.

Refs: openspec/changes/fix-tmux-mouse-config
2026-06-14 08:48:39 +00:00
Developer d247e44985 fix: include project_name in /workspaces list response
- The top-level GET /workspaces endpoint was returning project_name as
  an empty string because the GitRepository.project relationship was not
  eager-loaded.
- Select the full GitRepository entity and apply selectinload(project)
  so project_name is populated for every workspace row.

Backend quality gates: python3 -m pytest 313 passed, 34 skipped.
2026-06-13 19:59:13 +00:00
Developer d395aaf574 fix: stage config-profile mounts as directories so ~/.config stays writable
- Switch apply_resolved_profile from per-file bind mounts to one
  directory-level bind mount per ResolvedMount target.
- Stage all configured files under instance_dir/mounts/<sanitized_target>
  and bind-mount that directory, so Docker no longer creates a root-owned
  parent directory such as ~/.config.
- Propagate read-only mode ('ro') as the 'readonly' flag on volume entries.
- Update unit tests to expect directory-level mounts and add coverage for
  readonly/writable flags.

Quality gates: python3 -m py_compile, pytest (313 passed, 34 skipped),
npm run typecheck, npm run lint.
2026-06-13 11:56:48 +00:00
Developer 19291d6be9 chore: patch project maps after tool-config mount cleanup 2026-06-13 11:40:03 +00:00
Developer 7ef8b0eb36 chore: patch project maps after stale backend test cleanup
Regenerate .pi-map*.md artifacts for source changes in:
- apps/api/src/api/tool/tool_types_validation.py
- apps/api/src/schemas/tool/tool_type.py
- apps/api/tests/integration/test_tool_types_api_extended.py
- and all affected test files from backend-frontend refactoring cleanup
2026-06-12 20:36:31 +00:00
Developer ce8b5dc86d feat: complete reorganize-long-files cleanup
- Extract tool instance lifecycle endpoints (start/stop/restart/delete) from
  api/tool/tool_instances.py into new api/tool/tool_lifecycle.py.
- Register tool_lifecycle_router in main.py and api/tool/__init__.py.
- Extract inline WorkspaceDetailPage components into
  components/features/workspace/: detail header, tab bars, file/git/tools/settings
  panels. Slim page from ~446 to ~62 lines.
- Update OpenSpec reorganize-long-files tasks to reflect completed work and
  current source state; mark change completed.
- Regenerate project maps.

Quality gates: python3 -m py_compile (backend clean), npm run typecheck,
npm run lint, npm test -- --run (87 passed), pytest workspace integration
and unit tests (27 passed, 1 skipped).
2026-06-12 18:53:23 +00:00
Developer c26e9eacfa feat: complete workspace-first-ui cleanup and tests
- Delete dead repo-workspace code: RepoWorkspacePage, useRepoWorkspace,
  WorkspaceLayout, FileBrowser, old git components (git-toolbar, file-editor,
  commit-panel), and repo-workspace.css.
- Fix stale backend test imports for moved models/services.
- Add GitOperations unit tests.
- Add integration tests for workspace files, git, and instances endpoints.
- Add frontend tests for WorkspaceDetailPage and ProjectCard.
- Update OpenSpec workspace-first-ui tasks and mark change completed.
- Regenerate project maps.

Quality gates: npm run typecheck, npm run lint, npm test -- --run (87 passed),
python3 -m py_compile on changed backend files, pytest backend workspace tests.
2026-06-12 17:25:09 +00:00
Developer 070cd4d5a5 chore: track all project map and index files
Add generated .pi-map.md and .pi-map.index.md files across the repository
so the project navigation maps are shared and versioned. These artifacts
are maintained by project_map_init/patch/validate and must be kept in
sync with source edits.

Note: .cache/ remains ignored (added in previous commit).
2026-06-12 13:46:23 +00:00