Compare commits

..

162 Commits

Author SHA1 Message Date
alex bcc7486b59 fix(terminal): use ctrl shift c for output copy
Capture Ctrl+Shift+C before xterm input handling and copy selected output through a browser-compatible fallback. Preserve Ctrl+C as terminal input.
2026-07-22 13:01:31 +02:00
alex 29e765b2be merge: support terminal browser clipboard shortcuts 2026-07-22 12:52:20 +02:00
alex d14cdc1151 fix(terminal): support browser clipboard shortcuts
Copy selected terminal output with Ctrl/Cmd+C without sending an interrupt, and route browser paste consistently through the terminal transport.
2026-07-22 12:52:19 +02:00
alex c80dbf9737 merge: preserve mounted profile file updates 2026-07-22 11:52:43 +02:00
alex 5331a0f110 fix(config-profiles): preserve mounted file inodes
Update files within profile directory mounts in place so editor saves reach running containers.
2026-07-22 11:52:42 +02:00
alex b995521e22 merge: preserve profile file bind mount updates 2026-07-22 11:37:54 +02:00
alex 5610017f50 fix(config-profiles): preserve bound file inodes
Overwrite individually bind-mounted profile files in place so editor saves remain visible to running containers.
2026-07-22 11:37:53 +02:00
alex 61e7d68d71 merge: synchronize shared profile mount working copies 2026-07-22 11:23:34 +02:00
alex 2247ec47c9 fix(config-profiles): synchronize shared mount working copies
Use canonical profile files and writable Git working copies so editor and container changes share one source. Require confirmation before destructive Git refreshes and overlay profile files without composite snapshots.
2026-07-22 11:23:19 +02:00
alex 0d6c1926ae merge: add Git mount refresh feedback 2026-07-21 21:18:25 +02:00
alex 900a8e47a5 fix(config-profiles): show Git mount refresh progress
Disable duplicate refresh requests and show in-progress feedback while Git mount sources are refreshed.
2026-07-21 21:14:08 +02:00
alex 25e870ba43 merge: fix container mount layering 2026-07-21 20:54:44 +02:00
alex 16984b7cf6 fix(containers): compose profile and Git mounts safely
Stage profile sources per instance and compose overlapping bind mounts so Docker cannot mask Git content or leave writable files root-owned.\n\n- preserve shared Git clones while applying profile overlays\n- add mount composition and ownership regression coverage\n- update OpenSpec tracking
2026-07-21 20:49:03 +02:00
Developer fc52353b2e fix: break config profile refresh import cycle 2026-07-21 15:46:35 +00:00
Developer a63a983116 feat: merge live Git config mount refresh 2026-07-21 15:37:34 +00:00
Developer 886c863260 feat: refresh shared Git config mounts live
- Use profile-scoped canonical Git clone sources with locked refreshes
- Mount shared Git configuration read-only and isolate profile content
- Add API and desktop/mobile actions for live Git mount refresh

Quality gates: frontend build and backend py_compile passed.
Skipped: backend pytest/Ruff unavailable; Docker/manual checks not approved.
2026-07-21 14:37:42 +00:00
Developer 3c25fffd49 feat: merge live config profile refresh 2026-07-21 11:36:56 +00:00
Developer add7c1b500 feat: add live config profile refresh
- Standardize built-in tool users for shared writable profile mounts
- Mount canonical non-Git profile sources across compatible instances
- Report restart-required outcomes and guard active profile deletion
- Surface restart feedback in config profile editing

Quality gates: frontend build passed; backend py_compile and LSP passed.
Skipped: backend pytest/Ruff unavailable; Docker/manual checks not approved.
2026-07-21 11:12:41 +00:00
Developer f9f9372ee7 fix: merge web terminal resilience 2026-07-18 10:32:56 +00:00
Developer ea42165ed2 fix: harden web terminal paste and reconnect
- Queue bounded ordered terminal input so acknowledgements remain responsive
- Prevent stale sockets and retries from replacing healthy connections
- Preserve desktop scrollback behavior and add terminal regression coverage

Quality gates: frontend tests (91 passed), typecheck, lint, build, Python compilation, LSP diagnostics. Backend pytest skipped by user request.
2026-07-17 22:11:24 +00:00
Developer c178225c8b Merge branch 'fix/git-mount-clone-reuse' into dev 2026-07-17 20:54:21 +00:00
Developer b5e961ebe9 fix: reuse cached config profile git mounts
- Reuse valid deterministic git mount clones on repeated starts
- Remove incomplete clone destinations before retrying
- Add regression coverage for cached and partial clones
- Document OpenSpec change fix-config-profile-git-mount-clone-reuse

Quality gates: pytest (13 passed), ruff, mypy
2026-07-17 20:54:01 +00:00
Developer 5d379c5f8b merge: add Headquarter favicon 2026-07-15 13:06:41 +00:00
Developer 671540ded9 feat: add Headquarter favicon
- Add a geometric evergreen Headquarter mark for browser tabs\n- Register the SVG favicon and matching browser theme color\n\nOpenSpec: add-project-favicon\nQuality gates: npm run build
2026-07-15 13:06:40 +00:00
Developer ad26fd9f35 merge: dialog scroll containment 2026-07-15 10:20:39 +00:00
Developer 415aecc0dd fix: constrain dialog content to the viewport
- Add dynamic viewport bounds and scrollable body regions to dialogs and modals\n- Make tool-launch popups use the shared scrollable body pattern\n- Keep mobile sheets, action sheets, and notification popups scroll-contained\n- Restore the ProjectsPage test setup required for the frontend suite\n\nOpenSpec: fix-dialog-scroll-containment\nQuality gates: npm run typecheck, npm run lint, npm test (88 passed), npm run build
2026-07-15 10:20:20 +00:00
Developer bb38b37ceb fix: reinitialize terminal on mobile viewport changes
Recreate the terminal when responsive classification changes so mobile scrollback and touch listeners are installed.\n\nOpenSpec: fix-mobile-terminal-scrolling\nQuality gates: npm run typecheck, npm run lint, npm test (89 passed), npm run build
2026-07-14 19:41:34 +00:00
Developer 6698c20f25 fix: restore mobile terminal scrolling
- Retain xterm normal-buffer history on mobile while preserving desktop zero-scrollback behavior\n- Repair ProjectsPage tests for session context and current project list markup\n- Add focused terminal scrollback coverage\n\nOpenSpec: fix-mobile-terminal-scrolling\nQuality gates: npm run typecheck, npm run lint, npm test (89 passed), npm run build
2026-07-14 19:07:02 +00:00
Developer 5e06a2a226 Merge fix/terminal-paste-partial-writes into dev 2026-07-14 10:34:33 +00:00
Developer 49180e4c6d fix: fully drain terminal paste writes to PTY
- Make the host PTY master non-blocking and wait for write readiness.
- Retry partial writes so a large bracketed paste always delivers its closing
  marker instead of leaving pi in paste mode.
- Add regression coverage for partial PTY writes and resolve diagnostics.

Quality gates: ruff, mypy, focused pytest (3 passed)
2026-07-14 10:34:33 +00:00
Developer 7a5538b53f Merge fix/web-terminal-bracketed-paste into dev 2026-07-14 10:02:30 +00:00
Developer 41d24beade fix: forward browser terminal pastes as bracketed input
- Track application bracketed-paste mode from terminal output.
- Capture browser and mobile clipboard pastes only while that mode is enabled,
  normalizing line endings and sending a single BPM-framed input event.
- Remove the synchronous output decoding and console diagnostics that could
  stall terminal rendering under output load.
- Keep the terminal's zero-scrollback configuration and resolve existing
  no-case-declarations lint blockers in terminal keyboard shortcuts.

Quality gates: npm run typecheck, npm run lint
2026-07-14 10:02:30 +00:00
Developer d1777b88ad Merge fix/terminal-paste-bpm into dev 2026-07-13 13:42:56 +00:00
Developer 691bbaa87b fix: restore web terminal multiline paste by reverting scrollback to 10000
- Revert xterm.js scrollback from 0 to 10000; empirical correlation with
  bracketed-paste mode failing (each line submitted as separate command).
- Add temporary browser-console diagnostics to confirm whether xterm receives
  pi's \e[?2004h enable sequence and whether outbound paste is BPM-wrapped.
- Keep CSS scrollbar hiding and wheel-sensitivity 0 so the original scroll-jank
  fix remains effective.

Typecheck: passed (apps/web)
2026-07-13 13:42:56 +00:00
Developer 6191565e80 Merge branch 'fix/terminal-tui-scrollbar' into dev 2026-07-11 12:07:58 +00:00
Developer 468f34202a fix(terminal): hide scrollbar and stop stale-frame wheel scroll for TUI tools
The web terminal only hosts full-screen TUI tools (pi-agent, opencode),
which repaint in place in the normal buffer and do not use the alternate
screen or mouse tracking. With scrollback enabled, every repaint
accumulated as history, so xterm's viewport scrollbar appeared and the
mouse-wheel scrolled through stale frames instead of interacting with the
app; the scrollbar column also perturbed FitAddon's column count.

- terminal.tsx: set scrollback:0 and scrollSensitivity/fastScrollSensitivity:0
  so only the live viewport is kept (no bar, no stale-frame wheel jank).
- utilities.css: hide .xterm-viewport scrollbar (scrollbar-width:none +
  ::-webkit-scrollbar display:none) as belt-and-suspenders.

Wheel no longer scrolls stale frames; in-app scrolling uses the tool's own
keys. Headquarter-only change; no tool is touched.
2026-07-11 12:07:47 +00:00
Developer 996a858892 Merge branch 'fix/terminal-pong-leak' into dev 2026-07-11 11:49:01 +00:00
Developer ff7fcb6e8d fix(terminal): stop heartbeat pong leaking into the PTY as input
The WebSocket control-frame heuristic only recognized resize/ack/reset,
so the frontend's heartbeat reply {"type":"pong"} fell through to
write_input() and was typed into the shell / pi every ~30s. That garbage
corrupted the foreground app: stray text in the input line, rerenders,
and scroll-position resets (visible on resize/scroll redraws).

Treat any text frame that parses to a JSON object carrying a "type" field
as control traffic that must NEVER reach the PTY: handle known types and
ignore unknown ones. Keystrokes, bracketed-paste content, and plain text
are still forwarded as raw input.
2026-07-11 11:48:48 +00:00
Developer 3f06224b75 Merge branch 'fix/web-terminal-multiline-paste' into dev 2026-07-11 11:32:49 +00:00
Developer a1a77c99a6 fix: enable multiline paste in web terminal
Multiline pastes into the web terminal (especially into pi) were split
into one prompt per line because bracketed-paste markers were not
reaching the foreground app intact.

- Put the host PTY into raw mode (tty.setraw) after openpty() so it acts
  as a pass-through pipe. The default canonical line discipline was
  line-buffering input, splitting multiline pastes at newlines, and
  mangling bracketed-paste markers before docker exec / pi could see
  them. The in-container PTY (docker exec -t) provides real discipline.
- Route the mobile Paste button through xterm.js (term.paste) instead of
  sending raw clipboard text to the WebSocket, so content is wrapped in
  bracketed-paste markers when the app has enabled BPM.
- Treat a text frame as a control message only when it is a JSON object
  with a known type (resize/ack/reset); otherwise forward as raw input
  so JSON-shaped pastes are no longer silently dropped.

Quality gates: ruff, mypy (changed files), pytest unit (227 passed),
tsc, eslint
2026-07-11 11:32:23 +00:00
alex 3c96c7b153 Merge branch 'fix/remove-workspace-working-dir' into dev 2026-06-19 12:10:48 +02:00
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 6d7f538a79 Merge branch 'fix/terminal-manifest-lazyload' into dev 2026-06-19 11:48:59 +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 19f91c085e Merge branch 'dev' of ssh://git.commumedia.org:2222/alex/headquarter into dev 2026-06-17 20:57:27 +00:00
Developer 155d950556 Merge branch 'fix/pi-container-terminal-root-user' into dev 2026-06-17 20:52:02 +00: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
alex 428c70beaa fix: resolve project variable in create_tool_instance 2026-06-17 16:12:37 +02:00
alex 1c6dbcede8 fix: resolve project variable in create_tool_instance
create_tool_instance fetched `project` only inside the auto-display-name
branch, so callers supplying display_name left it unbound and the
manifest/dockerfile/compose branches crashed with UnboundLocalError at
_get_repository_mount_name (in-container layout /home/user/{project_name}).

Resolve project unconditionally after repo validation with a not-found
guard (narrowing Project|None -> Project), and drop the now-redundant
fetch from the auto-name branch. Add a regression test covering the
manifest + display_name path.

Quality gates: ruff, mypy, pytest (74 unit tests passed)
2026-06-17 16:12:22 +02:00
Developer 3e59a257dc feat(terminal): use project name for in-container cwd and clone directory 2026-06-17 13:26:16 +00:00
Developer 93da9b42a2 chore(project-map): regenerate .pi-map artifacts across the repo 2026-06-17 11:31:24 +00:00
Developer 0dd5f2317b fix(web/ui): improve nav-item hover contrast 2026-06-17 09:53:33 +00:00
Developer ab93863ea9 fix(web/ui): restore nav-item anchor styling for live session items 2026-06-17 09:47:37 +00:00
Developer 85627793b4 merge: fix/ui-padding-issues 2026-06-16 21:55:07 +00:00
Developer 8b6a4f7712 fix(web/ui): resolve padding and dialog layout issues from audit
- Remove conflicting .dialog/.dialog-body rules from utilities.css
- Fix live-session sidebar alignment and padding
- Wrap ProjectDialog, RepositoryCreateDialog, WorkspacesPage modals in .dialog-header/.dialog-body
- Migrate SessionsPage dirty-delete modal from .modal-* to .dialog-*
- Add padding to .project-card
- Fix session-card-actions border token (var(--border))
- Remove duplicate .dialog-actions rule in global.css
- Add pb-20 bottom clearance to ConfigProfile/ToolType editor scroll containers
- Add .card-md padding to ErrorState
2026-06-16 21:54:55 +00:00
Developer bf698b3ff2 feat(web/ui): unify session opening and add repo card in project list
- Add openSession utility with tab deduplication/focus
- Use openSession in navbar live sessions, use-instance-actions, and project tool links
- Pass onAddRepository to ProjectListItem and add dashed 'Add Repository' card
- Style add-repo card in projects.css
2026-06-16 21:31:38 +00:00
Developer d5119f29f6 feat(web/ui): merge project pane rework into dev 2026-06-16 21:20:15 +00:00
Developer 563ff13969 feat(web/ui): rework project pane into flat list with repo/workspace/tool hierarchy
- Add ProjectListItem component with project header actions
- Show repositories horizontally with branch labels
- List workspaces vertically under each repo
- Show running tools per workspace from useSessions
- Link workspace names to /workspaces/:id
- Link tool names to web URL or terminal page
- Keep existing mobile view and dialogs unchanged
2026-06-16 21:20:04 +00:00
Developer 39bac24100 fix(web/ui): align project heading left and keep add-repo button near title 2026-06-16 21:00:47 +00:00
Developer 889c598129 fix(web/ui): make config-section-actions sticky for both editors 2026-06-16 16:25:23 +00:00
Developer ac56c5e391 fix(web/ui): correct project card layout and unify config section actions
- Fix .project-card flex direction so expanded repos align left
- Add .config-section-actions for consistent save bars
- Replace sticky footer in Config Profiles with config-section-actions
- Replace dialog-actions in Tool Type editor with config-section-actions
- Convert manifest editor preview block to .config-section
2026-06-16 16:06:08 +00:00
Developer 17f4e91a84 feat(web/ui): merge ui polish round into dev 2026-06-16 15:56:13 +00:00
Developer b3f47310c3 docs(openspec): add ui polish round proposal, spec, and tasks 2026-06-16 15:56:06 +00:00
Developer 972dae64f2 feat(web/ui): unify config sections, restore home padding, improve project repos
- Add shared .config-section component class and migrate Tool Workshop
  and Config Profiles editors to use it.
- Restore --space-4 padding on home hero, summary cards, and sections
  with --space-3 on mobile.
- Restructure expandable project card: add 'Repositories' header with
  repo count and move 'Add Repository' button into the section header.
- Left-align repository blocks so they fill from the project detail edge.
2026-06-16 15:55:58 +00:00
Developer cbd5533adf feat(web/ui): merge Pass 3 consolidation and polish into dev 2026-06-16 15:27:58 +00:00
Developer 6e419f815d feat(web/ui): consolidate dialogs, cards, and breakpoints (Pass 3)
- Unify modal/dialog system; .modal-* are now aliases of .dialog-*
- Migrate WorkspacesPage, start-tool-modal, merge-dialog, workspace-tools-panel to .dialog-*
- Add .card-sm/.card-md/.card-lg/.card-elevated/.card-borderless modifiers
- Apply card utilities across workspaces, projects, ssh-keys, workspace-detail, git-history
- Remove duplicated card-like background/border/padding from page CSS
- Remove 860px breakpoint; standardize on 767px/768px mobile split
- Add docs/development/ui-review-checklist.md
- Archive web-ui-spacing-typography-rework OpenSpec change
2026-06-16 15:27:45 +00:00
Developer 4f4939406f docs(openspec): note push pending status in pass 2 report 2026-06-16 15:03:54 +00:00
Developer f47cf27720 feat(web/ui): merge pass 2 component cleanup into dev 2026-06-16 14:48:37 +00:00
Developer 6559c2a1d9 docs(openspec): add pass 2 spec, tasks, and completion report 2026-06-16 14:48:30 +00:00
Developer 5d5b39bec1 feat(web/ui): refactor component inline styles into utility classes
Pass 2 of the web UI spacing/typography/visual-rhythm rework.

- Add layout, spacing, typography, visual, card, and component utilities
- Add form-section, form-row, form-help, text-error, alert-success
- Unify .form-group and .form-field; add .status-badge family
- Alias legacy button classes to .btn primitives
- Refactor ToolTypeListSidebar and ConfigProfileListSidebar to use .sidebar
  and var(--sidebar-width) instead of hardcoded 280px
- Refactor ToolTypeEditorPanel, ConfigProfileEditorPanel,
  git-mount-editor, and manifest-editor to use utility classes

Quality gates: npm run typecheck, npm run lint, npm run build pass.
Inline style blocks in target components reduced from 198 to 11.
2026-06-16 14:47:37 +00:00
Developer 38d116dcf7 docs(openspec): mark pass 1 tasks complete 2026-06-16 12:24:31 +00:00
Developer da3f8da3ac docs(openspec): add web ui spacing/typography proposal, spec, tasks and audit 2026-06-16 12:24:10 +00:00
Developer 454a7845bb feat(web/ui): replace inline font-size violations and use new primitives in components 2026-06-16 12:24:04 +00:00
Developer 50e3af0d91 feat(web/ui): add mobile detail/search, ssh/profile utilities and nav-height tokens 2026-06-16 12:23:59 +00:00
Developer 8e1af41cd3 feat(web/ui): expand design tokens and add primitive component styles 2026-06-16 12:23:53 +00:00
Developer 355b471067 feat(web): redesign navbar live session items for scanability
Restructure live session nav entries into a 3-line layout:
- workspace/repository context on top
- session display name in the middle
- tool type and project on the bottom

Tool icon now has a status-dot badge, and the tooltip lists
display name, tool, project, context, and status.
2026-06-16 11:13:10 +00:00
Developer a2f0847aad Merge branch 'fix/terminal-session-callbacks' into dev 2026-06-15 13:53:15 +00:00
Developer 82ff8b8801 fix: route terminal callbacks and status per session
The recent redraw fix keeps all xterm instances mounted (display:none)
when switching sessions. However, sendData/focus/font-size refs and the
header connection status were still stored globally, so the last-mounted
hidden session could own keyboard input, font-size buttons, and the
status dot for the active session.

- Pass sessionId to onTerminalReady from TerminalComponent.
- Store terminal callbacks and status keyed by sessionId in use-terminal-page.
- Use activeSessionId to route special-key input, font-size changes, and header status.
- Clean up per-session refs and status when sessions are closed.
- Update MobileTerminalWrapper signature for the new callback shape.

Quality gates: npm run typecheck, npm run lint, npm test (87 passed)
2026-06-15 13:53:09 +00:00
Developer a6ad8268b9 fix: preserve terminal buffer across tab switches and focus regain
Render all terminal sessions and hide inactive ones with display:none so
xterm instances are no longer unmounted/remounted when switching tabs.

Remove terminal.clear() on the connected status message: the server keeps
the session buffer, and clearing was erasing visible content when the tab
regained focus or reconnected.

- Keep TerminalComponent instances alive in both Desktop and Mobile views.
- Only refit/resize on reconnect instead of clearing.

Closes terminal redraw on focus regain.
2026-06-15 13:11:08 +00:00
Developer 3358af57c2 fix: stack profile file mounts onto profile git-mounts to avoid masking
When a config profile declares both a git_mount and a mounts entry for the
same directory (e.g. ~/.pi), the generated bind-mounts would mask each other
inside the container. Instead, copy the static profile files into the
instance-scoped git-mount source directory so the container sees both the
cloned repo contents and the static files through a single bind-mount.

- Add _stack_profile_mounts_with_git_mounts helper to merge overlapping
  profile mounts into git-mount sources.
- Integrate stacking into start_tool_instance after resolving both mount
  types.
- Add unit tests for exact, descendant, non-overlapping, and file cases.
2026-06-15 12:50:19 +00:00
Developer 6a61669294 fix: git repo mount race and path resolution
- Include branch in git-mount clone dir hash so different branches of the
  same repo get separate directories and no longer race.
- Resolve profile working_directory before git mounts so relative targets
  are not silently skipped.
- Fall back to tool_type.home_directory for non-manifest tools.
- Fix glob target calculation to avoid duplicate directory segment.
- Add exc_info logging for git mount clone failures.
2026-06-15 12:15:50 +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 ed1f7805f6 fix: bump compiler version to v3 for runuser entrypoint
The previous compiler_version v2 already produced an image tag
(3d34c636) for the su-based fix. Images built under v2 still had the
broken su entrypoint that exited immediately. Bump to v3 so the runuser
change forces a fresh image build with the corrected privilege drop.

Quality gates:
- pytest tests/unit: 219 passed
2026-06-15 11:09:20 +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 e35e605914 fix: bump manifest image tag when compiler logic changes
compute_image_tag hashed only the manifest JSON, so cached images built
before the entrypoint fix were reused even though compile_entrypoint() now
produces a different entrypoint. This caused containers to keep using the
old (broken) entrypoint that exited immediately.

- Include a compiler_version token in the hash input so manifest compiler
  changes invalidate previously built images
- This forces a fresh image build for new instances after any change to
  compile_dockerfile, compile_entrypoint, or compile_compose

Quality gates:
- pytest tests/unit: 219 passed
- ruff: clean on changed files
- mypy: clean on changed files
2026-06-15 10:22:36 +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 c8db6ce933 fix: disable native touch panning on mobile terminal and archive specs
- Change mobile terminal CSS to use touch-action: none and
  overscroll-behavior: none so the custom touch handler owns swipes
- Archive completed/partial OpenSpec specs to
  openspec/changes/archive/2026-06-14-completed-specs-archive/
- Regenerate project maps

Quality gates: npm run typecheck, npm run lint (apps/web)
2026-06-14 18:07:01 +00:00
Developer 896674195c Merge branch 'feat/tool-container-home-directory' into dev 2026-06-14 13:10:10 +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 b4203a4a09 chore: update project maps for tool container home directory artifacts 2026-06-14 10:32:13 +00:00
Developer 5fc8e035e6 docs: tool container home directory design, plan, and test plan
- Add design doc / ADR for configurable /home/user home directory
   - Add implementation plan with phased rollout
   - Add test plan / QA checklist
   - Update OpenSpec task for home-path-expansion
2026-06-14 10:23:59 +00:00
Developer e6114ed18c Merge branch 'fix/terminal-container-overflow' into dev 2026-06-14 09:08:25 +00:00
Developer ac9f7a9299 fix: prevent terminal container from overflowing page on desktop
The desktop terminal page sometimes grew an outer scrollbar because the
terminal instance/wrapper/container chain lacked height constraints.
Without min/max-height enforcement, xterm.js's internal viewport could
expand its parent flex/grid track past the available space.

- Add overflow: hidden to .terminal-page.
- Add max-height: 100% and overflow: hidden to .terminal-instance.
- Add max-height: 100% to .terminal-wrapper.
- Add min-height: 0 to .terminal-container.
- Constrain .xterm-viewport to max-height/width 100% so it fills but
  never exceeds its container.

Quality gates: npm run typecheck, npm run lint, npm test -- --run (87 passed).

Refs: openspec/changes/fix-terminal-container-overflow
2026-06-14 09:07:43 +00:00
Developer 84cf423684 Merge branch 'fix/tmux-mouse-config' into dev 2026-06-14 08:49:57 +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 6c31ef9577 Merge branch 'feat/mobile-list-delete-button' into dev 2026-06-14 08:12:21 +00:00
Developer a12d6a8169 feat: restore delete buttons in mobile list views and fix edit action bar
- Wire MobileListView onItemDelete/onItemDuplicate callbacks to render
  action buttons in each list row.
- Pass onItemDelete in ToolWorkshopMobileView list view.
- Add CSS for mobile-list-item-action buttons.
- Fix MobileEditView sticky bottom action bar that was hidden behind
  the 64px mobile navigation bar; raise to bottom: 64px and z-index 110.

Quality gates: npm run typecheck, npm run lint, npm test -- --run (87 passed)

Refs: openspec/changes/mobile-list-delete-button
2026-06-14 08:10:58 +00:00
Developer 13b3c60abc Merge branch 'feat/mobile-edit-default-bottom-actions' into dev 2026-06-13 22:34:48 +00:00
Developer 23fc0a6b82 feat: mobile edit-as-default with sticky save/delete actions
- Update MobileEditView to render Save and optional Delete in a
  sticky bottom action bar; header now shows Cancel + title only.
- Make ConfigProfilesMobileView open edit view on profile tap.
- Make ToolWorkshopMobileView open edit view on tool type tap.
- Wire delete into MobileEditView for existing profiles and tool
  types.
- Stay on edit view after saving an existing item; create flow
  returns to list as before.
- Update ToolWorkshopPage cancel to return to list.
- Add mobile-edit-actions and mobile-edit-delete CSS.

Quality gates: npm run typecheck, npm run lint, npm test -- --run (87 passed)

Refs: openspec/changes/mobile-edit-default-bottom-actions
2026-06-13 22:29:15 +00:00
Developer 8b15689fb3 Merge branch 'feat/mobile-config-profiles-ui' into dev 2026-06-13 22:10:06 +00:00
Developer 98d4393387 feat: redesign mobile Config Profiles detail, preview, and edit pages
- Rewrite ConfigProfilesMobileView to match desktop functionality:
  full detail view with all fields, preview action showing resolved
  profile, and edit view with project/tool selects, includes,
  environment variables, runtime hints, files, mounts with nested
  files, and git mounts.
- Update useConfigProfiles.handleSubmit to return boolean success.
- Update ConfigProfilesPage to pass required state and callbacks.
- Add mobile-specific CSS for config profile forms, includes,
  mount/file cards, and preview panels.
- Allow MobileDetailView to render extra children.

Quality gates: npm run typecheck, npm run lint, npm test -- --run (87 passed)

Refs: openspec/changes/mobile-config-profiles-ui
2026-06-13 22:04:17 +00:00
Developer 35f0a3ea2e Merge branch 'fix/mobile-profile-title' into dev 2026-06-13 21:45:33 +00:00
Developer 1ae8d0e45f fix: hide page title on mobile Profile view
The mobile profile form already renders its own header via
ProfileMobileView, so the desktop page title was redundant on small
viewports.

Quality gates: npm run typecheck, npm run lint

Refs: openspec/changes/mobile-tool-profile-ui
2026-06-13 21:45:33 +00:00
Developer 025ccc5817 Merge branch 'feat/mobile-tool-profile-ui' into dev 2026-06-13 21:41:51 +00:00
Developer 350b393457 feat: rework mobile UI for Tool Workshop and Profile pages
- Rework ToolWorkshopMobileView to support full desktop functionality:
  definition type selection (Compose/Dockerfile/Manifest), manifest editor,
  conditional port, startup command, readiness probe, required variables,
  and validation feedback.
- Add ProfileMobileView and wire ProfilePage to render it on mobile.
- Update useToolWorkshop hook to return boolean success from submit.
- Add responsive CSS for mobile forms, edit views, and manifest editor.
- Update project maps.

Quality gates: npm run typecheck, npm run lint, npm test -- --run (87 passed)

Refs: openspec/changes/mobile-tool-profile-ui
2026-06-13 21:41:42 +00:00
Developer 8f7f682a92 fix: merge mobile terminal scroll detection improvements 2026-06-13 20:42:52 +00:00
Developer 2ece7074f6 fix: improve mobile terminal alternate-screen detection and scrolling
- Revert touch-action: none on .xterm-viewport so xterm.js can fall back
  to its own viewport scrolling when the custom handler doesn't take over.
- Detect alternate screen via reference equality
  (term.buffer.active === term.buffer.alternate) instead of the
  string, which could report normal buffer incorrectly.
- Lower vertical-scroll activation threshold from 4px to 2px and only
  prevent default once a vertical gesture is recognized.
- In normal buffer use term.scrollLines() so xterm.js handles the buffer
  scroll consistently; in alternate screen continue sending SGR 1006
  mouse-wheel sequences to tmux/vim.

Quality gates: typecheck, lint clean, npm test -- --run 87 passed.
2026-06-13 20:42:52 +00:00
Developer c7ab2de25e fix: merge mobile terminal scrolling improvements 2026-06-13 20:30:52 +00:00
Developer c9b0259993 fix: route mobile terminal swipes to the terminal, not the page layout
- Add touch-action: none and overscroll-behavior: none to mobile terminal
  page, content, wrapper, container, and xterm viewport so the browser
  never treats swipes as page/address-bar scrolling or pull-to-refresh.
- Make .terminal-page.mobile fixed-position to prevent viewport layout
  scroll; keep the overlay toolbar as absolute with pointer-events only
  on interactive parts.
- Rework the mobile touch handler in terminal.tsx:
  * Detect normal vs alternate buffer via term.buffer.active.type instead
    of measuring the DOM viewport, which was unreliable in tmux/vim.
  * Accumulate swipe distance and emit SGR 1006 mouse-wheel sequences in
    steps, so tmux pane scrolling tracks the gesture correctly.
  * Prevent default as soon as the swipe is recognized so the page does
    not start a competing scroll gesture.

Quality gates: npm run typecheck, npm run lint clean, npm test -- --run 87 passed.
2026-06-13 20:30:52 +00:00
Developer 1f2c3dbe2a fix: merge workspace project_name population 2026-06-13 19:59:13 +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 d60dec9d62 fix: merge workspace project name visibility 2026-06-13 19:53:43 +00:00
Developer 4913cc7297 fix: make workspace project name visible on mobile and desktop cards
- Move the project name below the workspace title row so it reads as a
  distinct line with a project icon.
- Move the status badge into the title row next to the workspace name,
  preventing it from crowding the project label.
- Add .workspace-title-row flex styles and update .workspace-project-name
  to display inline-flex with a brand-colored project icon.

Quality gates: npm run typecheck, npm run lint clean,
npm test -- --run 87 passed.
2026-06-13 19:53:43 +00:00
Developer 86360661e9 fix: merge nav sessions middle 2026-06-13 19:44:47 +00:00
Developer 1d09193652 fix: put Sessions back in the middle of nav on desktop and mobile
- Reorder desktop sidebar so Sessions sits between spaces/tools groups:
  Home, Projects, Workspaces, Sessions, Tool Workshop, Config Profiles, Settings.
- Reorder mobile bottom nav so Sessions is the center item:
  Home, Spaces, Sessions, Tools, Settings.
- Workspace cards already display the owning project name; no extra change needed.

Quality gates: npm run typecheck, npm run lint clean,
npm test -- --run 87 passed.
2026-06-13 19:44:47 +00:00
Developer 4ced6141ae feat: merge mobile Spaces nav and list views 2026-06-13 19:38:03 +00:00
Developer 530225d37c feat: mobile Spaces nav and list-based project/workspace views
- Combine Projects and Workspaces into a single 'Spaces' grouped mobile
  nav item that opens a bottom-sheet menu.
- Add SpacesBottomSheet component with Projects/Workspaces options.
- Extend MobileListView with optional renderItem prop for rich rows.
- Redesign mobile ProjectsPage rows to show project description and
  repository chips.
- Replace mobile WorkspacesPage list with compact WorkspaceCard grid,
  matching desktop card content.
- Add mobile-list-* CSS and mobile-workspaces-list spacing.

Quality gates: npm run typecheck, npm run lint clean,
npm test -- --run 87 passed.
2026-06-13 19:32:16 +00:00
Developer e6ab77d123 feat: merge workspace card redesign 2026-06-13 18:54:59 +00:00
Developer 4e1477c4be feat: redesign workspace card header and layout
- Swap workspace card header order: project name is now the primary eyebrow,
  workspace name is the bold title below it.
- Restructure workspace card into clean top/body/actions sections with more
  whitespace and clearer hierarchy.
- Replace cramped meta paragraphs with an inline meta row (repo, branch,
  instance count) and dedicated instance chip area.
- Use icon-only ghost buttons for sync/delete to reduce visual noise; keep
  prominent Start Tool button.
- Add top divider for actions, improve hover states, and make long names
  truncate gracefully.
- Update mobile workspace list subtitle to project · workspace name.
- Refresh workspaces.css with new card layout and responsive mobile rules.

Quality gates: npm run typecheck clean, npm run lint clean,
npm test -- --run 87 passed
2026-06-13 17:00:16 +00:00
Developer 2b5b8363ca chore: merge completed OpenSpec archive 2026-06-13 14:39:33 +00:00
Developer 1e0f95f8bd chore: archive completed OpenSpec changes
Move the following audited-and-implemented changes into
openspec/changes/archive/2026-06-12-completed-changes-archive/:

- tool-config-mount-cleanup
- config-profile-directory-mounts

Update archive README count and project map index/files accordingly.
openspec/changes/ now contains only the archive directory.

Quality gates: pytest 313 passed/34 skipped, npm run typecheck/lint clean,
npm test -- --run 87 passed
2026-06-13 14:24:30 +00:00
Developer 6a20c54da7 chore: merge project map patches 2026-06-13 14:15:30 +00:00
Developer db6ae38568 chore: patch project maps for loading-overlay and orphaned session entries
- Add loading-overlay.tsx to component maps
- Remove orphaned session-progress-panel.tsx and session-operations.tsx entries
- Refresh parent indexes after manual map edits

Quality gates: pytest 313 passed/34 skipped, npm run typecheck/lint clean,
npm test -- --run 87 passed
2026-06-13 12:31:35 +00:00
Developer 1e43ea48aa Merge branch 'fix/compose-readonly-mounts' into dev 2026-06-13 12:12:12 +00:00
Developer efe5e4ae50 fix: respect readonly flag on config-profile mounts in compose tool types
- modify_compose_file now appends :ro when a config-profile volume entry
  has readonly=true, matching the manifest compiler behavior.
- Add a guard for missing tool_type in prepare_manifest_instance.

Quality gates: python3 -m pytest (313 passed, 34 skipped)
2026-06-13 12:12:12 +00:00
Developer 31f1ce00d3 Merge branch 'feat/config-profile-directory-mounts' into dev 2026-06-13 11:56:48 +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 d78ca8a9d5 Merge branch 'chore/patch-project-maps' into dev 2026-06-13 11:40:03 +00:00
Developer 19291d6be9 chore: patch project maps after tool-config mount cleanup 2026-06-13 11:40:03 +00:00
Developer 5409ba2b13 Merge branch 'feat/tool-config-mount-cleanup' into dev 2026-06-13 11:35:55 +00:00
Developer 8bde9a213c feat: remove config/state mounts from built-in tool configs
- Remove pi_state and pi_config mounts from the pi-agent manifest.
- Add Alembic data migration to strip those mounts from existing DB rows.
- Remove opencode_home:/tmp volume and HOME=/tmp override from the opencode
  built-in compose template; config/state now belongs in config profiles.
- Workspace and SSH key mounts remain unchanged.

Quality gates: python3 -m py_compile, pytest (311 passed, 34 skipped),
npm run typecheck, npm run lint
2026-06-13 11:35:55 +00:00
Developer 315cb33e3f Merge branch 'chore/format-busy-overlay-followup' into dev 2026-06-13 11:19:22 +00:00
Developer 474843ffa6 chore: apply formatter to busy overlay files 2026-06-13 11:19:22 +00:00
Developer 766f7ff0d1 Merge branch 'feat/busy-container-overlays' into dev 2026-06-13 10:10:28 +00:00
Developer f8b162ec5f feat: busy container overlays for lifecycle actions
Add a reusable LoadingOverlay component that dims and disables the
container owning an in-flight action, with a spinning indicator and
label. Apply it to:

- SessionCard (when actionBusyId matches)
- InstanceList cards (per busyInstanceId with action-specific labels)
- CreateSessionForm (while submitting)
- ToolStarter (while starting)

Also add .icon-spin animation and position:relative to the relevant
containers.

Quality gates: npm run typecheck, npm run lint, npm test -- --run
(87 passed).
2026-06-13 10:10:28 +00:00
Developer 8a8a9bc9e4 Merge branch 'remove/session-operations-center' into dev 2026-06-13 09:50:14 +00:00
Developer 61f9c52455 remove: session operations center and progress panel
The bottom-right progress panel duplicated feedback already shown by
toasts. Remove it and the operation-tracking state to simplify the UI:

- Delete state/session-operations.tsx and session-progress-panel.tsx.
- Remove SessionOperationsProvider/SessionProgressPanel from AppShell.
- Remove startOperation/completeOperation calls from useInstanceActions
  and ToolStarter.
- Remove SessionOperationsProvider wrapper from DashboardPage.test.tsx.
- Remove .session-progress-panel CSS rules.
- Format use-events.test.ts mock to match project lint rules.

Quality gates: npm run typecheck, npm run lint, npm test -- --run
(87 passed).
2026-06-13 09:50:14 +00:00
Developer 43d65840ea Merge branch 'fix/progress-panel-sse-listeners' into dev 2026-06-13 09:30:45 +00:00
Developer ad5a4b5000 fix: listen for named SSE events in progress panel
The backend sends named lifecycle events (event: instance.health_changed),
but useEvents only set es.onmessage, which only receives unnamed message
events. Add explicit addEventListener registrations for all lifecycle
event types so the progress panel receives updates and completes.

Quality gates: npm run typecheck, npm run lint, npm test -- --run
(87 passed).
2026-06-13 09:30:45 +00:00
Developer 73088b75ba Merge branch 'fix/clone-mode-nullable-migration' into dev 2026-06-13 09:18:13 +00:00
Developer f23fadf52b fix(alembic): make tool_instances.clone_mode nullable
The workspace-first cleanup removed clone_mode from the creation flow,
so the API now inserts NULL. Align the database with the model by
making clone_mode nullable.

Apply with: cd apps/api && alembic upgrade head
2026-06-13 09:18:13 +00:00
Developer 9101033019 Merge branch 'fix/config-mount-permissions' into dev 2026-06-12 22:08:57 +00:00
Developer 72bf4ed962 fix: chown staged mount sources to container user
Config-profile and git mounts staged under instance_dir were created
by the API process (root), so when bind-mounted over ~/.config the
container user could not write. Recursively chown staged sources to
the resolved container uid/gid before compose up.

Quality gates: python3 -m py_compile passed; ruff/pytest skipped
(test tooling not available in this shell, helper smoke tested
with a temporary directory).
2026-06-12 22:08:57 +00:00
Developer 3b772ac239 Merge branch 'chore/archive-completed-openspec-changes' into dev 2026-06-12 21:11:11 +00:00
Developer 8c1948d226 chore: archive final 4 completed OpenSpec changes
Move the following completed changes from openspec/changes/ to
openspec/changes/archive/2026-06-12-completed-changes-archive/:
- multi-session-terminal-ux
- reorganize-long-files
- working-copies
- workspace-first-ui

Update parent and archive .pi-map*.md indexes to reflect the move and
remove the transient active-changes-archive grouping.

openspec/changes/ now contains only the archive/ directory.
2026-06-12 21:11:11 +00:00
Developer 43f3865f69 merge: chore/patch-project-maps 2026-06-12 20:36:39 +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 c6073fe9d2 merge: fix/stale-backend-test-cleanup 2026-06-12 20:23:21 +00:00
Developer 81b9a66ef5 fix: resolve stale backend test imports and schema drift
- Delete 4 obsolete unit tests tied to removed git mount/clone models
- Update imports and assertions across unit/integration/service tests
- Fix Settings defaults (postgres host, JWT props, cookie_samesite)
- Add skip guards for PostgreSQL-dependent integration tests
- Fix GitService env assertions and HealthMonitor state-change tests
- Repair docker/container inspect assertions in test_docker_service
- Fix ToolTypeCreate default_port validator ordering bug
- Fix check_port_exposed substring false-positive for port 0
- Update test_tool_types_api_extended to use interface_type field

Quality gates: pytest 311 passed, 34 skipped; npm typecheck/lint/test 87 passed
2026-06-12 20:23:17 +00:00
1035 changed files with 22901 additions and 9576 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
{
"fingerprint": "639c16d45210921c3c8ece071ef18bbe0c426ea2"
"fingerprint": "e7b3130f52a328d4051e75364e5394ac63df60c6"
}
+7 -7
View File
@@ -1,8 +1,8 @@
# Skill Registry — workspace
# Skill Registry — headquarter
<!-- Auto-generated by gentle-pi extensions/skill-registry.ts. Run /skill-registry:refresh to regenerate. -->
Last updated: 2026-06-05
Last updated: 2026-06-17
## Sources scanned
@@ -19,11 +19,11 @@ Last updated: 2026-06-05
| Skill | Trigger / description | Scope | Path |
| --- | --- | --- | --- |
| `openspec-apply-change` | Implement tasks from an OpenSpec change. Use when the user wants to start implementing, continue implementation, or work through tasks. | project | `/workspace/.opencode/skills/openspec-apply-change/SKILL.md` |
| `openspec-archive-change` | Archive a completed change in the experimental workflow. Use when the user wants to finalize and archive a change after implementation is complete. | project | `/workspace/.opencode/skills/openspec-archive-change/SKILL.md` |
| `openspec-explore` | Enter explore mode - a thinking partner for exploring ideas, investigating problems, and clarifying requirements. Use when the user wants to think through something before or during a change. | project | `/workspace/.opencode/skills/openspec-explore/SKILL.md` |
| `openspec-propose` | Propose a new change with all artifacts generated in one step. Use when the user wants to quickly describe what they want to build and get a complete proposal with design, specs, and tasks ready for implementation. | project | `/workspace/.opencode/skills/openspec-propose/SKILL.md` |
| `sift-backlog` | Triage and organize backlog tasks into actionable plans. Use when asked to review the backlog, prioritize tasks, create plans from backlog items, or move tasks from backlog to open status. Handles the full workflow of listing backlog tasks, grouping related tasks into plans, setting priorities and dependencies, activating plans, and changing task status from backlog to open. | project | `/workspace/.claude/skills/sift-backlog/SKILL.md` |
| `openspec-apply-change` | Implement tasks from an OpenSpec change. Use when the user wants to start implementing, continue implementation, or work through tasks. | project | `/home/user/headquarter/.opencode/skills/openspec-apply-change/SKILL.md` |
| `openspec-archive-change` | Archive a completed change in the experimental workflow. Use when the user wants to finalize and archive a change after implementation is complete. | project | `/home/user/headquarter/.opencode/skills/openspec-archive-change/SKILL.md` |
| `openspec-explore` | Enter explore mode - a thinking partner for exploring ideas, investigating problems, and clarifying requirements. Use when the user wants to think through something before or during a change. | project | `/home/user/headquarter/.opencode/skills/openspec-explore/SKILL.md` |
| `openspec-propose` | Propose a new change with all artifacts generated in one step. Use when the user wants to quickly describe what they want to build and get a complete proposal with design, specs, and tasks ready for implementation. | project | `/home/user/headquarter/.opencode/skills/openspec-propose/SKILL.md` |
| `sift-backlog` | Triage and organize backlog tasks into actionable plans. Use when asked to review the backlog, prioritize tasks, create plans from backlog items, or move tasks from backlog to open status. Handles the full workflow of listing backlog tasks, grouping related tasks into plans, setting priorities and dependencies, activating plans, and changing task status from backlog to open. | project | `/home/user/headquarter/.claude/skills/sift-backlog/SKILL.md` |
## Loading protocol
+1 -1
View File
@@ -2,7 +2,7 @@
dir: .claude
## role
Configuration and instruction files for the Claude AI assistant integration.
Configuration directory for Claude AI assistant settings and custom instructions.
## parent
index: ./.pi-map.index.md
map: ./.pi-map.md
+2 -2
View File
@@ -4,10 +4,10 @@ dir: .claude
index: .claude/.pi-map.index.md
## role
Configuration and instruction files for the Claude AI assistant integration.
Configuration directory for Claude AI assistant settings and custom instructions.
## files
## arch
Simple flat-file configuration storage with no complex architecture; contains prompt templates, system instructions, or context files used to guide Claude's behavior in this project.
Simple flat-file configuration storage with no executable code, using human-readable text files for AI behavior customization.
## tags
-
## symbols
+1 -1
View File
@@ -2,7 +2,7 @@
dir: .claude/skills
## role
Empty directory placeholder for future Claude Code skill definitions or custom AI-assisted workflows.
Contains skill definitions and configuration files for the Claude AI assistant to extend its capabilities with specialized behaviors and domain-specific expertise.
## parent
index: .claude/.pi-map.index.md
map: .claude/.pi-map.md
+2 -2
View File
@@ -4,10 +4,10 @@ dir: .claude/skills
index: .claude/skills/.pi-map.index.md
## role
Empty directory placeholder for future Claude Code skill definitions or custom AI-assisted workflows.
Contains skill definitions and configuration files for the Claude AI assistant to extend its capabilities with specialized behaviors and domain-specific expertise.
## files
## arch
None (no files present); likely intended for declarative skill modules using Claude's skill system pattern.
Declarative skill configuration system using structured metadata files (likely YAML/JSON) that define prompts, tools, and context boundaries for modular AI capability extensions.
## tags
-
## symbols
+1 -1
View File
@@ -2,7 +2,7 @@
dir: .claude/skills/sift-backlog
## role
Defines a workflow skill for triaging, organizing, and activating backlog tasks into actionable plans using a custom CLI tool.
Provides a workflow skill for triaging, organizing, and activating backlog tasks into actionable plans using a custom CLI tool.
## parent
index: .claude/skills/.pi-map.index.md
map: .claude/skills/.pi-map.md
+3 -3
View File
@@ -4,11 +4,11 @@ dir: .claude/skills/sift-backlog
index: .claude/skills/sift-backlog/.pi-map.index.md
## role
Defines a workflow skill for triaging, organizing, and activating backlog tasks into actionable plans using a custom CLI tool.
Provides a workflow skill for triaging, organizing, and activating backlog tasks into actionable plans using a custom CLI tool.
## files
- SKILL.md | Defines a workflow skill for triaging, organizing, and activating backlog tasks into actionable plans using a custom CLI tool. | dep: sf (custom CLI tool), task management system, plan management system
- SKILL.md | Defines a workflow skill for triaging, organizing, and activating backlog tasks into actionable plans using a custom CLI tool. | dep: sf CLI (task, plan, dependency, update subcommands)
## arch
Skill-based workflow automation following a structured triage pipeline (scan → filter → prioritize → activate) with CLI tool integration for backlog management.
Skill-based workflow automation using markdown-defined CLI tooling and structured task management patterns.
## tags
skill, defines, workflow, triaging, organizing, activating, backlog, tasks
## symbols
+1 -1
View File
@@ -2,7 +2,7 @@
dir: .opencode
## role
Configuration directory for the OpenCode AI coding assistant tool
This appears to be a configuration or metadata directory for the OpenCode AI coding assistant tool.
## parent
index: ./.pi-map.index.md
map: ./.pi-map.md
+2 -2
View File
@@ -4,10 +4,10 @@ dir: .opencode
index: .opencode/.pi-map.index.md
## role
Configuration directory for the OpenCode AI coding assistant tool
This appears to be a configuration or metadata directory for the OpenCode AI coding assistant tool.
## files
## arch
Tool-specific configuration storage using standard dot-directory convention with likely JSON/YAML config files and optional workspace state/logs
Hidden directory storing tool-specific state; no discernible software architecture as it contains no source files.
## tags
-
## symbols
+1 -1
View File
@@ -2,7 +2,7 @@
dir: .opencode/commands
## role
Defines structured workflow commands and AI assistant stances for an experimental OpenSpec-based development system that guides changes through exploration, proposal, implementation, and archival phases.
Defines AI assistant command workflows for an OpenSpec-based development system, covering task execution, change archival, exploratory analysis, and new change proposal generation.
## parent
index: .opencode/.pi-map.index.md
map: .opencode/.pi-map.md
+7 -7
View File
@@ -4,16 +4,16 @@ dir: .opencode/commands
index: .opencode/commands/.pi-map.index.md
## role
Defines structured workflow commands and AI assistant stances for an experimental OpenSpec-based development system that guides changes through exploration, proposal, implementation, and archival phases.
Defines AI assistant command workflows for an OpenSpec-based development system, covering task execution, change archival, exploratory analysis, and new change proposal generation.
## files
- opsx-apply.md | Defines an experimental workflow skill for implementing tasks from an OpenSpec change through a structured, interactive process with CLI integration and progress tracking. | dep: openspec CLI, AskUserQuestion tool, filesystem (for reading context files)
- opsx-archive.md | Defines a workflow for archiving completed changes in an experimental openspec-based development system | dep: openspec CLI, AskUserQuestion tool, Task tool, Skill tool, filesystem (mkdir, mv), JSON parsing
- opsx-explore.md | Defines the "explore mode" stance for an AI assistant - a thinking/discovery mode for investigating problems and clarifying requirements without implementing code | dep: OpenSpec system
- opsx-propose.md | Defines a workflow for proposing new changes in the openspec system by creating a change directory and generating all required artifacts (proposal.md, design.md, tasks.md) in dependency order | dep: openspec CLI, AskUserQuestion tool, TodoWrite tool, JSON parsing
- opsx-apply.md | Implement tasks from an OpenSpec change by selecting a change, reading context files, and executing pending tasks in a loop until completion or blocked. | dep: openspec CLI, AskUserQuestion tool, filesystem access
- opsx-archive.md | Defines a workflow for archiving completed changes in an experimental openspec-based development system, including validation, spec sync assessment, and user confirmation steps. | dep: openspec CLI, AskUserQuestion tool, Task tool, Skill tool, filesystem operations
- opsx-explore.md | Defines the "explore mode" stance and guidelines for an AI assistant to think through ideas, investigate problems, and clarify requirements without implementing code. | dep: OpenSpec system, ASCII diagrams, codebase investigation tools
- opsx-propose.md | Defines an AI assistant workflow for proposing new changes by creating a scaffolded change directory and generating all required artifacts (proposal.md, design.md, tasks.md) in dependency order using the openspec CLI tool. | dep: openspec CLI, AskUserQuestion tool, TodoWrite tool
## arch
Markdown-based declarative workflow definitions using a command pattern where each `.md` file specifies a distinct operational stance or phase (explore/propose/apply/archive) with embedded metadata, structured steps, and CLI integration points for progressive change lifecycle management.
Markdown-based command specifications using a structured workflow pattern with sequential steps, file I/O operations, CLI tool integration (openspec), and human-in-the-loop decision points for validation and confirmation.
## tags
opsx, defines, workflow, openspec, openspec cli, askuserquestion tool, explore, experimental
opsx, tasks, openspec, change, openspec cli, askuserquestion tool, defines, explore
## symbols
-
## workflows
+1 -1
View File
@@ -2,7 +2,7 @@
dir: .opencode/skills
## role
Contains reusable skill modules that provide specific capabilities or integrations for the OpenCode agent framework.
Contains custom AI agent skills or capabilities that extend the base functionality of the OpenCode framework with specialized tools and behaviors.
## parent
index: .opencode/.pi-map.index.md
map: .opencode/.pi-map.md
+2 -2
View File
@@ -4,10 +4,10 @@ dir: .opencode/skills
index: .opencode/skills/.pi-map.index.md
## role
Contains reusable skill modules that provide specific capabilities or integrations for the OpenCode agent framework.
Contains custom AI agent skills or capabilities that extend the base functionality of the OpenCode framework with specialized tools and behaviors.
## files
## arch
Modular plugin-based architecture where each skill is a self-contained unit implementing standardized interfaces for dynamic discovery and loading by the agent core.
Modular skill plugin architecture where individual capabilities are registered as discrete, composable units that can be dynamically loaded and invoked by the agent system.
## tags
-
## symbols
@@ -2,7 +2,7 @@
dir: .opencode/skills/openspec-apply-change
## role
Provides a spec-driven AI skill that implements tasks from OpenSpec changes by applying structured modifications to a codebase.
Provides an AI assistant skill that automates implementation of OpenSpec specification changes through a structured, CLI-driven workflow.
## parent
index: .opencode/skills/.pi-map.index.md
map: .opencode/skills/.pi-map.md
@@ -4,13 +4,13 @@ dir: .opencode/skills/openspec-apply-change
index: .opencode/skills/openspec-apply-change/.pi-map.index.md
## role
Provides a spec-driven AI skill that implements tasks from OpenSpec changes by applying structured modifications to a codebase.
Provides an AI assistant skill that automates implementation of OpenSpec specification changes through a structured, CLI-driven workflow.
## files
- SKILL.md | Defines an AI assistant skill for implementing tasks from an OpenSpec change using a spec-driven workflow | dep: openspec CLI, AskUserQuestion tool, filesystem access
- SKILL.md | Defines an AI assistant skill for implementing tasks from an OpenSpec change using a spec-driven workflow with the openspec CLI. | dep: openspec CLI, AskUserQuestion tool, filesystem access
## arch
Skill-based modular architecture using markdown-defined capabilities with a workflow-driven pattern that separates specification parsing from implementation execution.
Skill-based plugin architecture using markdown-defined capabilities with structured workflow steps (analyze, plan, implement, verify) and external CLI tool integration.
## tags
skill, defines, assistant, implementing, tasks, openspec, change, spec
skill, openspec, defines, assistant, implementing, tasks, change, spec
## symbols
-
## workflows
@@ -2,7 +2,7 @@
dir: .opencode/skills/openspec-archive-change
## role
Provides a skill definition for archiving completed changes in an experimental workflow using the openspec CLI.
Defines an automation skill for archiving completed changes in an experimental workflow using the openspec CLI.
## parent
index: .opencode/skills/.pi-map.index.md
map: .opencode/skills/.pi-map.md
@@ -4,13 +4,13 @@ dir: .opencode/skills/openspec-archive-change
index: .opencode/skills/openspec-archive-change/.pi-map.index.md
## role
Provides a skill definition for archiving completed changes in an experimental workflow using the openspec CLI.
Defines an automation skill for archiving completed changes in an experimental workflow using the openspec CLI.
## files
- SKILL.md | Defines a skill for archiving completed changes in an experimental workflow using the openspec CLI. | dep: openspec CLI, AskUserQuestion tool, Task tool, file system (mkdir, mv, read), JSON parsing
- SKILL.md | Defines a skill for archiving completed changes in an experimental workflow using the openspec CLI. | dep: openspec CLI, AskUserQuestion tool, Task tool, Skill tool, openspec-sync-specs skill
## arch
Single-file skill definition using markdown-based skill documentation pattern with CLI tool integration.
Skill-based plugin architecture following a declarative markdown skill definition pattern for CLI workflow automation.
## tags
skill, defines, archiving, completed, changes, experimental, workflow, openspec
skill, openspec, defines, archiving, completed, changes, experimental, workflow
## symbols
-
## workflows
@@ -2,7 +2,7 @@
dir: .opencode/skills/openspec-explore
## role
Defines a conversational AI skill that acts as a thinking partner for exploring ideas, investigating problems, and clarifying requirements without implementing code.
Provides a conversational exploration skill for OpenSpec CLI that enables non-implementation ideation, problem investigation, and requirements clarification.
## parent
index: .opencode/skills/.pi-map.index.md
map: .opencode/skills/.pi-map.md
+4 -4
View File
@@ -4,13 +4,13 @@ dir: .opencode/skills/openspec-explore
index: .opencode/skills/openspec-explore/.pi-map.index.md
## role
Defines a conversational AI skill that acts as a thinking partner for exploring ideas, investigating problems, and clarifying requirements without implementing code.
Provides a conversational exploration skill for OpenSpec CLI that enables non-implementation ideation, problem investigation, and requirements clarification.
## files
- SKILL.md | Defines a conversational AI skill/persona for "explore mode" - a thinking partner for exploring ideas, investigating problems, and clarifying requirements without implementing code. | dep: openspec CLI
- SKILL.md | Defines a conversational "explore mode" skill for OpenSpec CLI that acts as a thinking partner for exploring ideas, investigating problems, and clarifying requirements without implementing code. | dep: openspec CLI
## arch
Markdown-based skill definition using persona/role-prompting pattern with structured behavioral guidelines for non-implementation exploratory conversations.
Skill-based plugin architecture using markdown-defined behavioral specifications with conversational AI patterns, separating exploration/thinking workflows from implementation tasks.
## tags
skill, defines, conversational, persona, explore, mode, thinking, partner
skill, defines, conversational, explore, mode, openspec, cli, acts
## symbols
-
## workflows
@@ -2,7 +2,7 @@
dir: .opencode/skills/openspec-propose
## role
Defines a structured workflow skill for proposing new changes using the openspec CLI, generating proposal, design, and task artifacts in dependency order.
Automates the creation of structured software change proposals through a scaffolded directory workflow using the openspec CLI.
## parent
index: .opencode/skills/.pi-map.index.md
map: .opencode/skills/.pi-map.md
+4 -4
View File
@@ -4,13 +4,13 @@ dir: .opencode/skills/openspec-propose
index: .opencode/skills/openspec-propose/.pi-map.index.md
## role
Defines a structured workflow skill for proposing new changes using the openspec CLI, generating proposal, design, and task artifacts in dependency order.
Automates the creation of structured software change proposals through a scaffolded directory workflow using the openspec CLI.
## files
- SKILL.md | Defines a structured workflow for proposing new changes using the openspec CLI, generating proposal, design, and task artifacts in dependency order. | dep: openspec CLI, AskUserQuestion tool, TodoWrite tool
- SKILL.md | Defines an AI skill that automates proposing new software changes by scaffolding a directory structure and generating dependent artifacts (proposal, design, tasks) using the openspec CLI workflow. | dep: openspec CLI, AskUserQuestion tool, TodoWrite tool
## arch
Template-based skill definition using markdown documentation with structured workflow patterns for CLI-driven artifact generation and dependency-ordered execution.
Template-driven skill architecture following a sequential artifact generation pattern (proposal → design → tasks) with CLI-based workflow automation.
## tags
skill, defines, structured, workflow, proposing, new, changes, openspec
skill, defines, automates, proposing, new, software, changes, scaffolding
## symbols
-
## workflows
+7 -4
View File
@@ -16,7 +16,7 @@ dir: .
Trust boundary: index routes, map orients, source decides.
## role
Infrastructure and deployment configuration for a self-hosted project management platform with OAuth2 authentication, providing Docker Compose orchestration, environment templates, and development tooling.
Package .
## parent
-
## children
@@ -47,9 +47,6 @@ Infrastructure and deployment configuration for a self-hosted project management
- e2e
index: e2e/.pi-map.index.md
map: e2e/.pi-map.md
- minerv3
index: minerv3/.pi-map.index.md
map: minerv3/.pi-map.md
- openspec
index: openspec/.pi-map.index.md
map: openspec/.pi-map.md
@@ -59,6 +56,9 @@ Infrastructure and deployment configuration for a self-hosted project management
- tool-images
index: tool-images/.pi-map.index.md
map: tool-images/.pi-map.md
- uploads
index: uploads/.pi-map.index.md
map: uploads/.pi-map.md
## files
- .env.example
- .gitignore
@@ -70,6 +70,9 @@ Infrastructure and deployment configuration for a self-hosted project management
- docker-compose.yml
- progress.md
- swap-pane
- ui-audit-spacing-typography.md
- ui-rework-foundations-apply.md
- ui-rework-pass2-apply.md
## links
index: ./.pi-map.index.md
map: ./.pi-map.md
+16 -13
View File
@@ -18,22 +18,25 @@ index: ./.pi-map.index.md
Trust boundary: index routes, map orients, source decides.
## role
Infrastructure and deployment configuration for a self-hosted project management platform with OAuth2 authentication, providing Docker Compose orchestration, environment templates, and development tooling.
Package .
## files
- .env.example | Provides a template of environment variables for configuring a Headquarter application with PostgreSQL, Redis, Authentik SSO, and Docker/Traefik deployment
- .gitignore | Specifies files and directories for Git to ignore across a multi-language project with Python, Node, and custom tooling | dep: Git
- AGENTS.md | Defines operational rules, workflows, and constraints for AI agents working within an OpenSpec-driven software development project. | dep: OpenSpec, superpowers, git, docker compose, conventional commits
- CHANGELOG.md | Documents version history and notable changes for a Git-based project management web application
- Makefile | Provides standard development commands for containerized web application lifecycle management via Docker Compose | dep: docker compose, alembic, pytest, ruff, mypy, playwright, npm, postgres, redis
- README.md | A self-hosted platform for managing projects, git repositories, and development tools with OAuth2 authentication. | dep: FastAPI, SQLAlchemy, Pydantic, Alembic, python-jose, React, TypeScript, Vite, React Router, Docker, PostgreSQL, Traefik, Authentik, Git
- docker-compose.traefik.yml | Deploys a multi-service web application (frontend, API, PostgreSQL, Redis) behind an existing Traefik reverse proxy with TLS termination and environment-configurable domains. | dep: docker, traefik, postgres, redis, authentik, docker-compose
- docker-compose.yml | Defines a multi-service Docker Compose stack with PostgreSQL, Redis, API backend, and web frontend services for a "headquarter" application | dep: Docker, PostgreSQL, Redis, Vite, asyncpg, nginx
- progress.md | Tracks completed and remaining tasks for a backend-frontend code refactoring project organized in 7 phases
- swap-pane | Empty file with no functionality
- .env.example | Provides example environment variable configuration for a full-stack application with database, caching, authentication, and deployment settings
- .gitignore | Specifies files and directories for Git to ignore across a project using Beads/Dolt, Python, Node, and various IDE/OS tooling. | dep: git, dolt, beads, python, node, npm, yarn, pnpm, pytest, mypy, ruff, coverage
- AGENTS.md | Defines operational guidelines and workflows for AI agents collaborating on a software project governed by OpenSpec | dep: OpenSpec, superpowers (brainstorming, writing-plans, test-driven-development, systematic-debugging, verification-before-completion, using-git-worktrees, dispatching-parallel-agents), git
- CHANGELOG.md | Documents version history and notable changes for a project management and Git repository application
- Makefile | Provides standardized development commands for managing a Docker-based full-stack application with API, web frontend, database, and testing infrastructure | dep: docker compose, alembic, pytest, ruff, mypy, playwright, npm, postgres, redis
- README.md | Documentation for a self-hosted development platform that manages projects, git repositories, and development tools with OAuth2 authentication | dep: FastAPI, SQLAlchemy, Pydantic, Alembic, python-jose, React, TypeScript, Vite, React Router, Docker, PostgreSQL, Traefik, Authentik
- docker-compose.traefik.yml | Deploys a multi-service application (PostgreSQL, Redis, web frontend, API) behind an existing Traefik reverse proxy with TLS termination | dep: docker, docker-compose, traefik, postgres, redis, node/vite, python/fastapi
- docker-compose.yml | Defines a multi-service Docker Compose stack for a web application with PostgreSQL, Redis, API backend, and web frontend services | dep: Docker, Docker Compose, PostgreSQL, Redis, Vite, asyncpg, Python/FastAPI (implied), Node.js (implied)
- progress.md | Documents the progress and remaining tasks for a backend-frontend refactoring project involving modularization, code reorganization, and verification.
- swap-pane | Provides a command to swap the position of two tmux panes within a window or between windows | dep: tmux, client, window, layout, cmd-find, cmd-parse, options
- ui-audit-spacing-typography.md | A detailed audit report identifying critical CSS styling gaps, mobile layout failures, and design system inconsistencies in a web application's UI components and stylesheets. | dep: React/TSX components, CSS stylesheets (global.css, utilities.css, tokens.css, page-specific CSS), JSX/TSX files in apps/web/src/components and apps/web/src/pages
- ui-rework-foundations-apply.md | Documents the implementation of Pass 1 (Foundations) of a web UI spacing/typography/visual-rhythm rework, including design token expansion, primitive CSS class additions, component refactors, and OpenSpec documentation. | dep: CSS design tokens, React/TSX components, OpenSpec documentation system, Git/SSH, npm build toolchain
- ui-rework-pass2-apply.md | Documents the implementation and verification of Pass 2 of a web UI spacing/typography rework, refactoring inline styles into utility classes and design tokens across six components. | dep: CSS custom properties, BEM methodology, utility-first CSS, React/TSX components, OpenSpec documentation system
## arch
Containerized microservices architecture using Docker Compose with PostgreSQL/Redis data layer, Traefik reverse proxy for TLS termination, environment-based configuration pattern, and Makefile-driven development workflow.
Contains 13 files.
## tags
docker, redis, git, application, postgresql, compose, traefik, project
docker, web, git, application, ui, rework, python, redis
## symbols
-
## workflows
+1 -1
View File
@@ -2,7 +2,7 @@
dir: .stoneforge
## role
Hidden system directory storing runtime state, configuration, and version-control metadata for the Stoneforge application.
Infrastructure configuration and metadata storage for the Stoneforge tool
## parent
index: ./.pi-map.index.md
map: ./.pi-map.md
+6 -6
View File
@@ -4,15 +4,15 @@ dir: .stoneforge
index: .stoneforge/.pi-map.index.md
## role
Hidden system directory storing runtime state, configuration, and version-control metadata for the Stoneforge application.
Infrastructure configuration and metadata storage for the Stoneforge tool
## files
- .dashboard-opened | Stores timestamp and identifier data for tracking when a dashboard was opened
- .gitignore | Specifies files and patterns for Git to ignore in version control
- config.yaml | Configuration file for the Stoneforge application defining database, sync, playbook, identity, merge, workflow, and agent settings.
- .dashboard-opened | Stores timestamp metadata for tracking when a dashboard was opened
- .gitignore | Specifies files and patterns for Git to ignore in the repository | dep: git
- config.yaml | YAML configuration file defining settings for the Stoneforge tool, including database, sync, playbooks, identity, merge, workflow, and agent permissions.
## arch
Simple flat-file storage with YAML-based configuration, dotfile-based state tracking, and standard Git ignore patterns.
Dot-directory pattern for tool-specific state, using YAML for hierarchical configuration and dotfiles for ephemeral runtime metadata
## tags
config, stores, timestamp, identifier, data, tracking, dashboard, was
git, config, stores, timestamp, metadata, tracking, dashboard, was
## symbols
-
## workflows
+1 -1
View File
@@ -2,7 +2,7 @@
dir: .stoneforge/sync
## role
Distributed task coordination and ephemeral worker agent management for a multi-agent execution system.
Provides persistent storage for entity relationships and AI worker agent records in a synchronization system.
## parent
index: .stoneforge/.pi-map.index.md
map: .stoneforge/.pi-map.md
+5 -5
View File
@@ -4,14 +4,14 @@ dir: .stoneforge/sync
index: .stoneforge/sync/.pi-map.index.md
## role
Distributed task coordination and ephemeral worker agent management for a multi-agent execution system.
Provides persistent storage for entity relationships and AI worker agent records in a synchronization system.
## files
- dependencies.jsonl | Stores a sequence of dependency relationships between entities in JSON Lines format, tracking parent-child, blocking, and reply relationships with timestamps and creators.
- elements.jsonl | Stores JSONL records of ephemeral worker agents with their session history, worktree assignments, and lifecycle metadata for a distributed task execution system.
- dependencies.jsonl | Stores dependency relationships between entities in JSONL format, including parent-child, blocking, and reply-to relationships with timestamps and creators.
- elements.jsonl | Stores JSONL-formatted entity records for AI worker agents with their session histories and metadata in a data persistence file.
## arch
Event-sourced JSONL append-only logs with entity-relationship graph tracking, session-based worker lifecycle management, and decentralized worktree assignment.
Append-only JSONL document store with separate files for dependency graph edges (dependencies.jsonl) and entity nodes (elements.jsonl), using a log-structured data pattern for time-ordered, immutable record keeping.
## tags
stores, relationships, dependencies, elements, sequence, dependency, entities, json
stores, relationships, jsonl, dependencies, elements, dependency, entities, format
## symbols
-
## workflows
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps
## role
Contains the main application entry points and executable modules for the project.
Package apps
## parent
index: ./.pi-map.index.md
map: ./.pi-map.md
+2 -2
View File
@@ -4,10 +4,10 @@ dir: apps
index: apps/.pi-map.index.md
## role
Contains the main application entry points and executable modules for the project.
Package apps
## files
## arch
Follows a modular application structure with separate directories for each deployable application, typically using framework-specific patterns (e.g., MVC, MVVM, or component-based architecture depending on the stack).
Contains 0 files.
## tags
-
## symbols
+13 -4
View File
@@ -2,23 +2,32 @@
dir: apps/api
## role
Backend API service that provides a self-hosted FastAPI server for managing development projects, git repositories, and containerized tools via Docker.
Backend API server for the Headquarter platform, providing self-hosted project management, git repository management, and development tool orchestration services.
## parent
index: apps/.pi-map.index.md
map: apps/.pi-map.md
## children
- apps/api/.mypy_cache
index: apps/api/.mypy_cache/.pi-map.index.md
map: apps/api/.mypy_cache/.pi-map.md
- apps/api/.pi-lens
index: apps/api/.pi-lens/.pi-map.index.md
map: apps/api/.pi-lens/.pi-map.md
- apps/api/.pytest_cache
index: apps/api/.pytest_cache/.pi-map.index.md
map: apps/api/.pytest_cache/.pi-map.md
- apps/api/.venv-test
index: apps/api/.venv-test/.pi-map.index.md
map: apps/api/.venv-test/.pi-map.md
- apps/api/.ruff_cache
index: apps/api/.ruff_cache/.pi-map.index.md
map: apps/api/.ruff_cache/.pi-map.md
- apps/api/alembic
index: apps/api/alembic/.pi-map.index.md
map: apps/api/alembic/.pi-map.md
- apps/api/app
index: apps/api/app/.pi-map.index.md
map: apps/api/app/.pi-map.md
- apps/api/headquarter_api.egg-info
index: apps/api/headquarter_api.egg-info/.pi-map.index.md
map: apps/api/headquarter_api.egg-info/.pi-map.md
- apps/api/src
index: apps/api/src/.pi-map.index.md
map: apps/api/src/.pi-map.md
+10 -10
View File
@@ -4,19 +4,19 @@ dir: apps/api
index: apps/api/.pi-map.index.md
## role
Backend API service that provides a self-hosted FastAPI server for managing development projects, git repositories, and containerized tools via Docker.
Backend API server for the Headquarter platform, providing self-hosted project management, git repository management, and development tool orchestration services.
## files
- .dockerignore | Specifies files and directories to exclude from Docker build context to reduce image size and avoid copying unnecessary files into containers. | dep: Docker
- Dockerfile | Multi-stage Docker build for a Python application with Docker socket access, Cloudflare tunneling, and database dependency waiting | dep: python:3.11-slim, gcc, libpq-dev, docker-ce-cli, docker-compose-plugin, cloudflared, uvicorn, pyproject.toml dependencies
- README.md | Documentation for a self-hosted FastAPI backend API that manages projects, git repositories, and development tools via Docker instances. | dep: FastAPI, SQLAlchemy, PostgreSQL, asyncpg, Alembic, Docker, Docker Compose, Authentik, uvicorn, pytest, ruff, mypy
- alembic.ini | Configuration file for Alembic database migration tool connecting to a PostgreSQL database with async driver | dep: alembic, sqlalchemy, asyncpg, PostgreSQL
- pyproject.toml | Defines Python project metadata, dependencies, and tool configurations for a FastAPI-based backend API called "headquarter-api" | dep: fastapi, uvicorn, sqlalchemy, asyncpg, alembic, pydantic, pydantic-settings, python-multipart, httpx, structlog, cryptography, pytest, pytest-asyncio, mypy, ruff, aiosqlite
- uv.lock | Lock file for the uv Python package manager that pins exact dependency versions and their artifact hashes for reproducible installations | dep: uv, Python 3.11+, aiosqlite, alembic, annotated-doc, annotated-types, anyio, ast-serialize, asyncpg, and many other PyPI packages
- wait-for-db.sh | Wait for a PostgreSQL database to become available before executing a command, with configurable retry logic. | dep: nc (netcat), sh (POSIX shell), sleep
- .dockerignore | Specifies files and directories to exclude from the Docker build context to optimize image build times and prevent sensitive or unnecessary files from being included.
- Dockerfile | Multi-stage Dockerfile that builds and runs a Python application with Docker CLI access, cloudflared, and database readiness checks. | dep: python:3.11-slim, libpq5, git, openssh-client, docker-ce-cli, docker-compose-plugin, cloudflared, uvicorn, netcat-openbsd
- README.md | Provides comprehensive documentation for the Headquarter API, a self-hosted platform for managing projects, git repositories, and development tools. | dep: FastAPI, SQLAlchemy, PostgreSQL, asyncpg, Alembic, Docker, Authentik, Pydantic, Ruff, mypy, pytest
- alembic.ini | Configuration file for Alembic database migration tool, defining database connection and logging settings. | dep: alembic, sqlalchemy, asyncpg, postgresql
- pyproject.toml | Defines project metadata, dependencies, and tool configuration for the Headquarter platform API. | dep: fastapi, uvicorn, sqlalchemy, asyncpg, alembic, pydantic, pydantic-settings, httpx, structlog, cryptography, pytest, mypy, ruff
- uv.lock | This file is a UV lockfile that pins exact versions, hashes, and metadata for all Python project dependencies to ensure reproducible environments. | dep: uv, aiosqlite, alembic, annotated-types, anyio, asyncpg, sqlalchemy, mako
- wait-for-db.sh | Polls a PostgreSQL host/port until it is available or a retry limit is reached, then executes the passed command. | dep: nc, sleep
## arch
Modern Python async architecture using FastAPI with SQLAlchemy/alembic for PostgreSQL, uv for dependency management, multi-stage Docker builds with Cloudflare tunneling, and health-check orchestration for database readiness.
Modern Python async API using FastAPI/Starlette with SQLAlchemy ORM, Alembic migrations, multi-stage Docker containerization with cloudflared tunneling, UV package management, and PostgreSQL database with health-checked startup orchestration.
## tags
docker, alembic, python, database, fastapi, postgresql, asyncpg, uvicorn
alembic, docker, sqlalchemy, asyncpg, dockerfile, database, postgresql, pydantic
## symbols
-
## workflows
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/api/alembic
## role
Database migration tooling that manages schema versioning and changes for the API's PostgreSQL database using Alembic with async SQLAlchemy.
Database migration tooling for the API service, enabling version-controlled schema changes with async SQLAlchemy support.
## parent
index: apps/api/.pi-map.index.md
map: apps/api/.pi-map.md
+13 -6
View File
@@ -4,16 +4,23 @@ dir: apps/api/alembic
index: apps/api/alembic/.pi-map.index.md
## role
Database migration tooling that manages schema versioning and changes for the API's PostgreSQL database using Alembic with async SQLAlchemy.
Database migration tooling for the API service, enabling version-controlled schema changes with async SQLAlchemy support.
## files
- env.py | Configures Alembic database migration environment with async SQLAlchemy support for a project. | dep: alembic, sqlalchemy, sqlalchemy.ext.asyncio, logging.config, asyncio, src.config, src.models
- script.py.mako | Alembic database migration script template that generates upgrade/downgrade functions for SQLAlchemy schema migrations | dep: alembic, sqlalchemy
- env.py | Configures Alembic database migration environment with async SQLAlchemy support. | exp: func:run_migrations_offline() → None, call:context.configure, call:context.begin_transaction, call:context.run_migrations, func:do_run_migrations(connection: Connection) → None, call:context.configure, call:context.begin_transaction, call:context.run_migrations, func:run_async_migrations() → None, call:async_engine_from_config, call:config.get_section, call:connectable.connect, call:connection.run_sync, call:connectable.dispose, func:run_migrations_online() → None, call:asyncio.run, call:run_async_migrations | dep: logging.config, alembic, sqlalchemy, sqlalchemy.engine, sqlalchemy.ext.asyncio, src.config, src.models, asyncio
- script.py.mako | Alembic database migration script template that generates Python migration files for SQLAlchemy database schema changes | dep: alembic, sqlalchemy, mako
## arch
Template-driven migration pattern using Alembic's declarative configuration with async engine setup, environment context management, and auto-generated revision scripts with upgrade/downgrade function pairs.
Template-based migration generation using Alembic's standard env.py configuration pattern with async SQLAlchemy engine integration and Mako templating for migration script scaffolding.
## tags
sqlalchemy, alembic, database, migration, asyncio, config, src, env
migrations, run, sqlalchemy, async, alembic, call:context.configure, call:context.begin, transaction
## symbols
-
- run_migrations_offline
- do_run_migrations
- run_async_migrations
- run_migrations_online
- call:context.configure
- call:context.begin_transaction
- call:context.run_migrations
- call:async_engine_from_config
## workflows
- change alembic behavior
read: env.py, script.py.mako
+13 -2
View File
@@ -2,12 +2,14 @@
dir: apps/api/alembic/versions
## role
Database schema evolution management for the API application, tracking incremental changes to tables supporting users, authentication, projects, git repositories, tool types/instances, config profiles, workspaces, monitoring, and notifications.
Database schema versioning and incremental migration management for the API's relational data model using Alembic.
## parent
index: apps/api/alembic/.pi-map.index.md
map: apps/api/alembic/.pi-map.md
## children
-
- apps/api/alembic/versions/.ruff_cache
index: apps/api/alembic/versions/.ruff_cache/.pi-map.index.md
map: apps/api/alembic/versions/.ruff_cache/.pi-map.md
## files
- 0001_initial_schema.py
- 0002_refresh_tokens.py
@@ -47,12 +49,19 @@ map: apps/api/alembic/.pi-map.md
- 2026_05_29_remove_lsio_command_override.py
- 2026_05_29_remove_ssh_keys_mount_from_manifest.py
- 2026_06_01_add_workspaces.py
- 2026_06_13_make_clone_mode_nullable.py
- 2026_06_14_104415_add_tool_type_home_directory.py
- 2026_06_14_182955_fix_pi_agent_home_directory_mount.py
- 2026_06_15_090500_remove_pi_agent_explicit_repo_mount.py
- 2026_06_19_113000_remove_pi_agent_workspace_symlink.py
- 398082499c30_add_tool_config_fields.py
- 6fc7bfcf199f_merge_remove_is_builtin_and_add_config_.py
- 86cec91fdb00_merge_profile_resolver_and_workspaces_.py
- 8c6d1dbd4798_remove_pi_config_and_state_mounts_from_.py
- 8ed7dd80973d_create_config_folders_table.py
- af8512103d67_add_tool_type_fields.py
- f3d2dc90ba3a_merge_single_interface_and_clone_mode.py
- fc8f1a20cbf6_merge_home_directory_and_pi_agent_mount_.py
## links
index: apps/api/alembic/versions/.pi-map.index.md
map: apps/api/alembic/versions/.pi-map.md
@@ -63,5 +72,7 @@ map: apps/api/alembic/versions/.pi-map.md
read: 2026_05_24_220141_add_startup_command.py, 2026_05_29_remove_lsio_command_override.py
- change versions config
read: 0003_user_configs.py, 0009_tool_configs.py, 0013_add_config_profiles.py
- explore versions subdirectories
index: apps/api/alembic/versions/.ruff_cache/.pi-map.index.md
## dirty
-
+51 -42
View File
@@ -4,56 +4,63 @@ dir: apps/api/alembic/versions
index: apps/api/alembic/versions/.pi-map.index.md
## role
Database schema evolution management for the API application, tracking incremental changes to tables supporting users, authentication, projects, git repositories, tool types/instances, config profiles, workspaces, monitoring, and notifications.
Database schema versioning and incremental migration management for the API's relational data model using Alembic.
## files
- 0001_initial_schema.py | Defines the initial database schema migration creating five tables (users, ssh_keys, projects, git_repositories, user_configs) with relationships, indexes, and constraints using Alembic. | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:sa.String, call:postgresql.UUID, call:sa.DateTime, call:sa.func.now, call:sa.PrimaryKeyConstraint, call:sa.UniqueConstraint, call:op.create_index, call:op.f, call:sa.Text, call:sa.ForeignKeyConstraint, call:sa.Boolean, call:postgresql.JSONB, func:downgrade() → None, call:op.drop_table, call:op.drop_index, call:op.f | dep: alembic, sqlalchemy.dialects, sqlalchemy, postgresql dialect
- 0002_refresh_tokens.py | Alembic database migration that creates a refresh_tokens table with indexes for user authentication token management | exp: func:upgrade() → None, call:op.get_bind, call:sa.inspect, call:inspector.has_table, call:op.create_table, call:sa.Column, call:postgresql.UUID, call:sa.String, call:sa.DateTime, call:sa.ForeignKeyConstraint, call:sa.PrimaryKeyConstraint, call:sa.UniqueConstraint, call:inspector.get_indexes, call:op.f, call:op.create_index, func:downgrade() → None, call:op.get_bind, call:sa.inspect, call:inspector.has_table, call:inspector.get_indexes, call:op.f, call:op.drop_index, call:op.drop_table | dep: alembic, sqlalchemy.dialects, sqlalchemy, sqlalchemy.dialects.postgresql
- 0003_user_configs.py | Alembic database migration that creates a user_configs table with JSON configuration storage linked to users | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:sa.UUID, call:sa.JSON, call:sa.DateTime, call:sa.text, call:sa.ForeignKeyConstraint, call:sa.PrimaryKeyConstraint, call:sa.UniqueConstraint, func:downgrade() → None, call:op.drop_table | dep: typing, alembic, sqlalchemy
- 0004_tool_types.py | Alembic database migration that creates a tool_types table with metadata, templates, and versioning columns for a tool management system. | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:sa.Uuid, call:sa.String, call:sa.Text, call:sa.JSON, call:sa.Boolean, call:sa.ForeignKey, call:sa.DateTime, call:sa.text, func:downgrade() → None, call:op.drop_table | dep: typing, alembic, sqlalchemy
- 0001_initial_schema.py | Alembic database migration that creates the initial schema with five tables (users, ssh_keys, projects, git_repositories, user_configs) for a Git/SSH management application. | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:sa.String, call:postgresql.UUID, call:sa.DateTime, call:sa.func.now, call:sa.PrimaryKeyConstraint, call:sa.UniqueConstraint, call:op.create_index, call:op.f, call:sa.Text, call:sa.ForeignKeyConstraint, call:sa.Boolean, call:postgresql.JSONB, func:downgrade() → None, call:op.drop_table, call:op.drop_index, call:op.f | dep: alembic, sqlalchemy.dialects, sqlalchemy, sqlalchemy.dialects.postgresql
- 0002_refresh_tokens.py | Alembic database migration that creates a refresh_tokens table with indexes for secure session management | exp: func:upgrade() → None, call:op.get_bind, call:sa.inspect, call:inspector.has_table, call:op.create_table, call:sa.Column, call:postgresql.UUID, call:sa.String, call:sa.DateTime, call:sa.ForeignKeyConstraint, call:sa.PrimaryKeyConstraint, call:sa.UniqueConstraint, call:inspector.get_indexes, call:op.f, call:op.create_index, func:downgrade() → None, call:op.get_bind, call:sa.inspect, call:inspector.has_table, call:inspector.get_indexes, call:op.f, call:op.drop_index, call:op.drop_table | dep: alembic, sqlalchemy.dialects, sqlalchemy, sqlalchemy.dialects.postgresql
- 0003_user_configs.py | Alembic database migration that creates a user_configs table with JSON configuration storage per user | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:sa.UUID, call:sa.JSON, call:sa.DateTime, call:sa.text, call:sa.ForeignKeyConstraint, call:sa.PrimaryKeyConstraint, call:sa.UniqueConstraint, func:downgrade() → None, call:op.drop_table | dep: typing, alembic, sqlalchemy
- 0004_tool_types.py | Alembic database migration that creates a tool_types table with metadata, templates, and user tracking fields | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:sa.Uuid, call:sa.String, call:sa.Text, call:sa.JSON, call:sa.Boolean, call:sa.ForeignKey, call:sa.DateTime, call:sa.text, func:downgrade() → None, call:op.drop_table | dep: typing, alembic, sqlalchemy
- 0005_ssh_keys_timestamps.py | Alembic database migration that adds created_at and updated_at timestamp columns to the ssh_keys table | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.DateTime, call:sa.text, func:downgrade() → None, call:op.drop_column | dep: typing, alembic, sqlalchemy
- 0006_tool_instances.py | Alembic database migration that creates a tool_instances table with columns for tracking deployed tool instances, their status, container info, and foreign key relationships to tool_types, git_repositories, projects, and users. | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:postgresql.UUID, call:sa.text, call:sa.String, call:sa.Integer, call:sa.DateTime, call:sa.ForeignKeyConstraint, call:sa.PrimaryKeyConstraint, call:op.create_index, func:downgrade() → None, call:op.drop_index, call:op.drop_table | dep: typing, alembic, sqlalchemy.dialects, sqlalchemy, sqlalchemy.dialects.postgresql
- 0006_tool_instances.py | Alembic database migration that creates a tool_instances table with foreign key relationships to tool_types, git_repositories, projects, and users tables, plus indexes for common query patterns. | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:postgresql.UUID, call:sa.text, call:sa.String, call:sa.Integer, call:sa.DateTime, call:sa.ForeignKeyConstraint, call:sa.PrimaryKeyConstraint, call:op.create_index, func:downgrade() → None, call:op.drop_index, call:op.drop_table | dep: typing, alembic, sqlalchemy.dialects, sqlalchemy, sqlalchemy.dialects.postgresql
- 0007_instance_container_name.py | Alembic database migration that adds a nullable container_name column to the tool_instances table | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.String, func:downgrade() → None, call:op.drop_column | dep: typing, alembic, sqlalchemy
- 0008_tool_type_category.py | Alembic database migration that adds `category` and `interfaces` columns to the `tool_types` table | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.String, call:sa.JSON, func:downgrade() → None, call:op.drop_column | dep: typing, alembic, sqlalchemy
- 0009_tool_configs.py | Alembic database migration that creates a tool_configs table with UUID keys, foreign key relationships, and indexes for storing user/project tool configuration settings. | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:postgresql.UUID, call:sa.text, call:sa.String, call:sa.Text, call:sa.DateTime, call:sa.ForeignKeyConstraint, call:sa.PrimaryKeyConstraint, call:op.create_index, func:downgrade() → None, call:op.drop_index, call:op.drop_table | dep: typing, alembic, sqlalchemy.dialects, sqlalchemy, postgresql dialect
- 0010_tool_type_default_port.py | Alembic database migration that adds a nullable default_port column to the tool_types table. | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.Integer, func:downgrade() → None, call:op.drop_column | dep: typing, alembic, sqlalchemy
- 0011_tool_instance_tunnel_fields.py | Alembic database migration that adds tunnel-related fields (public_url and tunnel_id) to the tool_instances table | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.String, func:downgrade() → None, call:op.drop_column | dep: typing, alembic, sqlalchemy
- 0012_default_port_req.py | Alembic database migration that populates null default_port values for existing tool types and then makes the column non-nullable | exp: func:upgrade() → None, call:op.execute, call:op.alter_column, call:sa.Integer, func:downgrade() → None, call:op.alter_column, call:sa.Integer | dep: typing, alembic, sqlalchemy
- 0013_add_config_profiles.py | Alembic database migration that adds config profiles, includes, mounts tables and links tool instances to profiles with defensive idempotent checks | exp: func:_table_exists(table_name: str) → bool, call:sa.inspect(op.get_bind()).has_table, call:op.get_bind, func:_column_exists(table_name: str, column_name: str) → bool, call:_table_exists, call:sa.inspect(op.get_bind()).get_columns, call:op.get_bind, func:_index_exists(table_name: str, index_name: str) → bool, call:_table_exists, call:sa.inspect(op.get_bind()).get_indexes, call:op.get_bind, func:_foreign_key_exists(table_name: str, constrained_columns: list[str], referred_table: str) → bool, call:_table_exists, call:sa.inspect(op.get_bind()).get_foreign_keys, call:op.get_bind, call:foreign_key.get, func:upgrade() → None, call:_table_exists, call:op.create_table, call:sa.Column, call:postgresql.UUID, call:sa.String, call:sa.Text, call:sa.DateTime, call:sa.text, call:sa.ForeignKeyConstraint, call:sa.PrimaryKeyConstraint, call:sa.UniqueConstraint, call:_index_exists, call:op.create_index, call:sa.Integer, call:_column_exists, call:op.add_column, call:_foreign_key_exists, call:op.create_foreign_key, func:downgrade() → None, call:op.drop_index, call:op.drop_constraint, call:op.drop_column, call:op.drop_table | dep: collections.abc, alembic, sqlalchemy.dialects, sqlalchemy, sqlalchemy.dialects.postgresql
- 0013_add_probe_result.py | Alembic database migration that adds a JSON probe_result column to the tool_instances table | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, func:downgrade() → None, call:op.drop_column | dep: typing, alembic, sqlalchemy.dialects, sqlalchemy, sqlalchemy.dialects.postgresql
- 0014_add_profile_resolver_fields.py | Alembic database migration that adds profile resolver fields (project_id, tool_type_id, environment_variables, etc.) to config_profiles table and restructures config_mounts table (renaming mount_path to target_path, adding mode/files, removing content/source_profile_id). | exp: func:_table_exists(table_name: str) → bool, call:sa.inspect(op.get_bind()).has_table, call:op.get_bind, func:_column_exists(table_name: str, column_name: str) → bool, call:_table_exists, call:sa.inspect(op.get_bind()).get_columns, call:op.get_bind, func:_index_exists(table_name: str, index_name: str) → bool, call:_table_exists, call:sa.inspect(op.get_bind()).get_indexes, call:op.get_bind, func:_foreign_key_exists(table_name: str, constrained_columns: list[str], referred_table: str) → bool, call:_table_exists, call:sa.inspect(op.get_bind()).get_foreign_keys, call:op.get_bind, call:foreign_key.get, func:_foreign_key_names_for_column(table_name: str, column_name: str) → list[str], call:_table_exists, call:sa.inspect(op.get_bind()).get_foreign_keys, call:op.get_bind, call:foreign_key.get, call:names.append, func:upgrade() → None, call:_column_exists, call:op.add_column, call:sa.Column, call:postgresql.UUID, call:sa.JSON, call:sa.Text, call:sa.Integer, call:sa.Boolean, call:_foreign_key_exists, call:op.create_foreign_key, call:_index_exists, call:op.create_index, call:op.alter_column, call:sa.String, call:_foreign_key_names_for_column, call:op.drop_constraint, call:op.drop_column, func:downgrade() → None, call:op.add_column, call:sa.Column, call:postgresql.UUID, call:sa.Text, call:op.drop_column, call:op.alter_column, call:op.drop_index, call:op.drop_constraint | dep: collections.abc, alembic, sqlalchemy.dialects, sqlalchemy, sqlalchemy.dialects.postgresql
- 0014_merge_heads.py | Alembic merge migration that reconciles two divergent migration branches into a single history line | exp: func:upgrade() → None, func:downgrade() → None | dep: typing, alembic
- 0015_single_interface.py | Alembic database migration that replaces a JSON array `interfaces` column with `interface_type` string and `requires_port` boolean columns in the `tool_types` table, with dialect-specific data migration for PostgreSQL and SQLite. | exp: func:_get_dialect() → str, call:op.get_bind, func:upgrade() → None, call:_get_dialect, call:op.add_column, call:sa.Column, call:sa.String, call:sa.Boolean, call:op.execute, call:op.alter_column, call:op.drop_column, call:op.create_check_constraint, call:sa.text, func:downgrade() → None, call:_get_dialect, call:op.drop_constraint, call:op.add_column, call:sa.Column, call:postgresql.JSONB, call:sa.Text, call:op.execute, call:sa.JSON, call:op.drop_column | dep: typing, alembic, sqlalchemy.dialects, sqlalchemy, postgresql (dialect)
- 069d3da4dc9b_add_ssh_key_id_to_config_profiles.py | Alembic database migration that adds a nullable UUID foreign key column `ssh_key_id` to the `config_profiles` table referencing `ssh_keys.id` with SET NULL on delete | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.Uuid, call:sa.ForeignKey, func:downgrade() → None, call:op.drop_column | dep: alembic, sqlalchemy
- 20260527160017_add_pi_agent_tool_type.py | Alembic database migration that adds a "pi-agent" terminal-based coding tool type to a tool_types table with Docker configuration templates | exp: func:upgrade() → None, call:op.get_bind, call:conn.execute( sa.text("SELECT id FROM tool_types WHERE name = 'pi-agent'") ).fetchone, call:sa.text, call:json.dumps, func:downgrade() → None, call:op.get_bind, call:conn.execute, call:sa.text | dep: json, typing, alembic, uuid, sqlalchemy
- 0009_tool_configs.py | Alembic database migration that creates a tool_configs table with UUID primary keys, foreign keys to users/tool_types/projects, key-value configuration storage, and supporting indexes. | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:postgresql.UUID, call:sa.text, call:sa.String, call:sa.Text, call:sa.DateTime, call:sa.ForeignKeyConstraint, call:sa.PrimaryKeyConstraint, call:op.create_index, func:downgrade() → None, call:op.drop_index, call:op.drop_table | dep: typing, alembic, sqlalchemy.dialects, sqlalchemy, sqlalchemy.dialects.postgresql
- 0010_tool_type_default_port.py | Alembic database migration that adds a nullable default_port column to the tool_types table | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.Integer, func:downgrade() → None, call:op.drop_column | dep: typing, alembic, sqlalchemy
- 0011_tool_instance_tunnel_fields.py | Alembic database migration that adds tunnel-related fields (public_url and tunnel_id) to the tool_instances table. | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.String, func:downgrade() → None, call:op.drop_column | dep: typing, alembic, sqlalchemy
- 0012_default_port_req.py | Alembic database migration that populates default_port values for existing tool types and makes the column non-nullable | exp: func:upgrade() → None, call:op.execute, call:op.alter_column, call:sa.Integer, func:downgrade() → None, call:op.alter_column, call:sa.Integer | dep: typing, alembic, sqlalchemy
- 0013_add_config_profiles.py | Alembic database migration that adds config_profiles, config_includes, config_mounts tables and a selected_profile_id column to tool_instances with defensive checks for idempotent execution | exp: func:_table_exists(table_name: str) → bool, call:sa.inspect(op.get_bind()).has_table, call:op.get_bind, func:_column_exists(table_name: str, column_name: str) → bool, call:_table_exists, call:sa.inspect(op.get_bind()).get_columns, call:op.get_bind, func:_index_exists(table_name: str, index_name: str) → bool, call:_table_exists, call:sa.inspect(op.get_bind()).get_indexes, call:op.get_bind, func:_foreign_key_exists(table_name: str, constrained_columns: list[str], referred_table: str) → bool, call:_table_exists, call:sa.inspect(op.get_bind()).get_foreign_keys, call:op.get_bind, call:foreign_key.get, func:upgrade() → None, call:_table_exists, call:op.create_table, call:sa.Column, call:postgresql.UUID, call:sa.String, call:sa.Text, call:sa.DateTime, call:sa.text, call:sa.ForeignKeyConstraint, call:sa.PrimaryKeyConstraint, call:sa.UniqueConstraint, call:_index_exists, call:op.create_index, call:sa.Integer, call:_column_exists, call:op.add_column, call:_foreign_key_exists, call:op.create_foreign_key, func:downgrade() → None, call:op.drop_index, call:op.drop_constraint, call:op.drop_column, call:op.drop_table | dep: collections.abc, alembic, sqlalchemy.dialects, sqlalchemy, sqlalchemy.dialects.postgresql
- 0013_add_probe_result.py | Database migration to add a JSON probe_result column to the tool_instances table | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, func:downgrade() → None, call:op.drop_column | dep: typing, alembic, sqlalchemy.dialects, sqlalchemy, sqlalchemy.dialects.postgresql
- 0014_add_profile_resolver_fields.py | Alembic database migration that adds profile resolver fields to config_profiles and config_mounts tables with idempotent schema checks | exp: func:_table_exists(table_name: str) → bool, call:sa.inspect(op.get_bind()).has_table, call:op.get_bind, func:_column_exists(table_name: str, column_name: str) → bool, call:_table_exists, call:sa.inspect(op.get_bind()).get_columns, call:op.get_bind, func:_index_exists(table_name: str, index_name: str) → bool, call:_table_exists, call:sa.inspect(op.get_bind()).get_indexes, call:op.get_bind, func:_foreign_key_exists(table_name: str, constrained_columns: list[str], referred_table: str) → bool, call:_table_exists, call:sa.inspect(op.get_bind()).get_foreign_keys, call:op.get_bind, call:foreign_key.get, func:_foreign_key_names_for_column(table_name: str, column_name: str) → list[str], call:_table_exists, call:sa.inspect(op.get_bind()).get_foreign_keys, call:op.get_bind, call:foreign_key.get, call:names.append, func:upgrade() → None, call:_column_exists, call:op.add_column, call:sa.Column, call:postgresql.UUID, call:sa.JSON, call:sa.Text, call:sa.Integer, call:sa.Boolean, call:_foreign_key_exists, call:op.create_foreign_key, call:_index_exists, call:op.create_index, call:op.alter_column, call:sa.String, call:_foreign_key_names_for_column, call:op.drop_constraint, call:op.drop_column, func:downgrade() → None, call:op.add_column, call:sa.Column, call:postgresql.UUID, call:sa.Text, call:op.drop_column, call:op.alter_column, call:op.drop_index, call:op.drop_constraint | dep: collections.abc, alembic, sqlalchemy.dialects, sqlalchemy, sqlalchemy.dialects.postgresql
- 0014_merge_heads.py | Alembic merge migration that reconciles two divergent migration branches without applying schema changes | exp: func:upgrade() → None, func:downgrade() → None | dep: typing, alembic
- 0015_single_interface.py | Alembic database migration that replaces a JSON array `interfaces` column with a single `interface_type` string column and adds a `requires_port` boolean column, with dialect-specific SQL for PostgreSQL and SQLite data migration. | exp: func:_get_dialect() → str, call:op.get_bind, func:upgrade() → None, call:_get_dialect, call:op.add_column, call:sa.Column, call:sa.String, call:sa.Boolean, call:op.execute, call:op.alter_column, call:op.drop_column, call:op.create_check_constraint, call:sa.text, func:downgrade() → None, call:_get_dialect, call:op.drop_constraint, call:op.add_column, call:sa.Column, call:postgresql.JSONB, call:sa.Text, call:op.execute, call:sa.JSON, call:op.drop_column | dep: typing, alembic, sqlalchemy.dialects, sqlalchemy, sqlalchemy.dialects.postgresql
- 069d3da4dc9b_add_ssh_key_id_to_config_profiles.py | Alembic database migration that adds an ssh_key_id foreign key column to the config_profiles table | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.Uuid, call:sa.ForeignKey, func:downgrade() → None, call:op.drop_column | dep: alembic, sqlalchemy
- 20260527160017_add_pi_agent_tool_type.py | Adds a database migration that inserts a new "pi-agent" tool type into a tool_types table, defining a terminal-based Docker development environment with nvim, ranger, and tmux. | exp: func:upgrade() → None, call:op.get_bind, call:conn.execute( sa.text("SELECT id FROM tool_types WHERE name = 'pi-agent'") ).fetchone, call:sa.text, call:json.dumps, func:downgrade() → None, call:op.get_bind, call:conn.execute, call:sa.text | dep: json, typing, alembic, uuid, sqlalchemy
- 2026_05_22_add_clone_mode.py | Alembic database migration that adds ssh_key_id foreign key to git_repositories table and clone_mode/branch columns to tool_instances table | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:postgresql.UUID, call:op.create_foreign_key, call:sa.String, func:downgrade() → None, call:op.drop_column, call:op.drop_constraint | dep: alembic, sqlalchemy.dialects, sqlalchemy, sqlalchemy.dialects.postgresql
- 2026_05_23_remove_is_builtin.py | Alembic database migration to remove the `is_builtin` column from the `tool_types` table | exp: func:upgrade() → None, call:op.execute, func:downgrade() → None, call:op.add_column, call:sa.Column, call:sa.Boolean | dep: alembic, sqlalchemy
- 2026_05_24_220141_add_startup_command.py | Alembic database migration that adds a nullable `startup_command` text column to the `tool_types` table. | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.Text, func:downgrade() → None, call:op.drop_column | dep: typing, alembic, sqlalchemy
- 2026_05_24_add_config_profiles.py | Alembic database migration that creates config_profiles and config_profile_includes tables with indexes, and adds a foreign key column to tool_instances for managing user configuration profiles. | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:postgresql.UUID, call:sa.text, call:sa.ForeignKey, call:sa.String, call:sa.Text, call:postgresql.JSONB, call:sa.Boolean, call:sa.DateTime, call:sa.PrimaryKeyConstraint, call:sa.UniqueConstraint, call:op.create_index, call:sa.Integer, call:op.add_column, func:downgrade() → None, call:op.drop_index, call:op.drop_column, call:op.drop_table | dep: typing, alembic, sqlalchemy.dialects, sqlalchemy, postgresql dialect
- 2026_05_26_add_git_mounts.py | Alembic database migration that adds a git_mounts JSON column to the config_profiles table | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.JSON, func:downgrade() → None, call:op.drop_column | dep: typing, alembic, sqlalchemy
- 2026_05_27_external_repos.py | Alembic database migration that makes project_id nullable in git_repositories table to support external repositories and expands alembic_version version_num column to 64 characters. | exp: func:upgrade() → None, call:op.execute, call:op.alter_column, call:sa.UUID, func:downgrade() → None, call:op.alter_column, call:sa.UUID, call:op.execute | dep: typing, alembic, sqlalchemy
- 2026_05_28_add_monitoring_tables.py | Alembic database migration that creates monitoring tables (instance_events and health_checks) with indexes for tracking tool instance events and health checks | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:sa.Uuid, call:sa.String, call:sa.Text, call:sa.JSON, call:sa.DateTime, call:sa.func.now, call:sa.ForeignKeyConstraint, call:sa.PrimaryKeyConstraint, call:op.create_index, call:sa.Boolean, call:sa.Integer, func:downgrade() → None, call:op.drop_index, call:op.drop_table | dep: collections.abc, alembic, sqlalchemy
- 2026_05_28_add_terminal_sessions_table.py | Alembic database migration that creates a terminal_sessions table with tracking columns and foreign key to tool_instances | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:sa.UUID, call:sa.String, call:sa.DateTime, call:sa.text, call:sa.ForeignKeyConstraint, call:sa.PrimaryKeyConstraint, call:op.create_index, call:op.f, func:downgrade() → None, call:op.drop_index, call:op.f, call:op.drop_table | dep: collections.abc, alembic, sqlalchemy
- 2026_05_28_add_tool_definition_manifests.py | Alembic database migration that creates a tool_definition_manifests table, adds manifest-related columns to tool_types and tool_instances, and migrates existing pi-agent data from Dockerfile templates to JSON-based manifest definitions | exp: func:upgrade() → None, call:op.get_bind, call:op.create_table, call:sa.Column, call:sa.UUID, call:sa.String, call:sa.Text, call:sa.JSON, call:sa.Boolean, call:sa.TIMESTAMP, call:sa.func.now, call:sa.PrimaryKeyConstraint, call:sa.UniqueConstraint, call:sa.ForeignKeyConstraint, call:sa.CheckConstraint, call:conn.execute, call:sa.text, call:result.fetchone, call:op.add_column, call:op.create_foreign_key, call:op.drop_constraint, call:op.execute, call:json.dumps, call:str, func:downgrade() → None, call:op.get_bind, call:conn.execute, call:sa.text, call:result.fetchone, call:op.drop_column, call:op.drop_constraint, call:op.drop_table | dep: json, uuid, typing, alembic, sqlalchemy
- 2026_05_28_drop_tool_configs_and_config_folders.py | Alembic database migration that drops `tool_configs` and `config_folders` tables with conditional existence checks and full downgrade recreation | exp: func:upgrade() → None, call:op.get_bind, call:conn.execute, call:sa.text, call:result.fetchone, call:op.drop_table, func:downgrade() → None, call:op.create_table, call:sa.Column, call:sa.UUID, call:sa.String, call:sa.Text, call:sa.JSON, call:sa.Boolean, call:sa.TIMESTAMP, call:sa.func.now, call:sa.PrimaryKeyConstraint, call:sa.Integer | dep: typing, alembic, sqlalchemy
- 2026_05_29_add_notifications_table.py | Alembic database migration that creates a notifications table with user-linked, categorized, severity-graded messages supporting read/dismissed tracking and optimized querying indexes. | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:sa.Uuid, call:sa.String, call:sa.Text, call:sa.JSON, call:sa.DateTime, call:sa.func.now, call:sa.ForeignKeyConstraint, call:sa.PrimaryKeyConstraint, call:op.create_index, call:sa.text, func:downgrade() → None, call:op.drop_index, call:op.drop_table | dep: collections.abc, alembic, sqlalchemy
- 2026_05_29_add_ssh_key_ids_to_tool_instances.py | Alembic database migration that adds a JSON column named ssh_key_ids to the tool_instances table | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.JSON, func:downgrade() → None, call:op.drop_column | dep: alembic, sqlalchemy
- 2026_05_23_remove_is_builtin.py | Alembic database migration that removes the `is_builtin` column from the `tool_types` table with a downgrade path to restore it. | exp: func:upgrade() → None, call:op.execute, func:downgrade() → None, call:op.add_column, call:sa.Column, call:sa.Boolean | dep: alembic, sqlalchemy
- 2026_05_24_220141_add_startup_command.py | Alembic database migration that adds a nullable startup_command column to the tool_types table. | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.Text, func:downgrade() → None, call:op.drop_column | dep: typing, alembic, sqlalchemy
- 2026_05_24_add_config_profiles.py | Alembic database migration that creates config_profiles and config_profile_includes tables with indexes, and adds a selected_config_profile_id foreign key to tool_instances. | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:postgresql.UUID, call:sa.text, call:sa.ForeignKey, call:sa.String, call:sa.Text, call:postgresql.JSONB, call:sa.Boolean, call:sa.DateTime, call:sa.PrimaryKeyConstraint, call:sa.UniqueConstraint, call:op.create_index, call:sa.Integer, call:op.add_column, func:downgrade() → None, call:op.drop_index, call:op.drop_column, call:op.drop_table | dep: typing, alembic, sqlalchemy.dialects, sqlalchemy, sqlalchemy.dialects.postgresql
- 2026_05_26_add_git_mounts.py | Database migration to add a `git_mounts` JSON column to the `config_profiles` table | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.JSON, func:downgrade() → None, call:op.drop_column | dep: typing, alembic, sqlalchemy
- 2026_05_27_external_repos.py | Alembic database migration that makes project_id nullable in git_repositories table to support external repositories and expands alembic_version version_num column to VARCHAR(64). | exp: func:upgrade() → None, call:op.execute, call:op.alter_column, call:sa.UUID, func:downgrade() → None, call:op.alter_column, call:sa.UUID, call:op.execute | dep: typing, alembic, sqlalchemy
- 2026_05_28_add_monitoring_tables.py | Creates two database tables (instance_events and health_checks) for monitoring tool instances via an Alembic migration. | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:sa.Uuid, call:sa.String, call:sa.Text, call:sa.JSON, call:sa.DateTime, call:sa.func.now, call:sa.ForeignKeyConstraint, call:sa.PrimaryKeyConstraint, call:op.create_index, call:sa.Boolean, call:sa.Integer, func:downgrade() → None, call:op.drop_index, call:op.drop_table | dep: collections.abc, alembic, sqlalchemy
- 2026_05_28_add_terminal_sessions_table.py | Alembic database migration that creates a terminal_sessions table with foreign key to tool_instances, including indexes and audit timestamps | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:sa.UUID, call:sa.String, call:sa.DateTime, call:sa.text, call:sa.ForeignKeyConstraint, call:sa.PrimaryKeyConstraint, call:op.create_index, call:op.f, func:downgrade() → None, call:op.drop_index, call:op.f, call:op.drop_table | dep: collections.abc, alembic, sqlalchemy
- 2026_05_28_add_tool_definition_manifests.py | Alembic database migration that creates a tool_definition_manifests table, adds manifest support to tool_types and tool_instances, and migrates the pi-agent tool to a manifest-based definition with a base Ubuntu image. | exp: func:upgrade() → None, call:op.get_bind, call:op.create_table, call:sa.Column, call:sa.UUID, call:sa.String, call:sa.Text, call:sa.JSON, call:sa.Boolean, call:sa.TIMESTAMP, call:sa.func.now, call:sa.PrimaryKeyConstraint, call:sa.UniqueConstraint, call:sa.ForeignKeyConstraint, call:sa.CheckConstraint, call:conn.execute, call:sa.text, call:result.fetchone, call:op.add_column, call:op.create_foreign_key, call:op.drop_constraint, call:op.execute, call:json.dumps, call:str, func:downgrade() → None, call:op.get_bind, call:conn.execute, call:sa.text, call:result.fetchone, call:op.drop_column, call:op.drop_constraint, call:op.drop_table | dep: json, uuid, typing, alembic, sqlalchemy
- 2026_05_28_drop_tool_configs_and_config_folders.py | Alembic database migration that drops the tool_configs and config_folders tables with conditional existence checks, and provides downgrade to recreate them | exp: func:upgrade() → None, call:op.get_bind, call:conn.execute, call:sa.text, call:result.fetchone, call:op.drop_table, func:downgrade() → None, call:op.create_table, call:sa.Column, call:sa.UUID, call:sa.String, call:sa.Text, call:sa.JSON, call:sa.Boolean, call:sa.TIMESTAMP, call:sa.func.now, call:sa.PrimaryKeyConstraint, call:sa.Integer | dep: typing, alembic, sqlalchemy
- 2026_05_29_add_notifications_table.py | Creates a notifications table with user-linked, categorized, severity-graded messages supporting read/dismissed states and optimized querying via partial indexes. | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:sa.Uuid, call:sa.String, call:sa.Text, call:sa.JSON, call:sa.DateTime, call:sa.func.now, call:sa.ForeignKeyConstraint, call:sa.PrimaryKeyConstraint, call:op.create_index, call:sa.text, func:downgrade() → None, call:op.drop_index, call:op.drop_table | dep: collections.abc, alembic, sqlalchemy
- 2026_05_29_add_ssh_key_ids_to_tool_instances.py | Alembic database migration that adds a JSON ssh_key_ids column to the tool_instances table | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.JSON, func:downgrade() → None, call:op.drop_column | dep: alembic, sqlalchemy
- 2026_05_29_drop_ssh_key_id_from_config_profiles.py | Alembic database migration that removes the ssh_key_id column from the config_profiles table | exp: func:upgrade() → None, call:op.drop_column, func:downgrade() → None, call:op.add_column, call:sa.Column, call:sa.Uuid, call:sa.ForeignKey | dep: alembic, sqlalchemy
- 2026_05_29_fix_code_server_bind_addr.py | Alembic database migration that fixes code-server tool type compose templates by replacing deprecated `--bind-addr` flag with `--host` flag | exp: func:upgrade() → None, call:op.get_bind, call:conn.execute( sa.text(""" SELECT id, compose_template FROM tool_types WHERE name = 'code-server' AND compose_template LIKE '%--bind-addr%' """) ).fetchall, call:sa.text, call:compose_template.replace( "--bind-addr 0.0.0.0:8443", "--host 0.0.0.0" ).replace, call:print, func:downgrade() → None | dep: typing, alembic, sqlalchemy
- 2026_05_29_fix_code_server_bind_addr_port.py | Alembic database migration that fixes code-server Docker compose templates and instance files by replacing broken `--host` flags with correct `--bind-addr 0.0.0.0:port` configurations | exp: func:_fix_tool_type_templates(conn) → None, call:conn.execute( sa.text(""" SELECT id, compose_template, default_port FROM tool_types WHERE name = 'code-server' AND compose_template LIKE '%--host%' """) ).fetchall, call:sa.text, call:compose_template.split, call:len, call:line.lstrip, call:new_lines.append, call:"\n".join, call:print, func:_fix_instance_compose_files(conn) → None, call:conn.execute( sa.text(""" SELECT column_name FROM information_schema.columns WHERE table_name = 'tool_instances' AND column_name = 'compose_path' """) ).fetchone, call:sa.text, call:print, call:conn.execute( sa.text(""" SELECT id, compose_path, tool_type_id FROM tool_instances WHERE compose_path IS NOT NULL """) ).fetchall, call:Path, call:path.exists, call:path.read_text, call:conn.execute( sa.text(""" SELECT default_port FROM tool_types WHERE id = :id """), {"id": tool_type_id}, ).fetchone, call:yaml.safe_load, call:data["services"].values, call:path.write_text, call:yaml.dump, func:upgrade() → None, call:op.get_bind, call:_fix_tool_type_templates, call:_fix_instance_compose_files, func:downgrade() → None | dep: typing, alembic, yaml, pathlib, sqlalchemy
- 2026_05_29_fix_web_tool_bind_address.py | Alembic database migration that updates code-server and jupyter-notebook tool type compose templates to bind to 0.0.0.0 | exp: func:_fix_code_server_compose(conn) → None, call:conn.execute( sa.text(""" SELECT id, compose_template, definition_type FROM tool_types WHERE name = 'code-server' """) ).fetchone, call:sa.text, call:compose_template.split, call:enumerate, call:len, call:line.lstrip, call:new_lines.append, call:image_line.lstrip, call:new_lines.index, call:new_lines.insert, call:"\n".join, call:print, func:_fix_jupyter_compose(conn) → None, call:conn.execute( sa.text(""" SELECT id, compose_template, definition_type FROM tool_types WHERE name = 'jupyter-notebook' """) ).fetchone, call:sa.text, call:compose_template.split, call:enumerate, call:new_lines.append, call:len, call:line.lstrip, call:"\n".join, call:print, func:upgrade() → None, call:op.get_bind, call:_fix_code_server_compose, call:_fix_jupyter_compose, func:downgrade() → None | dep: typing, alembic, sqlalchemy
- 2026_05_29_remove_lsio_command_override.py | Alembic database migration that removes broken command overrides containing --bind-addr or --host flags from LinuxServer.io code-server Docker Compose templates in both database tool_types records and on-disk instance compose files. | exp: func:upgrade() → None, call:op.get_bind, call:conn.execute( sa.text(""" SELECT id, compose_template FROM tool_types WHERE name = 'code-server' """) ).fetchall, call:sa.text, call:yaml.safe_load, call:data["services"].values, call:svc.get, call:yaml.dump, call:print, call:conn.execute( sa.text(""" SELECT column_name FROM information_schema.columns WHERE table_name = 'tool_instances' AND column_name = 'compose_path' """) ).fetchone, call:conn.execute( sa.text(""" SELECT id, compose_path FROM tool_instances WHERE compose_path IS NOT NULL """) ).fetchall, call:Path, call:path.exists, call:path.read_text, call:path.write_text, func:downgrade() → None | dep: collections.abc, alembic, yaml, pathlib, sqlalchemy, pathlib.Path, information_schema
- 2026_05_29_remove_ssh_keys_mount_from_manifest.py | Alembic database migration that removes (or restores) the ssh_keys mount from a JSON manifest stored in the tool_definition_manifests table for the pi-agent tool definition. | exp: func:upgrade() → None, call:op.get_bind, call:conn.execute, call:sa.text, call:result.fetchone, call:isinstance, call:json.loads, call:manifest.get, call:len, call:m.get, call:json.dumps, func:downgrade() → None, call:op.get_bind, call:conn.execute, call:sa.text, call:result.fetchone, call:isinstance, call:json.loads, call:manifest.get, call:any, call:m.get, call:mounts.append, call:json.dumps | dep: json, typing, alembic, sqlalchemy
- 2026_06_01_add_workspaces.py | Alembic database migration that creates a workspaces table with foreign keys to git_repositories and users, adds indexes, and adds a workspace_id column to tool_instances | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:sa.Uuid, call:sa.String, call:sa.ForeignKey, call:sa.DateTime, call:sa.text, call:sa.UniqueConstraint, call:op.create_index, call:op.add_column, func:downgrade() → None, call:op.drop_index, call:op.drop_column, call:op.drop_table | dep: collections.abc, alembic, sqlalchemy
- 398082499c30_add_tool_config_fields.py | Alembic database migration that adds five new columns (port_override, start_command, working_directory, environment_variables, volumes) to the tool_configs table with a port range check constraint. | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.Integer, call:sa.Text, call:postgresql.JSONB, call:op.create_check_constraint, call:sa.text, func:downgrade() → None, call:op.drop_constraint, call:op.drop_column | dep: alembic, sqlalchemy.dialects, sqlalchemy, sqlalchemy.dialects.postgresql
- 6fc7bfcf199f_merge_remove_is_builtin_and_add_config_.py | Alembic database migration that merges two parallel revision branches (removing is_builtin and adding config_profiles) into a single history line | exp: func:upgrade() → None, func:downgrade() → None | dep: alembic
- 86cec91fdb00_merge_profile_resolver_and_workspaces_.py | Alembic database migration that merges two divergent migration branches (profile resolver and workspaces) into a single head | exp: func:upgrade() → None, func:downgrade() → None | dep: alembic
- 8ed7dd80973d_create_config_folders_table.py | Alembic database migration that creates a config_folders table with user-owned configuration folders supporting JSONB file storage and project overrides | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:postgresql.UUID, call:sa.text, call:sa.ForeignKey, call:sa.String, call:sa.Text, call:postgresql.JSONB, call:sa.Boolean, call:sa.DateTime, call:sa.UniqueConstraint, call:op.create_index, func:downgrade() → None, call:op.drop_index, call:op.drop_table | dep: alembic, sqlalchemy.dialects, sqlalchemy, sqlalchemy.dialects.postgresql
- af8512103d67_add_tool_type_fields.py | Alembic database migration that adds new columns (definition_type, dockerfile_template, build_context, readiness_probe) to the tool_types table with a CHECK constraint on definition_type. | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.String, call:sa.Text, call:postgresql.JSONB, call:op.create_check_constraint, call:sa.text, func:downgrade() → None, call:op.drop_constraint, call:op.drop_column | dep: alembic, sqlalchemy.dialects, sqlalchemy, sqlalchemy.dialects.postgresql
- f3d2dc90ba3a_merge_single_interface_and_clone_mode.py | Alembic database migration that merges two prior revisions (single_interface and clone_mode) into a single migration path | exp: func:upgrade() → None, func:downgrade() → None | dep: typing, alembic
- 2026_05_29_fix_code_server_bind_addr.py | Alembic database migration that fixes code-server tool type compose templates by replacing deprecated --bind-addr flag with --host flag | exp: func:upgrade() → None, call:op.get_bind, call:conn.execute( sa.text(""" SELECT id, compose_template FROM tool_types WHERE name = 'code-server' AND compose_template LIKE '%--bind-addr%' """) ).fetchall, call:sa.text, call:compose_template.replace( "--bind-addr 0.0.0.0:8443", "--host 0.0.0.0" ).replace, call:print, func:downgrade() → None | dep: typing, alembic, sqlalchemy
- 2026_05_29_fix_code_server_bind_addr_port.py | Alembic database migration that fixes code-server Docker compose templates by replacing incorrect `--host` flags with proper `--bind-addr 0.0.0.0:PORT` configurations in both database-stored tool type templates and on-disk instance compose files. | exp: func:_fix_tool_type_templates(conn) → None, call:conn.execute( sa.text(""" SELECT id, compose_template, default_port FROM tool_types WHERE name = 'code-server' AND compose_template LIKE '%--host%' """) ).fetchall, call:sa.text, call:compose_template.split, call:len, call:line.lstrip, call:new_lines.append, call:"\n".join, call:print, func:_fix_instance_compose_files(conn) → None, call:conn.execute( sa.text(""" SELECT column_name FROM information_schema.columns WHERE table_name = 'tool_instances' AND column_name = 'compose_path' """) ).fetchone, call:sa.text, call:print, call:conn.execute( sa.text(""" SELECT id, compose_path, tool_type_id FROM tool_instances WHERE compose_path IS NOT NULL """) ).fetchall, call:Path, call:path.exists, call:path.read_text, call:conn.execute( sa.text(""" SELECT default_port FROM tool_types WHERE id = :id """), {"id": tool_type_id}, ).fetchone, call:yaml.safe_load, call:data["services"].values, call:path.write_text, call:yaml.dump, func:upgrade() → None, call:op.get_bind, call:_fix_tool_type_templates, call:_fix_instance_compose_files, func:downgrade() → None | dep: typing, alembic, yaml, pathlib, sqlalchemy
- 2026_05_29_fix_web_tool_bind_address.py | Alembic database migration that fixes web tool (code-server and jupyter-notebook) compose templates to bind to 0.0.0.0 for network accessibility. | exp: func:_fix_code_server_compose(conn) → None, call:conn.execute( sa.text(""" SELECT id, compose_template, definition_type FROM tool_types WHERE name = 'code-server' """) ).fetchone, call:sa.text, call:compose_template.split, call:enumerate, call:len, call:line.lstrip, call:new_lines.append, call:image_line.lstrip, call:new_lines.index, call:new_lines.insert, call:"\n".join, call:print, func:_fix_jupyter_compose(conn) → None, call:conn.execute( sa.text(""" SELECT id, compose_template, definition_type FROM tool_types WHERE name = 'jupyter-notebook' """) ).fetchone, call:sa.text, call:compose_template.split, call:enumerate, call:new_lines.append, call:len, call:line.lstrip, call:"\n".join, call:print, func:upgrade() → None, call:op.get_bind, call:_fix_code_server_compose, call:_fix_jupyter_compose, func:downgrade() → None | dep: typing, alembic, sqlalchemy
- 2026_05_29_remove_lsio_command_override.py | Alembic database migration that removes broken command overrides containing --bind-addr or --host from LinuxServer.io (LSIO) code-server Docker Compose templates in both database tool_types records and on-disk instance compose files. | exp: func:upgrade() → None, call:op.get_bind, call:conn.execute( sa.text(""" SELECT id, compose_template FROM tool_types WHERE name = 'code-server' """) ).fetchall, call:sa.text, call:yaml.safe_load, call:data["services"].values, call:svc.get, call:yaml.dump, call:print, call:conn.execute( sa.text(""" SELECT column_name FROM information_schema.columns WHERE table_name = 'tool_instances' AND column_name = 'compose_path' """) ).fetchone, call:conn.execute( sa.text(""" SELECT id, compose_path FROM tool_instances WHERE compose_path IS NOT NULL """) ).fetchall, call:Path, call:path.exists, call:path.read_text, call:path.write_text, func:downgrade() → None | dep: collections.abc, alembic, yaml, pathlib, sqlalchemy
- 2026_05_29_remove_ssh_keys_mount_from_manifest.py | Alembic database migration that removes the ssh_keys mount from the pi-agent manifest in the tool_definition_manifests table, with downgrade support to restore it. | exp: func:upgrade() → None, call:op.get_bind, call:conn.execute, call:sa.text, call:result.fetchone, call:isinstance, call:json.loads, call:manifest.get, call:len, call:m.get, call:json.dumps, func:downgrade() → None, call:op.get_bind, call:conn.execute, call:sa.text, call:result.fetchone, call:isinstance, call:json.loads, call:manifest.get, call:any, call:m.get, call:mounts.append, call:json.dumps | dep: json, typing, alembic, sqlalchemy
- 2026_06_01_add_workspaces.py | Alembic database migration that creates a workspaces table with foreign keys to git_repositories and users, adds indexes, and adds a workspace_id column to tool_instances. | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:sa.Uuid, call:sa.String, call:sa.ForeignKey, call:sa.DateTime, call:sa.text, call:sa.UniqueConstraint, call:op.create_index, call:op.add_column, func:downgrade() → None, call:op.drop_index, call:op.drop_column, call:op.drop_table | dep: collections.abc, alembic, sqlalchemy
- 2026_06_13_make_clone_mode_nullable.py | Alembic database migration that makes the `clone_mode` column in the `tool_instances` table nullable to allow NULL values for new rows. | exp: func:upgrade() → None, call:op.alter_column, call:sa.String, func:downgrade() → None, call:op.alter_column, call:sa.String | dep: alembic, sqlalchemy
- 2026_06_14_104415_add_tool_type_home_directory.py | An Alembic database migration that adds a `home_directory` column to `tool_types` table and updates template strings from `/workspace` to a configurable `/home/user/{{WORKSPACE_NAME}}` path. | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.String, call:op.execute, call:sa.update(tool_types) .where(tool_types.c.compose_template.is_not(None)) .values, call:tool_types.c.compose_template.is_not, call:sa.func.replace, call:sa.update(tool_types) .where(tool_types.c.dockerfile_template.is_not(None)) .values, call:tool_types.c.dockerfile_template.is_not, func:downgrade() → None, call:op.execute, call:sa.update(tool_types) .where(tool_types.c.compose_template.is_not(None)) .values, call:tool_types.c.compose_template.is_not, call:sa.func.replace, call:sa.update(tool_types) .where(tool_types.c.dockerfile_template.is_not(None)) .values, call:tool_types.c.dockerfile_template.is_not, call:op.drop_column | dep: typing, alembic, sqlalchemy.sql, sqlalchemy
- 2026_06_14_182955_fix_pi_agent_home_directory_mount.py | Alembic database migration that fixes the pi-agent tool definition manifest's home directory mount path from /workspace to ~/{{WORKSPACE_NAME}} with a compatibility symlink and updated startup script. | exp: func:_find_pi_agent_manifest(conn: sa.Connection) → tuple[Union[str, None], Union[dict, None]], call:conn.execute( sa.select(tool_definition_manifests.c.id, tool_definition_manifests.c.manifest) .where(tool_definition_manifests.c.name == "pi-agent") ).fetchone, call:sa.select(tool_definition_manifests.c.id, tool_definition_manifests.c.manifest) .where, call:dict, func:_update_manifest(conn: sa.Connection, manifest_id: str, manifest: dict) → None, call:conn.execute, call:sa.update(tool_definition_manifests) .where(tool_definition_manifests.c.id == manifest_id) .values, func:upgrade() → None, call:op.get_bind, call:_find_pi_agent_manifest, call:manifest.get, call:mount.get, call:manifest.setdefault, call:_update_manifest, func:downgrade() → None, call:op.get_bind, call:_find_pi_agent_manifest, call:manifest.get, call:mount.get, call:manifest.setdefault, call:_update_manifest | dep: typing, alembic, sqlalchemy.sql, sqlalchemy
- 2026_06_15_090500_remove_pi_agent_explicit_repo_mount.py | Alembic database migration that removes explicit repo mounts from the pi-agent tool definition manifest and restores them on downgrade | exp: func:_find_pi_agent_manifest(conn: sa.Connection) → tuple[Union[str, None], Union[dict, None]], call:conn.execute( sa.select( tool_definition_manifests.c.id, tool_definition_manifests.c.manifest ).where(tool_definition_manifests.c.name == "pi-agent") ).fetchone, call:sa.select( tool_definition_manifests.c.id, tool_definition_manifests.c.manifest ).where, call:dict, func:_update_manifest(conn: sa.Connection, manifest_id: str, manifest: dict) → None, call:conn.execute, call:sa.update(tool_definition_manifests) .where(tool_definition_manifests.c.id == manifest_id) .values, func:upgrade() → None, call:op.get_bind, call:_find_pi_agent_manifest, call:manifest.get, call:mount.get, call:_update_manifest, func:downgrade() → None, call:op.get_bind, call:_find_pi_agent_manifest, call:manifest.setdefault, call:any, call:mount.get, call:mounts.append, call:_update_manifest | dep: typing, alembic, sqlalchemy.sql, sqlalchemy
- 2026_06_19_113000_remove_pi_agent_workspace_symlink.py | Alembic database migration that updates the pi-agent tool definition manifest to remove the /workspace symlink dependency and adjust working directory and startup script accordingly. | exp: func:_find_pi_agent_manifest(conn: sa.Connection) → tuple[str | None, dict | None], call:conn.execute( sa.select( tool_definition_manifests.c.id, tool_definition_manifests.c.manifest ).where(tool_definition_manifests.c.name == "pi-agent") ).fetchone, call:sa.select( tool_definition_manifests.c.id, tool_definition_manifests.c.manifest ).where, call:dict, func:_update_manifest(conn: sa.Connection, manifest_id: str, manifest: dict) → None, call:conn.execute, call:sa.update(tool_definition_manifests) .where(tool_definition_manifests.c.id == manifest_id) .values, func:upgrade() → None, call:op.get_bind, call:_find_pi_agent_manifest, call:manifest.setdefault, call:runtime.get, call:_update_manifest, func:downgrade() → None, call:op.get_bind, call:_find_pi_agent_manifest, call:manifest.setdefault, call:_update_manifest | dep: collections.abc, alembic, sqlalchemy.sql, sqlalchemy
- 398082499c30_add_tool_config_fields.py | Alembic database migration that adds configuration fields (port_override, start_command, working_directory, environment_variables, volumes) to the tool_configs table with a port range validation constraint. | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.Integer, call:sa.Text, call:postgresql.JSONB, call:op.create_check_constraint, call:sa.text, func:downgrade() → None, call:op.drop_constraint, call:op.drop_column | dep: alembic, sqlalchemy.dialects, sqlalchemy, sqlalchemy.dialects.postgresql
- 6fc7bfcf199f_merge_remove_is_builtin_and_add_config_.py | Alembic merge migration that combines two parallel migration branches (remove_is_builtin and add_config_profiles) into a single revision history | exp: func:upgrade() → None, func:downgrade() → None | dep: alembic
- 86cec91fdb00_merge_profile_resolver_and_workspaces_.py | Merges two Alembic migration branches (profile resolver and workspaces) into a single migration head | exp: func:upgrade() → None, func:downgrade() → None | dep: alembic
- 8c6d1dbd4798_remove_pi_config_and_state_mounts_from_.py | Alembic database migration that removes or restores pi_state and pi_config mounts from a JSON manifest stored in the tool_definition_manifests table for the 'pi-agent' tool. | exp: func:_load_manifest(manifest_json), call:isinstance, call:json.loads, func:upgrade() → None, call:op.get_bind, call:conn.execute, call:sa.text, call:result.fetchone, call:_load_manifest, call:manifest.get, call:len, call:m.get, call:json.dumps, func:downgrade() → None, call:op.get_bind, call:conn.execute, call:sa.text, call:result.fetchone, call:_load_manifest, call:manifest.get, call:m.get, call:mounts.append, call:json.dumps | dep: json, alembic, sqlalchemy
- 8ed7dd80973d_create_config_folders_table.py | Alembic database migration that creates a config_folders table with UUID primary key, user foreign key, JSONB fields for files and project overrides, and supporting indexes/constraints | exp: func:upgrade() → None, call:op.create_table, call:sa.Column, call:postgresql.UUID, call:sa.text, call:sa.ForeignKey, call:sa.String, call:sa.Text, call:postgresql.JSONB, call:sa.Boolean, call:sa.DateTime, call:sa.UniqueConstraint, call:op.create_index, func:downgrade() → None, call:op.drop_index, call:op.drop_table | dep: alembic, sqlalchemy.dialects, sqlalchemy, postgresql dialect
- af8512103d67_add_tool_type_fields.py | Alembic database migration that adds columns (definition_type, dockerfile_template, build_context, readiness_probe) and a check constraint to the tool_types table. | exp: func:upgrade() → None, call:op.add_column, call:sa.Column, call:sa.String, call:sa.Text, call:postgresql.JSONB, call:op.create_check_constraint, call:sa.text, func:downgrade() → None, call:op.drop_constraint, call:op.drop_column | dep: alembic, sqlalchemy.dialects, sqlalchemy, sqlalchemy.dialects.postgresql
- f3d2dc90ba3a_merge_single_interface_and_clone_mode.py | Alembic database migration that merges two previous migrations (single_interface and clone_mode) into a single revision point | exp: func:upgrade() → None, func:downgrade() → None | dep: typing, alembic
- fc8f1a20cbf6_merge_home_directory_and_pi_agent_mount_.py | Alembic database migration that merges two branch heads (home directory and pi agent mount cleanup) into a single revision point | exp: func:upgrade() → None, func:downgrade() → None | dep: alembic
## arch
Linear and branched Alembic migration pattern with merge migrations to reconcile divergent branches, using incremental revision files with upgrade/downgrade functions, idempotent operations, and occasional data migrations for schema transformations.
Linear migration history with occasional branch/merge patterns (using merge heads), sequential numbered and timestamped revision files, each containing declarative schema changes (CREATE TABLE/ALTER TABLE/DROP TABLE) with idempotent guards, downgrade paths, and occasional data migrations; supports PostgreSQL and SQLite dialects.
## tags
column, table, call:op.drop, key, alembic, downgrade, upgrade, constraint
column, table, call:op.drop, downgrade, upgrade, alembic, key, call:sa.text
## symbols
- upgrade
- downgrade
@@ -70,5 +77,7 @@ column, table, call:op.drop, key, alembic, downgrade, upgrade, constraint
read: 2026_05_24_220141_add_startup_command.py, 2026_05_29_remove_lsio_command_override.py
- change versions config
read: 0003_user_configs.py, 0009_tool_configs.py, 0013_add_config_profiles.py
- explore versions subdirectories
index: apps/api/alembic/versions/.ruff_cache/.pi-map.index.md
## dirty
-
@@ -104,11 +104,11 @@ RUN git config --global init.defaultBranch main \\
&& git config --global user.name "Developer"
# Create default tmux config
RUN echo 'set -g mouse on\\nset -g default-terminal "screen-256color"' > /home/user/.tmux.conf
RUN printf '%s\\n' 'set -g mouse on' 'set -g default-terminal "screen-256color"' > /home/user/.tmux.conf
# Create default ranger config
RUN mkdir -p /home/user/.config/ranger \\
&& echo 'set preview_files true\\nset use_preview_script true' > /home/user/.config/ranger/rc.conf
&& printf '%s\\n' 'set preview_files true' 'set use_preview_script true' > /home/user/.config/ranger/rc.conf
# Set up Pi config directory
RUN mkdir -p /home/user/.pi/agent
@@ -232,20 +232,6 @@ def upgrade() -> None:
"writable": True,
"owner": "user",
},
{
"name": "pi_state",
"target": "/tmp/.pi/agents",
"source_type": "instance",
"writable": True,
},
{
"name": "pi_config",
"target": "/home/user/.pi",
"source_type": "git_mount",
"git_mount_ref": "dotfiles",
"writable": True,
"owner": "user",
},
],
"runtime": {
"command": ["/bin/bash"],
@@ -322,10 +308,10 @@ RUN git config --global init.defaultBranch main \\
&& git config --global user.email "dev@headquarter.local" \\
&& git config --global user.name "Developer"
RUN echo 'set -g mouse on\\nset -g default-terminal "screen-256color"' > /home/user/.tmux.conf
RUN printf '%s\\n' 'set -g mouse on' 'set -g default-terminal "screen-256color"' > /home/user/.tmux.conf
RUN mkdir -p /home/user/.config/ranger \\
&& echo 'set preview_files true\\nset use_preview_script true' > /home/user/.config/ranger/rc.conf
&& printf '%s\\n' 'set preview_files true' 'set use_preview_script true' > /home/user/.config/ranger/rc.conf
RUN mkdir -p /home/user/.pi/agent
@@ -0,0 +1,35 @@
"""make clone_mode nullable
Revision ID: 2026_06_13_make_clone_mode_nullable
Revises: 86cec91fdb00
Create Date: 2026-06-13 10:00:00.000000
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "2026_06_13_make_clone_mode_nullable"
down_revision = "86cec91fdb00"
branch_labels = None
depends_on = None
def upgrade() -> None:
# The workspace-first cleanup no longer writes clone_mode; existing rows
# keep their value, but new rows may be NULL.
op.alter_column(
"tool_instances",
"clone_mode",
existing_type=sa.String(20),
nullable=True,
)
def downgrade() -> None:
op.alter_column(
"tool_instances",
"clone_mode",
existing_type=sa.String(20),
nullable=False,
)
@@ -0,0 +1,89 @@
"""add_tool_type_home_directory
Revision ID: 2026_06_14_104415
Revises: f3d2dc90ba3a
Create Date: 2026-06-14 10:44:15.000000
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
from sqlalchemy.sql import column, table
# revision identifiers, used by Alembic.
revision: str = "2026_06_14_104415"
down_revision: Union[str, Sequence[str], None] = "f3d2dc90ba3a"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
tool_types = table(
"tool_types",
column("id", sa.UUID),
column("home_directory", sa.String),
column("compose_template", sa.Text),
column("dockerfile_template", sa.Text),
)
OLD_WORKSPACE = "/workspace"
NEW_WORKSPACE = "/home/user/{{WORKSPACE_NAME}}"
def upgrade() -> None:
# Add the new column with a default that applies to existing rows.
op.add_column(
"tool_types",
sa.Column(
"home_directory",
sa.String(255),
nullable=False,
server_default="/home/user",
),
)
# Rewrite legacy templates that mount the workspace at /workspace so they
# use the new configurable home directory and preserve the workspace name.
op.execute(
sa.update(tool_types)
.where(tool_types.c.compose_template.is_not(None))
.values(
compose_template=sa.func.replace(
tool_types.c.compose_template, OLD_WORKSPACE, NEW_WORKSPACE
)
)
)
op.execute(
sa.update(tool_types)
.where(tool_types.c.dockerfile_template.is_not(None))
.values(
dockerfile_template=sa.func.replace(
tool_types.c.dockerfile_template, OLD_WORKSPACE, NEW_WORKSPACE
)
)
)
def downgrade() -> None:
# Restore the original /workspace strings before dropping the column.
op.execute(
sa.update(tool_types)
.where(tool_types.c.compose_template.is_not(None))
.values(
compose_template=sa.func.replace(
tool_types.c.compose_template, NEW_WORKSPACE, OLD_WORKSPACE
)
)
)
op.execute(
sa.update(tool_types)
.where(tool_types.c.dockerfile_template.is_not(None))
.values(
dockerfile_template=sa.func.replace(
tool_types.c.dockerfile_template, NEW_WORKSPACE, OLD_WORKSPACE
)
)
)
op.drop_column("tool_types", "home_directory")
@@ -0,0 +1,90 @@
"""fix_pi_agent_home_directory_mount
Revision ID: 2026_06_14_182955
Revises: fc8f1a20cbf6
Create Date: 2026-06-14 18:29:55.000000
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
from sqlalchemy.sql import column, table
# revision identifiers, used by Alembic.
revision: str = "2026_06_14_182955"
down_revision: Union[str, Sequence[str], None] = "fc8f1a20cbf6"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
tool_definition_manifests = table(
"tool_definition_manifests",
column("id", sa.UUID),
column("name", sa.String),
column("manifest", sa.JSON),
)
def _find_pi_agent_manifest(conn: sa.Connection) -> tuple[Union[str, None], Union[dict, None]]:
result = conn.execute(
sa.select(tool_definition_manifests.c.id, tool_definition_manifests.c.manifest)
.where(tool_definition_manifests.c.name == "pi-agent")
).fetchone()
if result is None:
return None, None
return result.id, dict(result.manifest)
def _update_manifest(conn: sa.Connection, manifest_id: str, manifest: dict) -> None:
conn.execute(
sa.update(tool_definition_manifests)
.where(tool_definition_manifests.c.id == manifest_id)
.values(manifest=manifest)
)
def upgrade() -> None:
conn = op.get_bind()
manifest_id, manifest = _find_pi_agent_manifest(conn)
if not manifest_id or not manifest:
return
# Mount the repo under the configured home directory, preserving the repo
# directory name via the WORKSPACE_NAME runtime variable.
for mount in manifest.get("mounts", []):
if mount.get("source_type") == "repo":
mount["target"] = "~/{{WORKSPACE_NAME}}"
# Keep /workspace as a compatibility symlink to the real mount path.
runtime = manifest.setdefault("runtime", {})
runtime["working_dir"] = "/workspace"
# Update the startup script to chown the real mount path.
scripts = manifest.setdefault("scripts", {})
scripts["startup"] = [
'if [ -n "$WORKSPACE_NAME" ]; then sudo chown -R user:user "$HOME/$WORKSPACE_NAME" 2>/dev/null || true; fi',
]
_update_manifest(conn, manifest_id, manifest)
def downgrade() -> None:
conn = op.get_bind()
manifest_id, manifest = _find_pi_agent_manifest(conn)
if not manifest_id or not manifest:
return
for mount in manifest.get("mounts", []):
if mount.get("source_type") == "repo":
mount["target"] = "/workspace"
runtime = manifest.setdefault("runtime", {})
runtime["working_dir"] = "/workspace"
scripts = manifest.setdefault("scripts", {})
scripts["startup"] = [
"if [ -d /workspace ]; then sudo chown -R user:user /workspace 2>/dev/null || true; fi",
]
_update_manifest(conn, manifest_id, manifest)
@@ -0,0 +1,86 @@
"""remove pi agent explicit repo mount
Revision ID: 2026_06_15_090500
Revises: 2026_06_14_182955
Create Date: 2026-06-15 09:05:00.000000
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
from sqlalchemy.sql import column, table
# revision identifiers, used by Alembic.
revision: str = "2026_06_15_090500"
down_revision: Union[str, Sequence[str], None] = "2026_06_14_182955"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
tool_definition_manifests = table(
"tool_definition_manifests",
column("id", sa.UUID),
column("name", sa.String),
column("manifest", sa.JSON),
)
def _find_pi_agent_manifest(
conn: sa.Connection,
) -> tuple[Union[str, None], Union[dict, None]]:
result = conn.execute(
sa.select(
tool_definition_manifests.c.id, tool_definition_manifests.c.manifest
).where(tool_definition_manifests.c.name == "pi-agent")
).fetchone()
if result is None:
return None, None
return result.id, dict(result.manifest)
def _update_manifest(conn: sa.Connection, manifest_id: str, manifest: dict) -> None:
conn.execute(
sa.update(tool_definition_manifests)
.where(tool_definition_manifests.c.id == manifest_id)
.values(manifest=manifest)
)
def upgrade() -> None:
conn = op.get_bind()
manifest_id, manifest = _find_pi_agent_manifest(conn)
if not manifest_id or not manifest:
return
# The repo mount is now synthesized by compile_compose based on the
# instance's repository, so the manifest no longer needs an explicit
# repo mount with a {{WORKSPACE_NAME}} placeholder.
manifest["mounts"] = [
mount
for mount in manifest.get("mounts", [])
if mount.get("source_type") != "repo"
]
_update_manifest(conn, manifest_id, manifest)
def downgrade() -> None:
conn = op.get_bind()
manifest_id, manifest = _find_pi_agent_manifest(conn)
if not manifest_id or not manifest:
return
mounts = manifest.setdefault("mounts", [])
if not any(mount.get("source_type") == "repo" for mount in mounts):
mounts.append(
{
"name": "workspace",
"target": "~/{{WORKSPACE_NAME}}",
"source_type": "repo",
"writable": True,
"owner": "user",
}
)
_update_manifest(conn, manifest_id, manifest)
@@ -0,0 +1,87 @@
"""remove pi agent workspace symlink
Revision ID: 2026_06_19_113000
Revises: 2026_06_15_090500
Create Date: 2026-06-19 11:30:00.000000
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
from sqlalchemy.sql import column, table
# revision identifiers, used by Alembic.
revision: str = "2026_06_19_113000"
down_revision: str | Sequence[str] | None = "2026_06_15_090500"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
tool_definition_manifests = table(
"tool_definition_manifests",
column("id", sa.UUID),
column("name", sa.String),
column("manifest", sa.JSON),
)
def _find_pi_agent_manifest(
conn: sa.Connection,
) -> tuple[str | None, dict | None]:
result = conn.execute(
sa.select(
tool_definition_manifests.c.id, tool_definition_manifests.c.manifest
).where(tool_definition_manifests.c.name == "pi-agent")
).fetchone()
if result is None:
return None, None
return result.id, dict(result.manifest)
def _update_manifest(conn: sa.Connection, manifest_id: str, manifest: dict) -> None:
conn.execute(
sa.update(tool_definition_manifests)
.where(tool_definition_manifests.c.id == manifest_id)
.values(manifest=manifest)
)
def upgrade() -> None:
conn = op.get_bind()
manifest_id, manifest = _find_pi_agent_manifest(conn)
if not manifest_id or not manifest:
return
runtime = manifest.setdefault("runtime", {})
# The /workspace compatibility symlink is no longer created by the
# compiler/entrypoint. Leaving working_dir set to /workspace causes the
# container to start in an empty directory. Let compile_compose default
# to /home/user/{workspace_name} instead.
if runtime.get("working_dir") == "/workspace":
del runtime["working_dir"]
scripts = manifest.setdefault("scripts", {})
# Update the startup script to operate on the real repo-named directory.
scripts["startup"] = [
'if [ -n "$WORKSPACE_NAME" ]; then sudo chown -R user:user "$HOME/$WORKSPACE_NAME" 2>/dev/null || true; fi',
]
_update_manifest(conn, manifest_id, manifest)
def downgrade() -> None:
conn = op.get_bind()
manifest_id, manifest = _find_pi_agent_manifest(conn)
if not manifest_id or not manifest:
return
runtime = manifest.setdefault("runtime", {})
runtime["working_dir"] = "/workspace"
scripts = manifest.setdefault("scripts", {})
scripts["startup"] = [
"if [ -d /workspace ]; then sudo chown -R user:user /workspace 2>/dev/null || true; fi",
]
_update_manifest(conn, manifest_id, manifest)
@@ -0,0 +1,112 @@
"""remove pi config and state mounts from pi-agent manifest
Revision ID: 8c6d1dbd4798
Revises: 2026_06_13_make_clone_mode_nullable
Create Date: 2026-06-13 11:21:25.983178
"""
import json
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '8c6d1dbd4798'
down_revision = '2026_06_13_make_clone_mode_nullable'
branch_labels = None
depends_on = None
def _load_manifest(manifest_json):
return manifest_json if isinstance(manifest_json, dict) else json.loads(manifest_json)
def upgrade() -> None:
"""Remove pi_state and pi_config mounts from the pi-agent manifest."""
conn = op.get_bind()
result = conn.execute(
sa.text(
"SELECT id, manifest FROM tool_definition_manifests WHERE name = 'pi-agent'"
)
)
row = result.fetchone()
if not row:
return
manifest_id, manifest_json = row
manifest = _load_manifest(manifest_json)
mounts = manifest.get("mounts", [])
original_count = len(mounts)
filtered_mounts = [
m for m in mounts if m.get("name") not in ("pi_state", "pi_config")
]
if len(filtered_mounts) < original_count:
manifest["mounts"] = filtered_mounts
conn.execute(
sa.text(
"UPDATE tool_definition_manifests SET manifest = :manifest WHERE id = :id"
),
{
"manifest": json.dumps(manifest),
"id": manifest_id,
},
)
def downgrade() -> None:
"""Restore pi_state and pi_config mounts to the pi-agent manifest."""
conn = op.get_bind()
result = conn.execute(
sa.text(
"SELECT id, manifest FROM tool_definition_manifests WHERE name = 'pi-agent'"
)
)
row = result.fetchone()
if not row:
return
manifest_id, manifest_json = row
manifest = _load_manifest(manifest_json)
mounts = manifest.get("mounts", [])
existing_names = {m.get("name") for m in mounts}
if "pi_state" not in existing_names:
mounts.append(
{
"name": "pi_state",
"target": "/tmp/.pi/agents",
"source_type": "instance",
"writable": True,
}
)
if "pi_config" not in existing_names:
mounts.append(
{
"name": "pi_config",
"target": "/home/user/.pi",
"source_type": "git_mount",
"git_mount_ref": "dotfiles",
"writable": True,
"owner": "user",
}
)
manifest["mounts"] = mounts
conn.execute(
sa.text(
"UPDATE tool_definition_manifests SET manifest = :manifest WHERE id = :id"
),
{
"manifest": json.dumps(manifest),
"id": manifest_id,
},
)
@@ -0,0 +1,23 @@
"""merge home directory and pi agent mount cleanup heads
Revision ID: fc8f1a20cbf6
Revises: 2026_06_14_104415, 8c6d1dbd4798
Create Date: 2026-06-14 11:08:41.273502
"""
# revision identifiers, used by Alembic.
revision = 'fc8f1a20cbf6'
down_revision = ('2026_06_14_104415', '8c6d1dbd4798')
branch_labels = None
depends_on = None
def upgrade() -> None:
pass
def downgrade() -> None:
pass
+5 -2
View File
@@ -2,11 +2,14 @@
dir: apps/api/src
## role
Core application package that bootstraps and configures the Headquarter API FastAPI service with its infrastructure concerns.
Core FastAPI application package that initializes and configures the Headquarter API with database, authentication, logging, and middleware infrastructure.
## parent
index: apps/api/.pi-map.index.md
map: apps/api/.pi-map.md
## children
- apps/api/src/.ruff_cache
index: apps/api/src/.ruff_cache/.pi-map.index.md
map: apps/api/src/.ruff_cache/.pi-map.md
- apps/api/src/api
index: apps/api/src/api/.pi-map.index.md
map: apps/api/src/api/.pi-map.md
@@ -49,6 +52,6 @@ map: apps/api/src/.pi-map.md
- change src config
read: config.py, logging_config.py
- explore src subdirectories
index: apps/api/src/api/.pi-map.index.md, apps/api/src/auth/.pi-map.index.md, apps/api/src/headquarter_api.egg-info/.pi-map.index.md
index: apps/api/src/.ruff_cache/.pi-map.index.md, apps/api/src/api/.pi-map.index.md, apps/api/src/auth/.pi-map.index.md
## dirty
-
+8 -8
View File
@@ -4,17 +4,17 @@ dir: apps/api/src
index: apps/api/src/.pi-map.index.md
## role
Core application package that bootstraps and configures the Headquarter API FastAPI service with its infrastructure concerns.
Core FastAPI application package that initializes and configures the Headquarter API with database, authentication, logging, and middleware infrastructure.
## files
- __init__.py | Marks the directory as a Python package for the Headquarter API.
- config.py | Defines application configuration settings using Pydantic with environment variable loading, database URL construction, and computed properties for service URLs and OAuth endpoints. | exp: class:Settings, func:build_database_url(user: str, password: str, host: str, port: int, database: str) → str | dep: pydantic, pydantic_settings
- database.py | Configures async SQLAlchemy database engine and session factory, and provides retry logic for database initialization with Alembic migrations via subprocess. | exp: func:init_database(max_retries, retry_delay) → bool, call:range, call:engine.connect, call:test_conn.execute, call:text, call:test_conn.close, call:logger.info, call:asyncio.get_event_loop().run_in_executor, call:subprocess.run, call:logger.debug, call:logger.error, call:asyncio.sleep, call:str(exc).lower, call:logger.warning | dep: asyncio, logging, subprocess, sqlalchemy.ext.asyncio, sqlalchemy.pool, src.config, sqlalchemy
- logging_config.py | Configures structured JSON logging with correlation ID injection, custom formatters, and HTTP request/exception middleware for a FastAPI application. | exp: class:CorrelationIdFilter, method:filter(self, record: logging.LogRecord) → bool, call:get_correlation_id, class:JSONFormatter, method:format(self, record: logging.LogRecord) → str, call:self.formatTime, call:record.getMessage, call:getattr, call:self.formatException, call:json.dumps, method:formatTime(self, record: logging.LogRecord, datefmt) → str, call:time.strftime, call:time.gmtime, class:RequestLoggingMiddleware, method:dispatch(self, request: Request, call_next: Callable) → Response, call:time.time, call:logger.info, call:call_next, call:int, call:logger.error, call:type, call:traceback.format_exc, class:ExceptionLoggingMiddleware, method:dispatch(self, request: Request, call_next: Callable) → Response, call:call_next, call:logger.critical, call:traceback.format_exc, func:configure_logging(level) → None, call:JSONFormatter, call:logging.StreamHandler, call:console_handler.setFormatter, call:console_handler.addFilter, call:CorrelationIdFilter, call:root_logger.setLevel, call:logging.getLogger("uvicorn").setLevel, call:logging.getLogger("uvicorn.access").setLevel, call:logging.getLogger("sqlalchemy.engine").setLevel, call:logger.info, call:logging.getLevelName | dep: json, logging, sys, time, traceback, collections.abc, fastapi, starlette.middleware.base, src.services.shared.correlation
- main.py | Initializes and configures a FastAPI application for the "Headquarter API" with database setup, middleware, routing, and background services. | exp: func:_sanitize_validation_errors(errors), call:error.get, call:str, call:ctx.items, call:isinstance, call:type, call:sanitized.append, func:validation_exception_handler(request: Request, exc: RequestValidationError), call:exc.errors, call:logger.warning, call:_sanitize_validation_errors, call:JSONResponse, func:on_startup(), call:logger.info, call:init_database, call:logger.error, call:sys.exit, call:_health_monitor.start, call:seed_builtin_tool_types, func:on_shutdown(), call:logger.info, call:_health_monitor.stop | dep: logging, os, fastapi, fastapi.exceptions, fastapi.middleware.cors, fastapi.responses, fastapi.staticfiles, src.api.config, src.api.project, src.api.system, src.api.tool, src.api.user, src.api.workspace, src.config, src.models, src.database, src.logging_config, src.seeds.builtin_tool_types, src.services.instance, src.services.shared, sys, src.api.*
- config.py | Defines application configuration settings using pydantic-settings, including database connectivity, Authentik SSO, JWT, session, and domain-based URL resolution. | exp: class:Settings, func:build_database_url(user: str, password: str, host: str, port: int, database: str) → str | dep: pydantic, pydantic_settings
- database.py | Configures an async SQLAlchemy database engine/session and provides a retry-based initialization function that runs Alembic migrations via subprocess. | exp: func:init_database(max_retries, retry_delay) → bool, call:range, call:engine.connect, call:test_conn.execute, call:text, call:test_conn.close, call:logger.info, call:asyncio.get_event_loop().run_in_executor, call:subprocess.run, call:os.path.dirname, call:os.path.abspath, call:logger.debug, call:logger.error, call:asyncio.sleep, call:str(exc).lower, call:logger.warning | dep: asyncio, logging, os, subprocess, sqlalchemy.ext.asyncio, sqlalchemy.pool, src.config, sqlalchemy
- logging_config.py | Configures structured JSON logging with correlation ID injection and provides ASGI middleware for logging HTTP requests, responses, and unhandled exceptions. | exp: class:CorrelationIdFilter, method:filter(self, record: logging.LogRecord) → bool, call:get_correlation_id, class:JSONFormatter, method:format(self, record: logging.LogRecord) → str, call:self.formatTime, call:record.getMessage, call:getattr, call:self.formatException, call:json.dumps, method:formatTime(self, record: logging.LogRecord, datefmt) → str, call:time.strftime, call:time.gmtime, class:RequestLoggingMiddleware, method:dispatch(self, request: Request, call_next: Callable) → Response, call:time.time, call:logger.info, call:call_next, call:int, call:logger.error, call:type, call:traceback.format_exc, class:ExceptionLoggingMiddleware, method:dispatch(self, request: Request, call_next: Callable) → Response, call:call_next, call:logger.critical, call:traceback.format_exc, func:configure_logging(level) → None, call:JSONFormatter, call:logging.StreamHandler, call:console_handler.setFormatter, call:console_handler.addFilter, call:CorrelationIdFilter, call:root_logger.setLevel, call:logging.getLogger("uvicorn").setLevel, call:logging.getLogger("uvicorn.access").setLevel, call:logging.getLogger("sqlalchemy.engine").setLevel, call:logger.info, call:logging.getLevelName | dep: json, logging, sys, time, traceback, collections.abc, fastapi, starlette.middleware.base, src.services.shared.correlation, starlette
- main.py | Initializes and configures the FastAPI application, setting up middleware, routers, database connections, and lifecycle event handlers for the Headquarter API. | exp: func:_sanitize_validation_errors(errors), call:error.get, call:str, call:ctx.items, call:isinstance, call:type, call:sanitized.append, func:validation_exception_handler(request: Request, exc: RequestValidationError), call:exc.errors, call:logger.warning, call:_sanitize_validation_errors, call:JSONResponse, func:on_startup(), call:logger.info, call:init_database, call:logger.error, call:sys.exit, call:_health_monitor.start, call:seed_builtin_tool_types, func:on_shutdown(), call:logger.info, call:_health_monitor.stop | dep: logging, os, fastapi, fastapi.exceptions, fastapi.middleware.cors, fastapi.responses, fastapi.staticfiles, src.api.config, src.api.project, src.api.system, src.api.tool, src.api.user, src.api.workspace, src.config, src.models, src.database, src.logging_config, src.seeds.builtin_tool_types, src.services.instance, src.services.shared, sys, src.api, src.seeds, src.services
## arch
Layered configuration with Pydantic settings, async SQLAlchemy with Alembic migration integration, structured JSON logging with correlation ID tracking, and FastAPI middleware/routing setup.
Layered architecture using Pydantic-settings for configuration, async SQLAlchemy with Alembic migrations, structured JSON logging with correlation ID tracking, and FastAPI lifecycle management with dependency injection for cross-cutting concerns.
## tags
src, database, logging, call:logger.info, api, middleware, fastapi, filter
src, logging, database, call:logger.info, api, middleware, filter, call:logging.get
## symbols
- Settings
- CorrelationIdFilter
@@ -30,6 +30,6 @@ src, database, logging, call:logger.info, api, middleware, fastapi, filter
- change src config
read: config.py, logging_config.py
- explore src subdirectories
index: apps/api/src/api/.pi-map.index.md, apps/api/src/auth/.pi-map.index.md, apps/api/src/headquarter_api.egg-info/.pi-map.index.md
index: apps/api/src/.ruff_cache/.pi-map.index.md, apps/api/src/api/.pi-map.index.md, apps/api/src/auth/.pi-map.index.md
## dirty
-
+5 -2
View File
@@ -2,11 +2,14 @@
dir: apps/api/src/api
## role
Defines reusable API validation utilities and package structure for FastAPI router organization.
Defines the core API router package with reusable Pydantic validation utilities for container and filesystem-related API schemas.
## parent
index: apps/api/src/.pi-map.index.md
map: apps/api/src/.pi-map.md
## children
- apps/api/src/api/.ruff_cache
index: apps/api/src/api/.ruff_cache/.pi-map.index.md
map: apps/api/src/api/.ruff_cache/.pi-map.md
- apps/api/src/api/config
index: apps/api/src/api/config/.pi-map.index.md
map: apps/api/src/api/config/.pi-map.md
@@ -35,6 +38,6 @@ map: apps/api/src/api/.pi-map.md
- change api behavior
read: __init__.py, shared_validators.py
- explore api subdirectories
index: apps/api/src/api/config/.pi-map.index.md, apps/api/src/api/project/.pi-map.index.md, apps/api/src/api/system/.pi-map.index.md
index: apps/api/src/api/.ruff_cache/.pi-map.index.md, apps/api/src/api/config/.pi-map.index.md, apps/api/src/api/project/.pi-map.index.md
## dirty
-
+4 -4
View File
@@ -4,12 +4,12 @@ dir: apps/api/src/api
index: apps/api/src/api/.pi-map.index.md
## role
Defines reusable API validation utilities and package structure for FastAPI router organization.
Defines the core API router package with reusable Pydantic validation utilities for container and filesystem-related API schemas.
## files
- __init__.py | Marks the directory as a Python package for API routers.
- shared_validators.py | Provides reusable Pydantic validator functions for API schema validation including mount paths, files, environment variables, and volume mounts. | exp: func:validate_mount_path(v: str | None) → str | None, call:v.startswith, raise:ValueError, func:validate_files(v: dict | None, max_size_bytes) → dict | None, call:v.items, call:path.startswith, call:len, call:content.encode, raise:ValueError, func:validate_env_vars(v: dict | None) → dict | None, call:isinstance, raise:ValueError, func:validate_volumes(v: list | None) → list | None, call:isinstance, call:enumerate, raise:ValueError
- shared_validators.py | Provides reusable Pydantic validator functions for validating mount paths, file contents, environment variables, and volume mounts in API schemas. | exp: func:validate_mount_path(v: str | None) → str | None, call:v.startswith, raise:ValueError, func:validate_files(v: dict | None, max_size_bytes) → dict | None, call:v.items, call:path.startswith, call:len, call:content.encode, raise:ValueError, func:validate_env_vars(v: dict | None) → dict | None, call:isinstance, raise:ValueError, func:validate_volumes(v: list | None) → list | None, call:isinstance, call:enumerate, raise:ValueError
## arch
Modular utility package with shared Pydantic validators for cross-cutting API schema concerns, following separation of validation logic from route handlers.
Modular package structure separating router organization from shared cross-cutting validation concerns using Pydantic validators.
## tags
validate, raise:value, error, call:isinstance, mount, api, init, path
## symbols
@@ -25,6 +25,6 @@ validate, raise:value, error, call:isinstance, mount, api, init, path
- change api behavior
read: __init__.py, shared_validators.py
- explore api subdirectories
index: apps/api/src/api/config/.pi-map.index.md, apps/api/src/api/project/.pi-map.index.md, apps/api/src/api/system/.pi-map.index.md
index: apps/api/src/api/.ruff_cache/.pi-map.index.md, apps/api/src/api/config/.pi-map.index.md, apps/api/src/api/project/.pi-map.index.md
## dirty
-
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/api/src/api/config
## role
Exposes REST API endpoints for managing user configuration profiles and settings.
Provides FastAPI API endpoints for managing user configuration settings and config profiles.
## parent
index: apps/api/src/api/.pi-map.index.md
map: apps/api/src/api/.pi-map.md
+6 -6
View File
@@ -4,15 +4,15 @@ dir: apps/api/src/api/config
index: apps/api/src/api/config/.pi-map.index.md
## role
Exposes REST API endpoints for managing user configuration profiles and settings.
Provides FastAPI API endpoints for managing user configuration settings and config profiles.
## files
- __init__.py | Aggregates and exports configuration-related API routers for the config module. | dep: src.api.config.config_profiles, src.api.config.user_config
- config_profiles.py | FastAPI router providing CRUD endpoints for user config profiles with includes, resolution, defaults, and git URL validation | exp: func:list_config_profiles(project_id, tool_type_id, current_user_id, session), call:select(ConfigProfile) .where(ConfigProfile.user_id == user_uuid) .options, call:selectinload, call:uuid.UUID, call:conditions.append, call:ConfigProfile.project_id.is_, call:ConfigProfile.tool_type_id.is_, call:query.where, call:or_, call:session.execute, call:result.scalars().all, call:profile_to_response, func:create_config_profile(data: ConfigProfileCreate, current_user_id, session), call:create_profile, call:logger.debug, call:profile_to_response, func:get_config_profile(profile_id: str, current_user_id, session), call:get_profile_with_includes, call:uuid.UUID, call:profile_to_response, raise:HTTPException, func:update_config_profile(profile_id: str, data: ConfigProfileUpdate, current_user_id, session), call:get_profile_with_includes, call:uuid.UUID, call:update_profile, call:logger.debug, call:profile_to_response, raise:HTTPException, func:delete_config_profile(profile_id: str, current_user_id, session), call:get_profile_with_includes, call:uuid.UUID, call:session.delete, call:session.commit, call:logger.debug, raise:HTTPException, func:update_profile_includes_endpoint(profile_id: str, data: ConfigProfileIncludeUpdate, current_user_id, session), call:get_profile_with_includes, call:uuid.UUID, call:update_includes, call:session.execute, call:select(ConfigProfileInclude).where, call:inc_result.scalars().all, call:logger.debug, call:profile_to_response, call:list, raise:HTTPException, func:preview_config_profile(profile_id: str, current_user_id, session), call:get_profile_with_includes, call:uuid.UUID, call:resolve_profile, call:resolved_profile_to_dict, raise:HTTPException, func:resolve_default_profile_endpoint(project_id, tool_type_id, current_user_id, session), call:resolve_default_profile, call:uuid.UUID, func:get_default_profiles_endpoint(user_id, session) → dict, call:session.execute, call:select(UserConfig).where, call:result.scalar_one_or_none, func:set_default_profiles_endpoint(data: DefaultProfilesUpdate, user_id, session) → dict, call:validate_default_profiles, call:get_or_create_user_config, call:session.commit, call:session.refresh, func:get_default_profile_for_tool_type_endpoint(tool_type_id: str, user_id, session) → dict, call:session.execute, call:select(UserConfig).where, call:result.scalar_one_or_none, call:user_config.default_profiles.get, func:validate_git_url_endpoint(data: ValidateGitUrlRequest, current_user_id, session) → ValidateGitUrlResponse, call:validate_git_url | dep: logging, uuid, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, sqlalchemy.orm, src.auth.dependencies, src.models, src.schemas.config, src.services.config.config_profile_resolver, src.services.config.crud_service, src.services.config.resolver_service
- user_config.py | Provides FastAPI endpoints to get and update the current user's configuration settings, creating a default config if none exists. | exp: func:_get_or_create_config(session: AsyncSession, user_id: uuid.UUID) → UserConfig, call:session.execute, call:select(UserConfig).where, call:result.scalar_one_or_none, call:UserConfig, call:session.add, call:session.commit, call:session.refresh, func:get_user_config(user_id, session) → UserConfigResponse, call:_get_user, call:_get_or_create_config, call:UserConfigResponse.model_validate, func:update_user_config(data: UserConfigUpdate, user_id, session) → UserConfigResponse, call:_get_user, call:_get_or_create_config, call:data.model_dump, call:logger.debug, call:session.commit, call:session.refresh, call:UserConfigResponse.model_validate | dep: logging, uuid, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models, src.schemas.user
- __init__.py | Aggregates and exports config-related API routers for module-level access | dep: src.api.config.config_profiles, src.api.config.user_config
- config_profiles.py | FastAPI router providing CRUD endpoints for user-owned config profiles with include resolution, default profile management, and git URL validation. | exp: func:list_config_profiles(project_id, tool_type_id, current_user_id, session), call:select(ConfigProfile) .where(ConfigProfile.user_id == user_uuid) .options, call:selectinload, call:uuid.UUID, call:conditions.append, call:ConfigProfile.project_id.is_, call:ConfigProfile.tool_type_id.is_, call:query.where, call:or_, call:session.execute, call:result.scalars().all, call:profile_to_response, func:create_config_profile(data: ConfigProfileCreate, current_user_id, session), call:create_profile, call:logger.debug, call:profile_to_response, func:get_config_profile(profile_id: str, current_user_id, session), call:get_profile_with_includes, call:uuid.UUID, call:profile_to_response, raise:HTTPException, func:update_config_profile(profile_id: str, data: ConfigProfileUpdate, current_user_id, session), call:get_profile_with_includes, call:uuid.UUID, call:update_profile, call:logger.debug, call:profile_to_response, raise:HTTPException, func:delete_config_profile(profile_id: str, current_user_id, session), call:get_profile_with_includes, call:uuid.UUID, call:session.delete, call:session.commit, call:logger.debug, raise:HTTPException, func:update_profile_includes_endpoint(profile_id: str, data: ConfigProfileIncludeUpdate, current_user_id, session), call:get_profile_with_includes, call:uuid.UUID, call:update_includes, call:session.execute, call:select(ConfigProfileInclude).where, call:inc_result.scalars().all, call:logger.debug, call:profile_to_response, call:list, raise:HTTPException, func:preview_config_profile(profile_id: str, current_user_id, session), call:get_profile_with_includes, call:uuid.UUID, call:resolve_profile, call:resolved_profile_to_dict, raise:HTTPException, func:resolve_default_profile_endpoint(project_id, tool_type_id, current_user_id, session), call:resolve_default_profile, call:uuid.UUID, func:get_default_profiles_endpoint(user_id, session) → dict, call:session.execute, call:select(UserConfig).where, call:result.scalar_one_or_none, func:set_default_profiles_endpoint(data: DefaultProfilesUpdate, user_id, session) → dict, call:validate_default_profiles, call:get_or_create_user_config, call:session.commit, call:session.refresh, func:get_default_profile_for_tool_type_endpoint(tool_type_id: str, user_id, session) → dict, call:session.execute, call:select(UserConfig).where, call:result.scalar_one_or_none, call:user_config.default_profiles.get, func:validate_git_url_endpoint(data: ValidateGitUrlRequest, current_user_id, session) → ValidateGitUrlResponse, call:validate_git_url | dep: logging, uuid, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, sqlalchemy.orm, src.auth.dependencies, src.models, src.schemas.config, src.services.config.config_profile_resolver, src.services.config.crud_service, src.services.config.resolver_service
- user_config.py | Provides FastAPI endpoints for retrieving and updating user configuration settings with automatic record creation. | exp: func:_get_or_create_config(session: AsyncSession, user_id: uuid.UUID) → UserConfig, call:session.execute, call:select(UserConfig).where, call:result.scalar_one_or_none, call:UserConfig, call:session.add, call:session.commit, call:session.refresh, func:get_user_config(user_id, session) → UserConfigResponse, call:_get_user, call:_get_or_create_config, call:UserConfigResponse.model_validate, func:update_user_config(data: UserConfigUpdate, user_id, session) → UserConfigResponse, call:_get_user, call:_get_or_create_config, call:data.model_dump, call:logger.debug, call:session.commit, call:session.refresh, call:UserConfigResponse.model_validate | dep: logging, uuid, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models, src.schemas.user
## arch
FastAPI router modules following domain-driven separation with CRUD patterns, validation logic, and automatic default initialization.
Modular router-based architecture using FastAPI with separate route modules for distinct config domains (profiles vs. user settings), CRUD patterns, and include resolution for hierarchical config.
## tags
config, profile, get, user, includes, call:uuid.uuid, or, default
config, profile, get, user, call:uuid.uuid, includes, or, default
## symbols
- list_config_profiles
- create_config_profile
+126 -4
View File
@@ -1,7 +1,9 @@
"""Config profile API endpoints."""
import logging
import os
import uuid
from pathlib import Path
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import select
@@ -9,7 +11,8 @@ from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from src.auth.dependencies import get_current_user_id, get_db_session
from src.models import ConfigProfile, ConfigProfileInclude, UserConfig
from src.config import Settings
from src.models import ConfigProfile, ConfigProfileInclude, ToolInstance, UserConfig
from src.schemas.config import (
ConfigProfileCreate,
ConfigProfileIncludeUpdate,
@@ -21,6 +24,7 @@ from src.schemas.config import (
)
from src.services.config.config_profile_resolver import (
ConfigProfileCycleError,
apply_resolved_profile,
resolve_profile,
resolved_profile_to_dict,
)
@@ -43,6 +47,59 @@ logger = logging.getLogger(__name__)
router = APIRouter(prefix="/config-profiles", tags=["config-profiles"])
def _canonical_profile_response(profile: ConfigProfile) -> dict:
"""Return profile data with edits from its shared working copy."""
response = profile_to_response(profile)
root = Path(Settings().instance_base_path) / "config-profiles" / str(profile.id)
def read_file(path: Path, fallback: str) -> str:
try:
return path.read_text() if path.is_file() else fallback
except OSError:
return fallback
response["files"] = {
relative_path: read_file(root / "files" / relative_path, content)
for relative_path, content in response["files"].items()
}
response["mounts"] = [dict(mount) for mount in response["mounts"]]
for mount in response["mounts"]:
mount_root = root / "mounts" / mount["target"].lstrip("/").replace("/", "_")
mount["files"] = {
relative_path: read_file(mount_root / relative_path, content)
for relative_path, content in mount.get("files", {}).items()
}
return response
async def _running_profile_outcomes(
session: AsyncSession, profile_id: uuid.UUID
) -> list[dict[str, str]]:
"""Report running instances that must restart to adopt a profile revision."""
result = await session.execute(
select(ToolInstance).where(ToolInstance.status == "running")
)
outcomes: list[dict[str, str]] = []
for instance in result.scalars().all():
if instance.selected_config_profile_id is None:
continue
resolved = await resolve_profile(session, instance.selected_config_profile_id)
dependencies = {resolved.profile_id} | {
uuid.UUID(item["id"])
for item in resolved.included_profiles
if item.get("id")
}
if profile_id in dependencies:
outcomes.append(
{
"instance_id": str(instance.id),
"status": "restart_required",
"reason": "Existing instance must restart to adopt shared profile mounts",
}
)
return outcomes
@router.get("", response_model=list[ConfigProfileResponse])
async def list_config_profiles(
project_id: str | None = Query(None, description="Filter by project compatibility"),
@@ -85,7 +142,7 @@ async def list_config_profiles(
result = await session.execute(query)
profiles = result.scalars().all()
return [profile_to_response(p) for p in profiles]
return [_canonical_profile_response(profile) for profile in profiles]
@router.post(
@@ -118,7 +175,7 @@ async def get_config_profile(
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized"
)
return profile_to_response(profile)
return _canonical_profile_response(profile)
@router.put("/{profile_id}", response_model=ConfigProfileResponse)
@@ -140,8 +197,63 @@ async def update_config_profile(
)
profile = await update_profile(session, profile, data)
resolved = await resolve_profile(session, profile.id)
apply_resolved_profile(
os.path.join(Settings().instance_base_path, "profile-refresh"),
resolved,
)
response = _canonical_profile_response(profile)
response["refresh_outcomes"] = await _running_profile_outcomes(session, profile.id)
logger.debug("Updated config profile %s", profile.id)
return profile_to_response(profile)
return response
@router.post("/{profile_id}/refresh-git-mounts")
async def refresh_profile_git_mounts(
profile_id: str,
confirm_destructive_refresh: bool = False,
current_user_id: uuid.UUID = Depends(get_current_user_id),
session: AsyncSession = Depends(get_db_session),
):
"""Destructively refresh profile Git working copies used by instances."""
if not confirm_destructive_refresh:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Confirm destructive Git refresh before replacing local edits",
)
profile = await get_profile_with_includes(session, uuid.UUID(profile_id))
if profile is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Profile not found"
)
if profile.user_id != current_user_id:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized"
)
# Import lazily: instance_service imports tool schemas that transitively
# load API routers, so importing it during router initialization cycles.
from src.services.tool.instance_service import resolve_git_mounts
outcomes = await _running_profile_outcomes(session, profile.id)
for outcome in outcomes:
instance = await session.get(ToolInstance, uuid.UUID(outcome["instance_id"]))
if (
instance is None
or not instance.compose_path
or instance.selected_config_profile_id is None
):
continue
resolved = await resolve_profile(session, instance.selected_config_profile_id)
await resolve_git_mounts(
session,
resolved,
os.path.dirname(instance.compose_path),
)
outcome["status"] = "refreshed"
outcome["reason"] = "Canonical Git mount source refreshed in place"
return {"refresh_outcomes": outcomes}
@router.delete("/{profile_id}", status_code=status.HTTP_204_NO_CONTENT)
@@ -161,6 +273,16 @@ async def delete_config_profile(
status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized"
)
outcomes = await _running_profile_outcomes(session, profile.id)
if outcomes:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail={
"message": "Profile is still used by running instances",
"outcomes": outcomes,
},
)
await session.delete(profile)
await session.commit()
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/api/src/api/project
## role
Provides REST API endpoints for project and Git repository management in the backend service.
Provides FastAPI REST API endpoints for project and Git repository management, exposing CRUD operations and version control functionality to API consumers.
## parent
index: apps/api/src/api/.pi-map.index.md
map: apps/api/src/api/.pi-map.md
File diff suppressed because one or more lines are too long
+6 -2
View File
@@ -2,12 +2,14 @@
dir: apps/api/src/api/system
## role
Provides system-level API endpoints for monitoring, administration, and infrastructure interaction including dashboards, health checks, events, notifications, and container terminal access.
Provides system-level API endpoints for monitoring, administration, and infrastructure operations including dashboards, health checks, event streaming, instance proxying, notifications, and terminal access.
## parent
index: apps/api/src/api/.pi-map.index.md
map: apps/api/src/api/.pi-map.md
## children
-
- apps/api/src/api/system/.ruff_cache
index: apps/api/src/api/system/.ruff_cache/.pi-map.index.md
map: apps/api/src/api/system/.ruff_cache/.pi-map.md
## files
- __init__.py
- dashboard.py
@@ -22,5 +24,7 @@ map: apps/api/src/api/system/.pi-map.md
## workflows
- change system behavior
read: __init__.py, dashboard.py, events.py
- explore system subdirectories
index: apps/api/src/api/system/.ruff_cache/.pi-map.index.md
## dirty
-
+13 -11
View File
@@ -4,19 +4,19 @@ dir: apps/api/src/api/system
index: apps/api/src/api/system/.pi-map.index.md
## role
Provides system-level API endpoints for monitoring, administration, and infrastructure interaction including dashboards, health checks, events, notifications, and container terminal access.
Provides system-level API endpoints for monitoring, administration, and infrastructure operations including dashboards, health checks, event streaming, instance proxying, notifications, and terminal access.
## files
- __init__.py | Aggregates and re-exports system API router modules from submodules for centralized access. | dep: src.api.system.dashboard, src.api.system.events, src.api.system.health, src.api.system.instance_proxy, src.api.system.notifications, src.api.system.terminal
- dashboard.py | Provides a FastAPI endpoint that returns a dashboard summary with aggregated counts of projects, repositories, SSH keys, and recent activity for the authenticated user. | exp: func:get_dashboard_summary(user_id, session) → dict, call:session.execute, call:select(func.count()).select_from(Project).where, call:func.count, call:projects_result.scalar, call:select(func.count()).select_from(GitRepository).where, call:repos_result.scalar, call:select(func.count()).select_from(SSHKey).where, call:ssh_keys_result.scalar, call:select(Project) .where(Project.owner_id == user_id) .order_by(Project.created_at.desc()) .limit, call:Project.created_at.desc, call:recent_projects.scalars().all | dep: uuid, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models, src.models.project, src.models (GitRepository, Project, SSHKey)
- events.py | Implements an SSE streaming endpoint that delivers instance events to authenticated users with per-user connection limits and keep-alive pings. | exp: func:events_stream(request: Request, user_id) → StreamingResponse, call:_connection_counts.get, call:InstanceEventBus, call:asyncio.Queue, call:queue.put_nowait, call:contextlib.suppress, call:queue.get_nowait, call:event_bus.subscribe, call:asyncio.wait_for, call:queue.get, call:json.dumps, call:unsubscribe, call:max, call:_connection_counts.pop, call:StreamingResponse, call:event_generator, raise:HTTPException, func:event_generator() → AsyncGenerator[str, None], call:InstanceEventBus, call:asyncio.Queue, call:queue.put_nowait, call:contextlib.suppress, call:queue.get_nowait, call:event_bus.subscribe, call:asyncio.wait_for, call:queue.get, call:json.dumps, call:unsubscribe, call:max, call:_connection_counts.get, call:_connection_counts.pop, func:on_event(payload: InstanceEventPayload) → None, call:queue.put_nowait, call:contextlib.suppress, call:queue.get_nowait | dep: asyncio, contextlib, json, uuid, collections.abc, fastapi, fastapi.responses, src.auth.dependencies, src.services.instance.event_bus
- health.py | Implements FastAPI health check endpoints for monitoring system status including database connectivity and disk space. | exp: func:health_check() → dict[str, Any], call:HealthChecks, call:time_module.perf_counter, call:SessionLocal, call:session.execute, call:text, call:DatabaseHealth, call:round, call:shutil.disk_usage, call:DiskHealth, call:HealthResponse( status=overall_status, timestamp=datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"), version="0.1.0", checks=checks, uptime_seconds=round(time.time() - _start_time, 2), ).model_dump, call:datetime.now(timezone.utc).isoformat().replace, call:time.time, func:health_check_db() → dict[str, Any], call:time_module.perf_counter, call:SessionLocal, call:session.execute, call:text, call:DatabaseHealthResponse( status="healthy", response_time_ms=round(db_time, 2), ).model_dump, call:round, call:DatabaseHealthResponse( status="unhealthy", response_time_ms=0.0, ).model_dump | dep: time, datetime, typing, fastapi, sqlalchemy, src.database, src.schemas.system, shutil
- instance_proxy.py | Proxies HTTP requests from FastAPI endpoints to running containerized tool instances after verifying ownership and status. | exp: func:_proxy_request(request: Request, instance_id: uuid.UUID, path: str, user_id: uuid.UUID, session: AsyncSession) → Response, call:session.get, call:str, call:request.headers.items, call:key.lower, call:httpx.AsyncClient, call:request.body, call:client.request, call:logger.error, call:dict, call:response_headers.pop, call:Response, raise:HTTPException, func:proxy_to_instance(request: Request, instance_id: uuid.UUID, path, user_id, session) → Response, call:_proxy_request | dep: logging, uuid, httpx, fastapi, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models
- notifications.py | Defines FastAPI REST endpoints for managing user notifications (list, unread count, mark read, dismiss/clear) with support for muted categories from user config. | exp: class:NotificationItem, class:NotificationListResponse, class:UnreadCountResponse, class:MarkAllReadResponse, class:ClearAllResponse, func:_get_mute_categories(session: AsyncSession, user_id: uuid.UUID) → list[str], call:session.execute, call:select(UserConfig).where, call:result.scalar_one_or_none, call:config.config.get, call:isinstance, func:list_notifications(limit, offset, unread_only, user, session) → NotificationListResponse, call:_get_mute_categories, call:notification_service.list_notifications, call:NotificationListResponse, call:NotificationItem.model_validate, func:get_unread_count(user, session) → UnreadCountResponse, call:notification_service.get_unread_count, call:UnreadCountResponse, func:mark_notification_read(notification_id: uuid.UUID, user, session) → NotificationItem, call:notification_service.mark_read, call:NotificationItem.model_validate, raise:HTTPException, func:mark_all_read(user, session) → MarkAllReadResponse, call:notification_service.mark_all_read, call:MarkAllReadResponse, func:clear_all_notifications(user, session) → ClearAllResponse, call:notification_service.dismiss_all, call:ClearAllResponse, func:dismiss_notification(notification_id: uuid.UUID, user, session) → None, call:notification_service.dismiss, raise:HTTPException | dep: uuid, datetime, fastapi, pydantic, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models.user, src.models, src.services.shared.notification_service, sqlalchemy
- terminal.py | Provides WebSocket and HTTP endpoints for managing interactive terminal sessions attached to running Docker container tool instances, including session creation, attachment, input/output streaming, resize/reset control messages, and session listing. | exp: class:SessionRef, method:__init__(self, session, slot_session_id), func:terminal_websocket_default(websocket: WebSocket, instance_id: str, db_session) → None, call:_handle_terminal_websocket, func:terminal_websocket_specific(websocket: WebSocket, instance_id: str, session_id: str, db_session) → None, call:_handle_terminal_websocket, func:_handle_terminal_websocket(websocket: WebSocket, instance_id: str, target_session_id: str | None, db_session: AsyncSession) → None, call:logger.debug, call:websocket.accept, call:uuid.UUID, call:logger.error, call:websocket.close, call:_get_user_from_websocket, call:logger.warning, call:db_session.get, call:get_container_status, call:terminal_manager.get_or_create_session, call:terminal_manager.get_session, call:logger.info, call:terminal_manager.create_session, call:terminal_manager._find_key_by_internal_id, call:terminal_manager.attach_websocket, call:websocket.send_json, call:SessionRef, call:asyncio.create_task, call:_write_loop, call:_heartbeat_loop, call:asyncio.wait, call:len, call:task.cancel, call:str, call:suppress, call:terminal_manager.detach_websocket, func:_write_loop(session_ref: SessionRef, websocket, instance_id: str) → None, call:session.is_alive, call:asyncio.sleep, call:websocket.receive, call:session.write_input, call:text.startswith, call:json.loads, call:ctrl.get, call:logger.debug, call:session.resize, call:session.acknowledge_data, call:websocket.send_json, call:terminal_manager.reset_session, call:terminal_manager.attach_websocket, call:text.encode, func:_heartbeat_loop(websocket: WebSocket) → None, call:asyncio.sleep, call:websocket.send_json, func:_get_terminal_instance(instance_id: uuid.UUID, user_id: uuid.UUID, db_session: AsyncSession) → ToolInstance, call:db_session.get, raise:HTTPException, func:list_terminal_sessions(instance_id: uuid.UUID, user_id, db_session) → dict, call:_get_terminal_instance, call:db_session.execute, call:select(TerminalSessionModel) .where(TerminalSessionModel.instance_id == instance_id) .where(TerminalSessionModel.status != "closed") .order_by, call:TerminalSessionModel.created_at.asc, call:result.scalars().all, call:terminal_manager.get_session, call:str, call:sessions.append, call:live_session.has_websockets, call:row.created_at.isoformat, call:row.last_activity_at.isoformat, func:create_terminal_session(instance_id: uuid.UUID, data: dict, user_id, db_session) → dict, call:_get_terminal_instance, call:db_session.get, call:data.get, call:terminal_manager.create_session, raise:HTTPException, func:close_terminal_session(instance_id: uuid.UUID, session_id: str, user_id, db_session) → dict, call:_get_terminal_instance, call:terminal_manager._find_key_by_internal_id, call:str, call:terminal_manager.get_session, call:terminal_manager.close_session, raise:HTTPException, func:reset_specific_terminal_session(instance_id: uuid.UUID, session_id: str, user_id, db_session) → dict, call:_get_terminal_instance, call:terminal_manager._find_key_by_internal_id, call:str, call:terminal_manager.get_session, call:db_session.get, call:terminal_manager.reset_session, raise:HTTPException, func:rename_terminal_session(instance_id: uuid.UUID, session_id: str, data: dict, user_id, db_session) → dict, call:_get_terminal_instance, call:data.get, call:isinstance, call:terminal_manager.get_session, call:str, call:db_session.get, call:uuid.UUID, call:db_session.commit, raise:HTTPException, func:reset_terminal_session(instance_id: uuid.UUID, user_id, db_session) → dict, call:_get_terminal_instance, call:db_session.get, call:terminal_manager.reset_session, call:logger.info, call:str, call:logger.error, raise:HTTPException, func:_get_user_from_websocket(websocket: WebSocket, db_session: AsyncSession) → uuid.UUID | None, call:websocket.cookies.get, call:Settings, call:decode_session_cookie, call:uuid.UUID, call:str | dep: asyncio, json, logging, uuid, contextlib, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, starlette.websockets, src.auth.dependencies, src.models, src.services.terminal.terminal_manager, src.services.docker, src.auth.session, src.config, starlette
- __init__.py | Aggregates and exports system API routers for a modular web application framework. | dep: src.api.system.dashboard, src.api.system.events, src.api.system.health, src.api.system.instance_proxy, src.api.system.notifications, src.api.system.terminal
- dashboard.py | Provides a FastAPI endpoint that returns a dashboard summary with counts of projects, repositories, SSH keys, and recent activity for the authenticated user. | exp: func:get_dashboard_summary(user_id, session) → dict, call:session.execute, call:select(func.count()).select_from(Project).where, call:func.count, call:projects_result.scalar, call:select(func.count()).select_from(GitRepository).where, call:repos_result.scalar, call:select(func.count()).select_from(SSHKey).where, call:ssh_keys_result.scalar, call:select(Project) .where(Project.owner_id == user_id) .order_by(Project.created_at.desc()) .limit, call:Project.created_at.desc, call:recent_projects.scalars().all | dep: uuid, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models, src.models.project
- events.py | Implements an SSE streaming endpoint that delivers instance events to authenticated users with per-user connection limits and heartbeat pings. | dep: asyncio, contextlib, json, uuid, collections.abc, fastapi, src.auth.dependencies, src.services.instance.event_bus
- health.py | Provides FastAPI health check endpoints that monitor system health including database connectivity/response time and disk usage, returning structured health status responses. | exp: func:health_check() → dict[str, Any], call:HealthChecks, call:time_module.perf_counter, call:SessionLocal, call:session.execute, call:text, call:DatabaseHealth, call:round, call:shutil.disk_usage, call:DiskHealth, call:HealthResponse( status=overall_status, timestamp=datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"), version="0.1.0", checks=checks, uptime_seconds=round(time.time() - _start_time, 2), ).model_dump, call:datetime.now(timezone.utc).isoformat().replace, call:time.time, func:health_check_db() → dict[str, Any], call:time_module.perf_counter, call:SessionLocal, call:session.execute, call:text, call:DatabaseHealthResponse( status="healthy", response_time_ms=round(db_time, 2), ).model_dump, call:round, call:DatabaseHealthResponse( status="unhealthy", response_time_ms=0.0, ).model_dump | dep: time, datetime, typing, fastapi, sqlalchemy, src.database, src.schemas.system, shutil
- instance_proxy.py | Proxies HTTP requests from authenticated users to running containerized tool instances after verifying ownership and instance status. | exp: func:_proxy_request(request: Request, instance_id: uuid.UUID, path: str, user_id: uuid.UUID, session: AsyncSession) → Response, call:session.get, call:str, call:request.headers.items, call:key.lower, call:httpx.AsyncClient, call:request.body, call:client.request, call:logger.error, call:dict, call:response_headers.pop, call:Response, raise:HTTPException, func:proxy_to_instance(request: Request, instance_id: uuid.UUID, path, user_id, session) → Response, call:_proxy_request | dep: logging, uuid, httpx, fastapi, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models
- notifications.py | Defines FastAPI REST endpoints for user notification management including listing, marking as read, dismissing, and retrieving unread counts with support for muted categories. | exp: class:NotificationItem, class:NotificationListResponse, class:UnreadCountResponse, class:MarkAllReadResponse, class:ClearAllResponse, func:_get_mute_categories(session: AsyncSession, user_id: uuid.UUID) → list[str], call:session.execute, call:select(UserConfig).where, call:result.scalar_one_or_none, call:config.config.get, call:isinstance, func:list_notifications(limit, offset, unread_only, user, session) → NotificationListResponse, call:_get_mute_categories, call:notification_service.list_notifications, call:NotificationListResponse, call:NotificationItem.model_validate, func:get_unread_count(user, session) → UnreadCountResponse, call:notification_service.get_unread_count, call:UnreadCountResponse, func:mark_notification_read(notification_id: uuid.UUID, user, session) → NotificationItem, call:notification_service.mark_read, call:NotificationItem.model_validate, raise:HTTPException, func:mark_all_read(user, session) → MarkAllReadResponse, call:notification_service.mark_all_read, call:MarkAllReadResponse, func:clear_all_notifications(user, session) → ClearAllResponse, call:notification_service.dismiss_all, call:ClearAllResponse, func:dismiss_notification(notification_id: uuid.UUID, user, session) → None, call:notification_service.dismiss, raise:HTTPException | dep: uuid, datetime, fastapi, pydantic, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models.user, src.models, src.services.shared.notification_service, sqlalchemy
- terminal.py | Provides WebSocket endpoints for browser-based terminal access to running Docker container tool instances, handling authentication, session management, input/output streaming, and terminal resize/reset operations. | exp: class:SessionRef, method:__init__(self, session, slot_session_id), func:terminal_websocket_default(websocket: WebSocket, instance_id: str, db_session) → None, call:_handle_terminal_websocket, func:terminal_websocket_specific(websocket: WebSocket, instance_id: str, session_id: str, db_session) → None, call:_handle_terminal_websocket, func:_resolve_container_user(db_session: AsyncSession, instance: ToolInstance) → str | None, call:db_session.get, call:dict, call:resolve_base, call:deep_merge, call:get_manifest_container_user, func:_handle_terminal_websocket(websocket: WebSocket, instance_id: str, target_session_id: str | None, db_session: AsyncSession) → None, call:logger.debug, call:websocket.accept, call:uuid.UUID, call:logger.error, call:websocket.close, call:_get_user_from_websocket, call:logger.warning, call:db_session.get, call:get_container_status, call:_resolve_container_user, call:terminal_manager.get_or_create_session, call:terminal_manager.get_session, call:logger.info, call:terminal_manager.create_session, call:terminal_manager._find_key_by_internal_id, call:terminal_manager.attach_websocket, call:websocket.send_json, call:SessionRef, call:asyncio.create_task, call:_write_loop, call:_heartbeat_loop, call:asyncio.wait, call:len, call:task.cancel, call:str, call:suppress, call:terminal_manager.detach_websocket, func:_write_loop(session_ref: SessionRef, websocket, instance_id: str) → None, call:session.is_alive, call:asyncio.sleep, call:websocket.receive, call:session.write_input, call:text.startswith, call:json.loads, call:ctrl.get, call:logger.debug, call:session.resize, call:session.acknowledge_data, call:websocket.send_json, call:terminal_manager.reset_session, call:terminal_manager.attach_websocket, call:text.encode, func:_heartbeat_loop(websocket: WebSocket) → None, call:asyncio.sleep, call:websocket.send_json, func:_get_terminal_instance(instance_id: uuid.UUID, user_id: uuid.UUID, db_session: AsyncSession) → ToolInstance, call:db_session.get, raise:HTTPException, func:list_terminal_sessions(instance_id: uuid.UUID, user_id, db_session) → dict, call:_get_terminal_instance, call:db_session.execute, call:select(TerminalSessionModel) .where(TerminalSessionModel.instance_id == instance_id) .where(TerminalSessionModel.status != "closed") .order_by, call:TerminalSessionModel.created_at.asc, call:result.scalars().all, call:terminal_manager.get_session, call:str, call:sessions.append, call:live_session.has_websockets, call:row.created_at.isoformat, call:row.last_activity_at.isoformat, func:create_terminal_session(instance_id: uuid.UUID, data: dict, user_id, db_session) → dict, call:_get_terminal_instance, call:db_session.get, call:data.get, call:_resolve_container_user, call:terminal_manager.create_session, raise:HTTPException, func:close_terminal_session(instance_id: uuid.UUID, session_id: str, user_id, db_session) → dict, call:_get_terminal_instance, call:terminal_manager._find_key_by_internal_id, call:str, call:terminal_manager.get_session, call:terminal_manager.close_session, raise:HTTPException, func:reset_specific_terminal_session(instance_id: uuid.UUID, session_id: str, user_id, db_session) → dict, call:_get_terminal_instance, call:terminal_manager._find_key_by_internal_id, call:str, call:terminal_manager.get_session, call:db_session.get, call:_resolve_container_user, call:terminal_manager.reset_session, raise:HTTPException, func:rename_terminal_session(instance_id: uuid.UUID, session_id: str, data: dict, user_id, db_session) → dict, call:_get_terminal_instance, call:data.get, call:isinstance, call:terminal_manager.get_session, call:str, call:db_session.get, call:uuid.UUID, call:db_session.commit, raise:HTTPException, func:reset_terminal_session(instance_id: uuid.UUID, user_id, db_session) → dict, call:_get_terminal_instance, call:db_session.get, call:_resolve_container_user, call:terminal_manager.reset_session, call:logger.info, call:str, call:logger.error, raise:HTTPException, func:_get_user_from_websocket(websocket: WebSocket, db_session: AsyncSession) → uuid.UUID | None, call:websocket.cookies.get, call:Settings, call:decode_session_cookie, call:uuid.UUID, call:str | dep: asyncio, json, logging, uuid, contextlib, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, starlette.websockets, src.auth.dependencies, src.models, src.services.build.manifest_compiler, src.services.terminal.terminal_manager, src.services.docker, src.auth.session, src.config, starlette
## arch
FastAPI modular router pattern with separate files per domain concern, combining REST endpoints, SSE streaming, WebSocket connections, and HTTP proxying; uses dependency injection for auth and integrates with Docker/container runtime for terminal and instance proxy features.
Modular FastAPI router pattern with per-feature separation, combining standard REST endpoints, SSE streaming, and WebSocket connections, all with unified authentication and user-scoped access control.
## tags
session, terminal, call:terminal, call:, src, get, response, websocket
terminal, session, call:terminal, call:, src, get, response, instance
## symbols
- NotificationItem
- NotificationListResponse
@@ -25,9 +25,11 @@ session, terminal, call:terminal, call:, src, get, response, websocket
- ClearAllResponse
- SessionRef
- get_dashboard_summary
- events_stream
- health_check
## workflows
- change system behavior
read: __init__.py, dashboard.py, events.py
- explore system subdirectories
index: apps/api/src/api/system/.ruff_cache/.pi-map.index.md
## dirty
-
+182 -59
View File
@@ -3,6 +3,8 @@
import asyncio
import json
import logging
from asyncio import QueueFull
from json import JSONDecodeError
import uuid
from contextlib import suppress
@@ -12,14 +14,28 @@ from sqlalchemy.ext.asyncio import AsyncSession
from starlette.websockets import WebSocketDisconnect
from src.auth.dependencies import get_current_user_id, get_db_session
from src.models import TerminalSessionModel
from src.models import ToolInstance
from src.models import ToolType
from src.services.terminal.terminal_manager import MaxSessionsExceededError, terminal_manager
from src.models import (
TerminalSessionModel,
ToolDefinitionManifest,
ToolInstance,
ToolType,
)
from src.services.build.manifest_compiler import (
deep_merge,
get_manifest_container_user,
resolve_base,
)
from src.services.terminal.terminal_manager import (
MaxSessionsExceededError,
terminal_manager,
)
router = APIRouter()
logger = logging.getLogger(__name__)
MAX_PENDING_INPUT_MESSAGES = 64
MAX_TERMINAL_INPUT_BYTES = 1024 * 1024
class SessionRef:
"""Mutable reference to a terminal session, allowing updates during reset."""
@@ -57,6 +73,39 @@ async def terminal_websocket_specific(
await _handle_terminal_websocket(websocket, instance_id, session_id, db_session)
async def _resolve_container_user(
db_session: AsyncSession,
instance: ToolInstance,
) -> str | None:
"""Resolve the container user for docker exec from the tool manifest.
For manifest-based tools, the user declared in the manifest (or its base
definition) is returned so terminal sessions run with the same privileges
as the main container process. Legacy tools return None, preserving the
previous behavior.
"""
tool_type = await db_session.get(ToolType, instance.tool_type_id)
if not tool_type or tool_type.definition_type != "manifest":
return None
if not tool_type.manifest_id:
return None
manifest_def = await db_session.get(ToolDefinitionManifest, tool_type.manifest_id)
if not manifest_def:
return None
manifest = dict(manifest_def.manifest)
if manifest_def.base_definition_id:
base_def = await db_session.get(
ToolDefinitionManifest, manifest_def.base_definition_id
)
if base_def:
manifest = resolve_base(deep_merge(dict(base_def.manifest), manifest))
return get_manifest_container_user(manifest)
async def _handle_terminal_websocket(
websocket: WebSocket,
instance_id: str,
@@ -139,7 +188,7 @@ async def _handle_terminal_websocket(
)
return
# Fetch tool type to get startup_command
# Fetch tool type to get startup_command and container_user
tool_type = await db_session.get(ToolType, instance.tool_type_id)
startup_command = tool_type.startup_command if tool_type else None
if startup_command:
@@ -149,6 +198,14 @@ async def _handle_terminal_websocket(
startup_command,
)
container_user = await _resolve_container_user(db_session, instance)
if container_user:
logger.debug(
"Terminal sessions for instance %s will run as user %s",
instance_id,
container_user,
)
session = None
# Get or create terminal session
@@ -159,6 +216,7 @@ async def _handle_terminal_websocket(
instance_uuid,
instance.container_id,
startup_command=startup_command,
container_user=container_user,
)
slot_session_id = "default"
else:
@@ -189,6 +247,7 @@ async def _handle_terminal_websocket(
startup_command=startup_command,
name=db_row.name,
session_id=target_session_id,
container_user=container_user,
)
else:
logger.warning(
@@ -266,8 +325,38 @@ async def _handle_terminal_websocket(
)
async def _input_write_loop(input_queue: asyncio.Queue[tuple[object, bytes]]) -> None:
"""Serialize PTY writes without blocking terminal control messages."""
while True:
session, data = await input_queue.get()
try:
await session.write_input(data) # type: ignore[attr-defined]
except Exception:
logger.debug("Terminal input write failed", exc_info=True)
finally:
input_queue.task_done()
def _queue_terminal_input(
input_queue: asyncio.Queue[tuple[object, bytes]], session: object, data: bytes
) -> bool:
"""Queue bounded terminal input without blocking control-message processing."""
if len(data) > MAX_TERMINAL_INPUT_BYTES:
return False
try:
input_queue.put_nowait((session, data))
except QueueFull:
return False
return True
async def _write_loop(session_ref: SessionRef, websocket, instance_id: str) -> None:
"""Read input from WebSocket and send to container."""
"""Receive terminal messages while a dedicated worker serializes PTY input."""
input_queue: asyncio.Queue[tuple[object, bytes]] = asyncio.Queue(
maxsize=MAX_PENDING_INPUT_MESSAGES
)
input_writer = asyncio.create_task(_input_write_loop(input_queue))
try:
while True:
session = session_ref.session
@@ -277,72 +366,100 @@ async def _write_loop(session_ref: SessionRef, websocket, instance_id: str) -> N
message = await websocket.receive()
if message["type"] == "websocket.receive":
if "bytes" in message:
await session.write_input(message["bytes"])
if not _queue_terminal_input(
input_queue, session, message["bytes"]
):
logger.warning("Terminal input buffer exceeded for %s", instance_id)
await websocket.close(code=1009, reason="Terminal input buffer full")
break
elif "text" in message:
text = message["text"]
# A text frame that parses to a JSON object with a
# "type" field is a control message and must NEVER be
# written to the PTY (e.g. the heartbeat {"type":"pong"}
# must be consumed, not typed into the shell/pi). Handle
# known types and ignore unknown ones. Everything else
# (keystrokes, bracketed-paste content, plain text) is
# forwarded as raw terminal input.
ctrl = None
if text.startswith("{"):
# Control message (JSON)
try:
ctrl = json.loads(text)
msg_type = ctrl.get("type")
parsed = json.loads(text)
except JSONDecodeError:
parsed = None
if isinstance(parsed, dict) and "type" in parsed:
ctrl = parsed
if msg_type == "resize":
cols = ctrl.get("cols", 80)
rows = ctrl.get("rows", 24)
logger.debug(
"Received resize message for instance %s: %sx%s",
instance_id,
cols,
rows,
)
await session.resize(cols, rows)
elif msg_type == "ack":
char_count = ctrl.get("chars", 0)
if char_count > 0:
session.acknowledge_data(char_count)
elif msg_type == "reset":
# Reset terminal session (scoped to current slot)
logger.debug(
"Resetting terminal session for instance %s (slot=%s)",
session.instance_id,
session_ref.slot_session_id,
)
await websocket.send_json(
{"type": "status", "status": "resetting"}
)
if ctrl is None:
if not _queue_terminal_input(
input_queue, session, text.encode("utf-8")
):
logger.warning(
"Terminal input buffer exceeded for %s", instance_id
)
await websocket.close(
code=1009, reason="Terminal input buffer full"
)
break
continue
# Reset the session scoped to its slot
new_session = await terminal_manager.reset_session(
session.instance_id,
session.container_id,
startup_command=session.startup_command,
session_id=session_ref.slot_session_id,
name=session.name,
)
msg_type = ctrl["type"]
if msg_type == "resize":
cols = ctrl.get("cols", 80)
rows = ctrl.get("rows", 24)
logger.debug(
"Received resize message for instance %s: %sx%s",
instance_id,
cols,
rows,
)
await session.resize(cols, rows)
elif msg_type == "ack":
char_count = ctrl.get("chars", 0)
if char_count > 0:
session.acknowledge_data(char_count)
elif msg_type == "reset":
# Reset terminal session (scoped to current slot)
logger.debug(
"Resetting terminal session for instance %s (slot=%s)",
session.instance_id,
session_ref.slot_session_id,
)
await websocket.send_json(
{"type": "status", "status": "resetting"}
)
# Update the mutable session reference
session_ref.session = new_session
# Reset the session scoped to its slot
new_session = await terminal_manager.reset_session(
session.instance_id,
session.container_id,
startup_command=session.startup_command,
session_id=session_ref.slot_session_id,
name=session.name,
container_user=session.container_user,
)
# Attach to new session
await terminal_manager.attach_websocket(
new_session, websocket
)
await websocket.send_json(
{"type": "status", "status": "connected"}
)
# Update the mutable session reference
session_ref.session = new_session
# Continue the loop with the new session
continue
# Attach to new session
await terminal_manager.attach_websocket(new_session, websocket)
await websocket.send_json(
{"type": "status", "status": "connected"}
)
except json.JSONDecodeError:
# Not a valid JSON control message, treat as regular input
await session.write_input(text.encode("utf-8"))
else:
await session.write_input(text.encode("utf-8"))
# Continue the loop with the new session
continue
elif message["type"] == "websocket.disconnect":
break
except Exception:
except WebSocketDisconnect:
pass
except (RuntimeError, TypeError, ValueError) as exc:
logger.debug("Terminal WebSocket receive loop ended: %s", exc)
finally:
input_writer.cancel()
with suppress(asyncio.CancelledError):
await input_writer
async def _heartbeat_loop(websocket: WebSocket) -> None:
@@ -486,6 +603,7 @@ async def create_terminal_session(
startup_command = tool_type.startup_command if tool_type else None
name = data.get("name")
container_user = await _resolve_container_user(db_session, instance)
try:
session = await terminal_manager.create_session(
@@ -493,6 +611,7 @@ async def create_terminal_session(
instance.container_id,
startup_command=startup_command,
name=name,
container_user=container_user,
)
except MaxSessionsExceededError:
raise HTTPException(
@@ -595,6 +714,7 @@ async def reset_specific_terminal_session(
# Preserve name if possible
live_session = terminal_manager.get_session(str(instance_id), session_id)
name = live_session.name if live_session else None
container_user = await _resolve_container_user(db_session, instance)
new_session = await terminal_manager.reset_session(
instance_id,
@@ -602,6 +722,7 @@ async def reset_specific_terminal_session(
startup_command=startup_command,
session_id=key[1],
name=name,
container_user=container_user,
)
return {
@@ -687,6 +808,7 @@ async def reset_terminal_session(
# Fetch tool type to get startup_command
tool_type = await db_session.get(ToolType, instance.tool_type_id)
startup_command = tool_type.startup_command if tool_type else None
container_user = await _resolve_container_user(db_session, instance)
try:
# Reset the default session
@@ -694,6 +816,7 @@ async def reset_terminal_session(
instance_id,
instance.container_id,
startup_command=startup_command,
container_user=container_user,
)
logger.info(
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/api/src/api/tool
## role
Provides FastAPI REST API endpoints and routers for managing the complete lifecycle of Docker-based tools, including type definitions, instances, sessions, and lifecycle operations.
Provides FastAPI REST API endpoints for managing containerized tools throughout their lifecycle, including type definitions, instance management, session tracking, and Docker operations.
## parent
index: apps/api/src/api/.pi-map.index.md
map: apps/api/src/api/.pi-map.md
+8 -8
View File
@@ -4,17 +4,17 @@ dir: apps/api/src/api/tool
index: apps/api/src/api/tool/.pi-map.index.md
## role
Provides FastAPI REST API endpoints and routers for managing the complete lifecycle of Docker-based tools, including type definitions, instances, sessions, and lifecycle operations.
Provides FastAPI REST API endpoints for managing containerized tools throughout their lifecycle, including type definitions, instance management, session tracking, and Docker operations.
## files
- __init__.py | Aggregates and exports all tool-related API routers from submodules as a unified module interface. | dep: src.api.tool.sessions, src.api.tool.tool_definitions, src.api.tool.tool_instances, src.api.tool.tool_lifecycle, src.api.tool.tool_types
- sessions.py | API endpoint that retrieves all active tool instances (sessions) for the currently authenticated user with related metadata | exp: func:get_user_sessions(user_id, session) → dict, call:_get_user, call:session.execute, call:select(ToolInstance) .where(ToolInstance.owner_id == user_id) .where( ToolInstance.status.in_( ["running", "building", "pending", "stopped", "error"] ) ) .order_by, call:ToolInstance.status.in_, call:ToolInstance.created_at.desc, call:result.scalars().all, call:session.get, call:sessions.append, call:str, call:instance.created_at.isoformat | dep: uuid, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models
- tool_definitions.py | FastAPI router providing CRUD endpoints and manifest compilation for tool definition manifests stored in a database. | exp: class:CreateToolDefinitionRequest, class:UpdateToolDefinitionRequest, func:create_tool_definition(data: CreateToolDefinitionRequest, user_id, session) → dict, call:uuid.UUID, call:session.get, call:session.execute, call:select(ToolDefinitionManifest).where, call:existing.scalar_one_or_none, call:ToolDefinitionManifest, call:session.add, call:session.commit, call:session.refresh, call:logger.info, call:str, call:definition.created_at.isoformat, raise:HTTPException, func:list_tool_definitions(user_id, session, include_bases) → dict, call:select, call:query.where, call:ToolDefinitionManifest.is_base.is_, call:session.execute, call:query.order_by, call:ToolDefinitionManifest.created_at.desc, call:result.scalars().all, call:str, call:d.created_at.isoformat, func:get_tool_definition(definition_id: uuid.UUID, user_id, session) → dict, call:session.get, call:str, call:definition.created_at.isoformat, call:definition.updated_at.isoformat, raise:HTTPException, func:update_tool_definition(definition_id: uuid.UUID, data: UpdateToolDefinitionRequest, user_id, session) → dict, call:session.get, call:session.commit, call:session.refresh, call:logger.info, call:str, call:definition.updated_at.isoformat, raise:HTTPException, func:delete_tool_definition(definition_id: uuid.UUID, user_id, session) → dict, call:session.get, call:session.execute, call:select(ToolType).where, call:result.scalars().all, call:", ".join, call:session.delete, call:session.commit, call:logger.info, call:str, raise:HTTPException, func:compile_tool_definition(definition_id: uuid.UUID, user_id, session) → dict, call:session.get, call:dict, call:resolve_base, call:deep_merge, call:compile_dockerfile, call:compile_entrypoint, call:compute_image_tag, call:compile_compose, call:session.commit, call:str, raise:HTTPException | dep: logging, uuid, fastapi, pydantic, sqlalchemy, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models, src.services.build.manifest_compiler
- tool_instances.py | Provides FastAPI REST endpoints for managing tool instances including CRUD operations, logs, health checks, tunnel recreation, event history, and HTTP proxying to running containers. | exp: func:create_instance(project_id: uuid.UUID, repo_id: uuid.UUID, data: CreateInstanceRequest, user_id, session) → dict, call:_get_user, call:_get_owned_project, call:create_tool_instance, call:str, call:instance.created_at.isoformat, raise:HTTPException, func:list_instances(project_id: uuid.UUID, repo_id: uuid.UUID, user_id, session) → list[dict], call:_get_user, call:_get_owned_project, call:session.execute, call:select(ToolInstance) .where(ToolInstance.repository_id == repo_id) .where(ToolInstance.owner_id == user_id) .order_by, call:ToolInstance.created_at.desc, call:result.scalars().all, call:str, call:i.created_at.isoformat, func:get_instance(project_id: uuid.UUID, repo_id: uuid.UUID, instance_id: uuid.UUID, user_id, session) → dict, call:_get_user, call:_get_owned_project, call:session.get, call:get_container_status, call:str, call:instance.created_at.isoformat, call:instance.last_started_at.isoformat, raise:HTTPException, func:rename_instance(project_id: uuid.UUID, repo_id: uuid.UUID, instance_id: uuid.UUID, data: dict, user_id, session) → dict, call:_get_user, call:_get_owned_project, call:data.get("display_name", "").strip, call:rename_tool_instance, call:str, raise:HTTPException, func:get_instance_logs(project_id: uuid.UUID, repo_id: uuid.UUID, instance_id: uuid.UUID, tail, user_id, session) → dict, call:_get_user, call:_get_owned_project, call:session.get, call:get_container_logs, raise:HTTPException, func:recreate_tunnel_endpoint(project_id: uuid.UUID, repo_id: uuid.UUID, instance_id: uuid.UUID, user_id, session) → dict, call:_get_user, call:_get_owned_project, call:recreate_instance_tunnel, raise:HTTPException, func:check_instance_tunnel_health(project_id: uuid.UUID, repo_id: uuid.UUID, instance_id: uuid.UUID, user_id, session) → dict, call:_get_user, call:_get_owned_project, call:session.get, call:get_container_status, call:instance.probe_result.get, call:"\n".join, call:check_tunnel_health, call:tunnel_health.get, raise:HTTPException, func:get_instance_events(project_id: uuid.UUID, repo_id: uuid.UUID, instance_id: uuid.UUID, limit, user_id, session) → list[dict], call:_get_user, call:_get_owned_project, call:session.get, call:session.execute, call:select(InstanceEvent) .where(InstanceEvent.instance_id == instance_id) .order_by(InstanceEvent.created_at.desc()) .limit, call:InstanceEvent.created_at.desc, call:result.scalars().all, call:str, call:row.created_at.isoformat, raise:HTTPException, func:proxy_to_instance(request: Request, project_id: uuid.UUID, repo_id: uuid.UUID, instance_id: uuid.UUID, path, user_id, session) → Response, call:session.get, call:str, call:dict, call:headers.pop, call:httpx.AsyncClient, call:request.body, call:client.request, call:logger.error, call:response_headers.pop, call:Response, raise:HTTPException | dep: logging, uuid, httpx, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models, src.services.docker, src.services.shared.tunnel, src.schemas.tool, src.services.tool.instance_service
- __init__.py | Aggregates and exports router modules for the Tool API package. | dep: src.api.tool.sessions, src.api.tool.tool_definitions, src.api.tool.tool_instances, src.api.tool.tool_lifecycle, src.api.tool.tool_types
- sessions.py | Provides a FastAPI endpoint to retrieve all active tool sessions (running instances) for the currently authenticated user with related metadata. | exp: func:get_user_sessions(user_id, session) → dict, call:_get_user, call:session.execute, call:select(ToolInstance) .where(ToolInstance.owner_id == user_id) .where( ToolInstance.status.in_( ["running", "building", "pending", "stopped", "error"] ) ) .order_by, call:ToolInstance.status.in_, call:ToolInstance.created_at.desc, call:result.scalars().all, call:session.get, call:sessions.append, call:str, call:instance.created_at.isoformat | dep: uuid, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models
- tool_definitions.py | Provides FastAPI CRUD endpoints for managing tool definition manifests with compilation to Dockerfile and Docker Compose previews. | exp: class:CreateToolDefinitionRequest, class:UpdateToolDefinitionRequest, func:create_tool_definition(data: CreateToolDefinitionRequest, user_id, session) → dict, call:uuid.UUID, call:session.get, call:session.execute, call:select(ToolDefinitionManifest).where, call:existing.scalar_one_or_none, call:ToolDefinitionManifest, call:session.add, call:session.commit, call:session.refresh, call:logger.info, call:str, call:definition.created_at.isoformat, raise:HTTPException, func:list_tool_definitions(user_id, session, include_bases) → dict, call:select, call:query.where, call:ToolDefinitionManifest.is_base.is_, call:session.execute, call:query.order_by, call:ToolDefinitionManifest.created_at.desc, call:result.scalars().all, call:str, call:d.created_at.isoformat, func:get_tool_definition(definition_id: uuid.UUID, user_id, session) → dict, call:session.get, call:str, call:definition.created_at.isoformat, call:definition.updated_at.isoformat, raise:HTTPException, func:update_tool_definition(definition_id: uuid.UUID, data: UpdateToolDefinitionRequest, user_id, session) → dict, call:session.get, call:session.commit, call:session.refresh, call:logger.info, call:str, call:definition.updated_at.isoformat, raise:HTTPException, func:delete_tool_definition(definition_id: uuid.UUID, user_id, session) → dict, call:session.get, call:session.execute, call:select(ToolType).where, call:result.scalars().all, call:", ".join, call:session.delete, call:session.commit, call:logger.info, call:str, raise:HTTPException, func:compile_tool_definition(definition_id: uuid.UUID, user_id, session) → dict, call:session.get, call:dict, call:resolve_base, call:deep_merge, call:compile_dockerfile, call:compile_entrypoint, call:compute_image_tag, call:compile_compose, call:session.commit, call:str, raise:HTTPException | dep: logging, uuid, fastapi, pydantic, sqlalchemy, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models, src.services.build.manifest_compiler
- tool_instances.py | FastAPI router providing REST endpoints for managing tool instances (CRUD, logs, health checks, tunnel management, proxying, and event history) within project repositories. | exp: func:create_instance(project_id: uuid.UUID, repo_id: uuid.UUID, data: CreateInstanceRequest, user_id, session) → dict, call:_get_user, call:_get_owned_project, call:create_tool_instance, call:str, call:instance.created_at.isoformat, raise:HTTPException, func:list_instances(project_id: uuid.UUID, repo_id: uuid.UUID, user_id, session) → list[dict], call:_get_user, call:_get_owned_project, call:session.execute, call:select(ToolInstance) .where(ToolInstance.repository_id == repo_id) .where(ToolInstance.owner_id == user_id) .order_by, call:ToolInstance.created_at.desc, call:result.scalars().all, call:str, call:i.created_at.isoformat, func:get_instance(project_id: uuid.UUID, repo_id: uuid.UUID, instance_id: uuid.UUID, user_id, session) → dict, call:_get_user, call:_get_owned_project, call:session.get, call:get_container_status, call:str, call:instance.created_at.isoformat, call:instance.last_started_at.isoformat, raise:HTTPException, func:rename_instance(project_id: uuid.UUID, repo_id: uuid.UUID, instance_id: uuid.UUID, data: dict, user_id, session) → dict, call:_get_user, call:_get_owned_project, call:data.get("display_name", "").strip, call:rename_tool_instance, call:str, raise:HTTPException, func:get_instance_logs(project_id: uuid.UUID, repo_id: uuid.UUID, instance_id: uuid.UUID, tail, user_id, session) → dict, call:_get_user, call:_get_owned_project, call:session.get, call:get_container_logs, raise:HTTPException, func:recreate_tunnel_endpoint(project_id: uuid.UUID, repo_id: uuid.UUID, instance_id: uuid.UUID, user_id, session) → dict, call:_get_user, call:_get_owned_project, call:recreate_instance_tunnel, raise:HTTPException, func:check_instance_tunnel_health(project_id: uuid.UUID, repo_id: uuid.UUID, instance_id: uuid.UUID, user_id, session) → dict, call:_get_user, call:_get_owned_project, call:session.get, call:get_container_status, call:instance.probe_result.get, call:"\n".join, call:check_tunnel_health, call:tunnel_health.get, raise:HTTPException, func:get_instance_events(project_id: uuid.UUID, repo_id: uuid.UUID, instance_id: uuid.UUID, limit, user_id, session) → list[dict], call:_get_user, call:_get_owned_project, call:session.get, call:session.execute, call:select(InstanceEvent) .where(InstanceEvent.instance_id == instance_id) .order_by(InstanceEvent.created_at.desc()) .limit, call:InstanceEvent.created_at.desc, call:result.scalars().all, call:str, call:row.created_at.isoformat, raise:HTTPException, func:proxy_to_instance(request: Request, project_id: uuid.UUID, repo_id: uuid.UUID, instance_id: uuid.UUID, path, user_id, session) → Response, call:session.get, call:str, call:dict, call:headers.pop, call:httpx.AsyncClient, call:request.body, call:client.request, call:logger.error, call:response_headers.pop, call:Response, raise:HTTPException | dep: logging, uuid, httpx, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models, src.services.docker, src.services.shared.tunnel, src.schemas.tool, src.services.tool.instance_service
- tool_lifecycle.py | FastAPI router providing REST endpoints for managing Docker-based tool instance lifecycle operations (start, stop, restart, delete). | exp: func:start_instance(project_id: uuid.UUID, repo_id: uuid.UUID, instance_id: uuid.UUID, data, user_id, session) → dict, call:_get_user, call:_get_owned_project, call:start_tool_instance, raise:HTTPException, func:stop_instance(project_id: uuid.UUID, repo_id: uuid.UUID, instance_id: uuid.UUID, user_id, session) → dict, call:_get_user, call:_get_owned_project, call:stop_tool_instance, raise:HTTPException, func:restart_instance(project_id: uuid.UUID, repo_id: uuid.UUID, instance_id: uuid.UUID, user_id, session) → dict, call:_get_user, call:_get_owned_project, call:restart_tool_instance, raise:HTTPException, func:delete_instance(project_id: uuid.UUID, repo_id: uuid.UUID, instance_id: uuid.UUID, force, user_id, session) → None, call:_get_user, call:_get_owned_project, call:delete_tool_instance, call:str, call:detail.lower, raise:HTTPException | dep: logging, uuid, fastapi, sqlalchemy.ext.asyncio, src.auth.dependencies, src.schemas.tool, src.services.tool.instance_service
- tool_types.py | Defines FastAPI routes for CRUD operations and validation of Docker tool types with compose/dockerfile/manifest definitions. | exp: func:_require_admin(user: User) → None, func:create_tool_type(data: ToolTypeCreate, user_id, session) → ToolType, call:_get_user, call:_require_admin, call:session.scalar, call:select(ToolType).where, call:ToolType, call:session.add, call:session.commit, call:session.refresh, raise:HTTPException, func:list_tool_types(user_id, session) → list[ToolType], call:_get_user, call:session.execute, call:select(ToolType).order_by, call:list, call:result.scalars().all, func:get_tool_type(tool_type_id: uuid.UUID, user_id, session) → ToolType, call:_get_user, call:session.get, raise:HTTPException, func:update_tool_type(tool_type_id: uuid.UUID, data: ToolTypeUpdate, user_id, session) → ToolType, call:_get_user, call:_require_admin, call:session.get, call:data.model_dump, call:update_data.get, call:validate_compose_yaml, call:check_port_exposed, call:validate_required_variables, call:update_data.items, call:setattr, call:session.commit, call:session.refresh, raise:HTTPException, func:validate_tool_type_template(data: ToolTypeValidateRequest, user_id, session) → dict, call:_get_user, call:errors.append, call:validate_compose_yaml, call:str, call:data.dockerfile_template.strip().startswith, call:len, func:validate_tool_type(tool_type_id: uuid.UUID, user_id, session) → dict, call:_get_user, call:session.get, call:errors.append, call:validate_compose_yaml, call:str, call:tool_type.dockerfile_template.strip().startswith, call:len, raise:HTTPException, func:delete_tool_type(tool_type_id: uuid.UUID, user_id, session) → None, call:_get_user, call:_require_admin, call:session.get, call:session.delete, call:session.commit, raise:HTTPException | dep: uuid, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, src.api.tool.tool_types_validation, src.auth.dependencies, src.models, src.models.user, src.schemas.tool
- tool_types_validation.py | Validates Docker Compose YAML templates by sanitizing template variables, parsing YAML, checking required structure, verifying port exposure, and ensuring required variables are present. | exp: func:sanitize_template_vars(template: str) → str, call:re.sub, func:validate_compose_yaml(template: str) → dict, call:sanitize_template_vars, call:yaml.safe_load, call:isinstance, raise:ValueError, func:check_port_exposed(parsed: dict, port: int) → bool, call:str, call:isinstance, call:parsed["services"].values, func:validate_required_variables(template: str, variables: list[str]) → None, raise:HTTPException | dep: re, yaml, fastapi, fastapi.HTTPException, fastapi.status
- tool_types.py | Defines FastAPI REST endpoints for CRUD operations on tool types with validation for Docker Compose, Dockerfile, and manifest definitions. | exp: func:_require_admin(user: User) → None, func:create_tool_type(data: ToolTypeCreate, user_id, session) → ToolType, call:_get_user, call:_require_admin, call:session.scalar, call:select(ToolType).where, call:ToolType, call:session.add, call:session.commit, call:session.refresh, raise:HTTPException, func:list_tool_types(user_id, session) → list[ToolType], call:_get_user, call:session.execute, call:select(ToolType).order_by, call:list, call:result.scalars().all, func:get_tool_type(tool_type_id: uuid.UUID, user_id, session) → ToolType, call:_get_user, call:session.get, raise:HTTPException, func:update_tool_type(tool_type_id: uuid.UUID, data: ToolTypeUpdate, user_id, session) → ToolType, call:_get_user, call:_require_admin, call:session.get, call:data.model_dump, call:update_data.get, call:validate_compose_yaml, call:check_port_exposed, call:validate_required_variables, call:update_data.items, call:setattr, call:session.commit, call:session.refresh, raise:HTTPException, func:validate_tool_type_template(data: ToolTypeValidateRequest, user_id, session) → dict, call:_get_user, call:errors.append, call:validate_compose_yaml, call:str, call:data.dockerfile_template.strip().startswith, call:len, func:validate_tool_type(tool_type_id: uuid.UUID, user_id, session) → dict, call:_get_user, call:session.get, call:errors.append, call:validate_compose_yaml, call:str, call:tool_type.dockerfile_template.strip().startswith, call:len, raise:HTTPException, func:delete_tool_type(tool_type_id: uuid.UUID, user_id, session) → None, call:_get_user, call:_require_admin, call:session.get, call:session.delete, call:session.commit, raise:HTTPException | dep: uuid, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, src.api.tool.tool_types_validation, src.auth.dependencies, src.models, src.models.user, src.schemas.tool
- tool_types_validation.py | Provides validation utilities for Docker Compose templates including YAML parsing, port exposure checking, and required template variable verification. | exp: func:sanitize_template_vars(template: str) → str, call:re.sub, func:validate_compose_yaml(template: str) → dict, call:sanitize_template_vars, call:yaml.safe_load, call:isinstance, raise:ValueError, func:check_port_exposed(parsed: dict, port: int) → bool, call:str, call:isinstance, call:parsed["services"].values, call:port_mapping.split, call:mapping.split, func:validate_required_variables(template: str, variables: list[str]) → None, raise:HTTPException | dep: re, yaml, fastapi
## arch
Modular FastAPI router composition pattern with separated concerns across CRUD operations, validation, lifecycle management, and proxying, aggregated through `__init__.py` for unified module interface.
Modular FastAPI router pattern with domain-driven separation of concerns (types, instances, sessions, lifecycle) and Docker-centric validation/compilation utilities for container orchestration.
## tags
get, call:, tool, raise:httpexception, user, instance, call:str, call:session.get
## symbols
@@ -60,8 +60,14 @@ def check_port_exposed(parsed: dict, port: int) -> bool:
for service_config in parsed["services"].values():
if isinstance(service_config, dict) and "ports" in service_config:
for port_mapping in service_config["ports"]:
if isinstance(port_mapping, str) and port_str in port_mapping:
return True
if isinstance(port_mapping, str):
# Port mappings can be "host:container", "ip:host:container",
# "container", or ".../protocol". The container port is the
# last numeric segment before any protocol suffix.
mapping = port_mapping.split("/")[0]
parts = mapping.split(":")
if parts[-1] == port_str:
return True
elif isinstance(port_mapping, int) and port_mapping == port:
return True
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/api/src/api/user
## role
Provides user-facing API endpoints for authentication, profile management, and SSH key operations in a FastAPI backend application.
Provides user-facing API endpoints for authentication, profile management, and SSH key operations in the FastAPI backend.
## parent
index: apps/api/src/api/.pi-map.index.md
map: apps/api/src/api/.pi-map.md
+6 -6
View File
@@ -4,14 +4,14 @@ dir: apps/api/src/api/user
index: apps/api/src/api/user/.pi-map.index.md
## role
Provides user-facing API endpoints for authentication, profile management, and SSH key operations in a FastAPI backend application.
Provides user-facing API endpoints for authentication, profile management, and SSH key operations in the FastAPI backend.
## files
- __init__.py | Aggregates and exports user-related API routers from submodules for easy importing | dep: src.api.user.auth, src.api.user.ssh_keys, src.api.user.users
- auth.py | Implements OAuth2/OIDC authentication endpoints for a FastAPI application using Authentik as the identity provider, including login initiation, callback handling, logout, and current user retrieval. | exp: func:get_db_session() → AsyncGenerator[AsyncSession, None], call:SessionLocal, func:login(next) → RedirectResponse, call:Settings, call:token_urlsafe, call:build_login_redirect_url, call:logger.debug, call:RedirectResponse, call:response.set_cookie, func:callback(code: str, state: str, auth_state, auth_next, session) → RedirectResponse, call:logger.debug, call:logger.warning, call:Settings, call:httpx.AsyncClient, call:exchange_code_for_tokens, call:logger.info, call:logger.error, call:fetch_user_info, call:str, call:user_info.get, call:session.scalar, call:select(User).where, call:User, call:session.add, call:session.commit, call:session.refresh, call:create_session_cookie, call:build_cookie_options, call:cast, call:bool, call:cookie_options.get, call:RedirectResponse, call:redirect_response.set_cookie, call:redirect_response.delete_cookie, raise:HTTPException, func:logout(response: Response) → dict[str, str], call:Settings, call:build_cookie_options, call:cast, call:bool, call:str, call:cookie_options.get, call:response.delete_cookie, func:me(session_cookie, session) → dict[str, Any], call:logger.debug, call:bool, call:logger.warning, call:Settings, call:decode_session_cookie, call:session.get, call:logger.info, call:str, raise:HTTPException | dep: logging, secrets, typing, httpx, fastapi, fastapi.responses, sqlalchemy, sqlalchemy.ext.asyncio, src.auth.cookies, src.auth.oidc, src.auth.session, src.config, src.database, src.models.user
- ssh_keys.py | Implements a FastAPI router for managing Ed25519 SSH key pairs, including generation, storage with encrypted private keys, listing, deletion, and cryptographic signing/verification operations. | exp: func:_get_fernet() → Fernet, call:Settings, call:hashlib.sha256(settings.session_secret.encode()).digest, call:settings.session_secret.encode, call:base64.urlsafe_b64encode, call:Fernet, func:generate_ssh_key_pair() → tuple[str, str], call:Ed25519PrivateKey.generate, call:private_key.public_key, call:private_key.private_bytes, call:serialization.NoEncryption, call:public_key.public_bytes, call:private_bytes.decode, call:public_bytes.decode, func:create_ssh_key(data: SSHKeyCreate, user_id, session) → SSHKey, call:_get_user, call:generate_ssh_key_pair, call:_get_fernet, call:fernet.encrypt(private_key.encode()).decode, call:private_key.encode, call:SSHKey, call:session.add, call:session.commit, call:session.refresh, func:list_ssh_keys(user_id, session) → list[SSHKey], call:_get_user, call:session.execute, call:select(SSHKey).where, call:list, call:result.scalars().all, func:delete_ssh_key(key_id: uuid.UUID, user_id, session) → None, call:_get_user, call:session.get, call:session.delete, call:session.commit, raise:HTTPException, func:sign_payload(key_id: uuid.UUID, data: SignPayloadRequest, user_id, session) → SignatureResponse, call:_get_user, call:session.get, call:_get_fernet, call:fernet.decrypt(ssh_key.private_key_encrypted.encode()).decode, call:ssh_key.private_key_encrypted.encode, call:serialization.load_ssh_private_key, call:private_key_pem.encode, call:private_key.sign, call:data.payload.encode, call:SignatureResponse, call:base64.b64encode(signature).decode, raise:HTTPException, func:verify_signature(key_id: uuid.UUID, data: VerifySignatureRequest, user_id, session) → VerifySignatureResponse, call:_get_user, call:session.get, call:serialization.load_ssh_public_key, call:ssh_key.public_key.encode, call:base64.b64decode, call:public_key.verify, call:data.payload.encode, call:VerifySignatureResponse, raise:HTTPException | dep: base64, uuid, cryptography.fernet, cryptography.hazmat.primitives, cryptography.hazmat.primitives.asymmetric.ed25519, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, src.auth.dependencies, src.config, src.models, src.schemas.project, hashlib, cryptography
- users.py | Implements FastAPI endpoints for authenticated user profile management including retrieval, updates, and avatar uploads. | exp: func:get_profile(user_id, session) → User, call:_get_user, func:update_profile(data: UserProfileUpdate, user_id, session) → User, call:_get_user, call:len, call:data.name.strip, call:data.email.strip, call:session.commit, call:session.refresh, raise:HTTPException, func:upload_avatar(file: UploadFile, user_id, session) → User, call:_get_user, call:file.read, call:len, call:Path, call:old_path.exists, call:old_path.unlink, call:filename_part.split(".")[-1].lower, call:uuid.uuid4, call:file_path.write_bytes, call:session.commit, call:session.refresh, raise:HTTPException | dep: uuid, pathlib, fastapi, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models.user, src.schemas.user
- __init__.py | Aggregates and exports user-related API routers from submodules for easy importing. | dep: src.api.user.auth, src.api.user.ssh_keys, src.api.user.users
- auth.py | Implements OAuth2/OIDC authentication endpoints for a FastAPI application using Authentik as the identity provider, including login initiation, callback handling, session management, logout, and current user retrieval. | exp: func:get_db_session() → AsyncGenerator[AsyncSession, None], call:SessionLocal, func:login(next) → RedirectResponse, call:Settings, call:token_urlsafe, call:build_login_redirect_url, call:logger.debug, call:RedirectResponse, call:response.set_cookie, func:callback(code: str, state: str, auth_state, auth_next, session) → RedirectResponse, call:logger.debug, call:logger.warning, call:Settings, call:httpx.AsyncClient, call:exchange_code_for_tokens, call:logger.info, call:logger.error, call:fetch_user_info, call:str, call:user_info.get, call:session.scalar, call:select(User).where, call:User, call:session.add, call:session.commit, call:session.refresh, call:create_session_cookie, call:build_cookie_options, call:cast, call:bool, call:cookie_options.get, call:RedirectResponse, call:redirect_response.set_cookie, call:redirect_response.delete_cookie, raise:HTTPException, func:logout(response: Response) → dict[str, str], call:Settings, call:build_cookie_options, call:cast, call:bool, call:str, call:cookie_options.get, call:response.delete_cookie, func:me(session_cookie, session) → dict[str, Any], call:logger.debug, call:bool, call:logger.warning, call:Settings, call:decode_session_cookie, call:session.get, call:logger.info, call:str, raise:HTTPException | dep: logging, secrets, typing, httpx, fastapi, fastapi.responses, sqlalchemy, sqlalchemy.ext.asyncio, src.auth.cookies, src.auth.oidc, src.auth.session, src.config, src.database, src.models.user
- ssh_keys.py | FastAPI router for managing Ed25519 SSH keys including generation, storage with encrypted private keys, signing payloads, and signature verification | exp: func:_get_fernet() → Fernet, call:Settings, call:hashlib.sha256(settings.session_secret.encode()).digest, call:settings.session_secret.encode, call:base64.urlsafe_b64encode, call:Fernet, func:generate_ssh_key_pair() → tuple[str, str], call:Ed25519PrivateKey.generate, call:private_key.public_key, call:private_key.private_bytes, call:serialization.NoEncryption, call:public_key.public_bytes, call:private_bytes.decode, call:public_bytes.decode, func:create_ssh_key(data: SSHKeyCreate, user_id, session) → SSHKey, call:_get_user, call:generate_ssh_key_pair, call:_get_fernet, call:fernet.encrypt(private_key.encode()).decode, call:private_key.encode, call:SSHKey, call:session.add, call:session.commit, call:session.refresh, func:list_ssh_keys(user_id, session) → list[SSHKey], call:_get_user, call:session.execute, call:select(SSHKey).where, call:list, call:result.scalars().all, func:delete_ssh_key(key_id: uuid.UUID, user_id, session) → None, call:_get_user, call:session.get, call:session.delete, call:session.commit, raise:HTTPException, func:sign_payload(key_id: uuid.UUID, data: SignPayloadRequest, user_id, session) → SignatureResponse, call:_get_user, call:session.get, call:_get_fernet, call:fernet.decrypt(ssh_key.private_key_encrypted.encode()).decode, call:ssh_key.private_key_encrypted.encode, call:serialization.load_ssh_private_key, call:private_key_pem.encode, call:private_key.sign, call:data.payload.encode, call:SignatureResponse, call:base64.b64encode(signature).decode, raise:HTTPException, func:verify_signature(key_id: uuid.UUID, data: VerifySignatureRequest, user_id, session) → VerifySignatureResponse, call:_get_user, call:session.get, call:serialization.load_ssh_public_key, call:ssh_key.public_key.encode, call:base64.b64decode, call:public_key.verify, call:data.payload.encode, call:VerifySignatureResponse, raise:HTTPException | dep: base64, uuid, cryptography.fernet, cryptography.hazmat.primitives, cryptography.hazmat.primitives.asymmetric.ed25519, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, src.auth.dependencies, src.config, src.models, src.schemas.project, hashlib, cryptography
- users.py | Implements FastAPI endpoints for user profile management including retrieval, updates, and avatar uploads with file validation. | exp: func:get_profile(user_id, session) → User, call:_get_user, func:update_profile(data: UserProfileUpdate, user_id, session) → User, call:_get_user, call:len, call:data.name.strip, call:data.email.strip, call:session.commit, call:session.refresh, raise:HTTPException, func:upload_avatar(file: UploadFile, user_id, session) → User, call:_get_user, call:file.read, call:len, call:Path, call:old_path.exists, call:old_path.unlink, call:filename_part.split(".")[-1].lower, call:uuid.uuid4, call:file_path.write_bytes, call:session.commit, call:session.refresh, raise:HTTPException | dep: uuid, pathlib, fastapi, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models.user, src.schemas.user, sqlalchemy
## arch
Modular FastAPI router pattern with separate submodule files per domain concern (auth, SSH keys, users), aggregated via __init__.py, using OAuth2/OIDC external identity provider (Authentik) and encrypted key storage for cryptographic operations.
Modular FastAPI router pattern with separate submodules for distinct concerns (auth, profile, SSH keys), using OAuth2/OIDC external identity provider (Authentik), encrypted key storage, and file upload handling.
## tags
user, get, call:, key, ssh, src, cookie, raise:httpexception
## symbols
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/api/src/api/workspace
## role
Provides FastAPI REST endpoints for workspace management, file operations, and git integration in user-scoped development environments.
Provides REST API endpoints for managing user workspaces, including file operations, Git version control, tool instances, and workspace lifecycle management.
## parent
index: apps/api/src/api/.pi-map.index.md
map: apps/api/src/api/.pi-map.md
+7 -7
View File
@@ -4,15 +4,15 @@ dir: apps/api/src/api/workspace
index: apps/api/src/api/workspace/.pi-map.index.md
## role
Provides FastAPI REST endpoints for workspace management, file operations, and git integration in user-scoped development environments.
Provides REST API endpoints for managing user workspaces, including file operations, Git version control, tool instances, and workspace lifecycle management.
## files
- __init__.py | Aggregates and re-exports workspace API router modules for centralized access | dep: src.api.workspace.workspace_files, src.api.workspace.workspace_git, src.api.workspace.workspace_instances, src.api.workspace.workspaces
- workspace_files.py | Provides FastAPI endpoints for listing, reading, and writing files within user workspaces with optional git commit integration. | exp: func:_get_workspace(session: AsyncSession, workspace_id: uuid.UUID, user_id: uuid.UUID) → Workspace, call:session.execute, call:select(Workspace).where, call:result.scalar_one_or_none, raise:HTTPException, func:list_files(workspace_id: uuid.UUID, path, user_id, session) → dict, call:_get_workspace, call:FileService, call:service.list_directory, raise:HTTPException, func:get_file_content(workspace_id: uuid.UUID, path: str, user_id, session) → dict, call:_get_workspace, call:FileService, call:service.read_file, raise:HTTPException, func:write_file(workspace_id: uuid.UUID, data: dict, user_id, session) → dict, call:_get_workspace, call:FileService, call:data.get("path", "").strip, call:data.get("message", "").strip, call:service.write_file, call:GitOperations, call:git.commit, raise:HTTPException | dep: uuid, fastapi, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models, src.services.shared.file_service, sqlalchemy, src.services.git.git_operations
- workspace_git.py | Provides FastAPI REST endpoints for git operations (status, branches, commit, push, pull, fetch, checkout, history) scoped to user workspaces. | exp: func:_get_workspace(session: AsyncSession, workspace_id: uuid.UUID, user_id: uuid.UUID) → Workspace, call:session.execute, call:select(Workspace).where, call:result.scalar_one_or_none, raise:HTTPException, func:git_status(workspace_id: uuid.UUID, user_id, session) → dict, call:_get_workspace, call:GitOperations, call:git.status, raise:HTTPException, func:git_branches(workspace_id: uuid.UUID, user_id, session) → dict, call:_get_workspace, call:GitOperations, call:git.branches, raise:HTTPException, func:git_commit(workspace_id: uuid.UUID, data: dict, user_id, session) → dict, call:_get_workspace, call:data.get("message", "").strip, call:GitOperations, call:git.commit, raise:HTTPException, func:git_push(workspace_id: uuid.UUID, user_id, session) → dict, call:_get_workspace, call:GitOperations, call:git.push, raise:HTTPException, func:git_pull(workspace_id: uuid.UUID, user_id, session) → dict, call:_get_workspace, call:GitOperations, call:git.pull, raise:HTTPException, func:git_fetch(workspace_id: uuid.UUID, user_id, session) → dict, call:_get_workspace, call:GitOperations, call:git.fetch, raise:HTTPException, func:git_checkout(workspace_id: uuid.UUID, data: dict, user_id, session) → dict, call:_get_workspace, call:data.get("branch", "").strip, call:GitOperations, call:git.checkout, call:session.commit, raise:HTTPException, func:git_history(workspace_id: uuid.UUID, path, limit, user_id, session) → dict, call:_get_workspace, call:GitOperations, call:git.history, raise:HTTPException | dep: uuid, fastapi, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models, src.services.git.git_operations, sqlalchemy
- workspace_instances.py | Defines API endpoints for listing tool instances associated with a specific workspace, verifying workspace ownership. | exp: func:_get_workspace(session: AsyncSession, workspace_id: uuid.UUID, user_id: uuid.UUID) → Workspace, call:session.execute, call:select(Workspace).where, call:result.scalar_one_or_none, raise:HTTPException, func:list_workspace_instances(workspace_id: uuid.UUID, user_id, session) → list[dict], call:_get_workspace, call:session.execute, call:select(ToolInstance) .where(ToolInstance.workspace_id == workspace_id) .order_by, call:ToolInstance.created_at.desc, call:result.scalars().all, call:str, call:i.created_at.isoformat | dep: uuid, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models
- workspaces.py | Provides FastAPI CRUD endpoints for managing Git repository workspaces with nested and top-level URL structures, including listing, creating, updating, deleting, and syncing workspaces with instance count tracking. | exp: func:list_all_workspaces(user_id, session) → list[dict], call:select(func.count(ToolInstance.id)) .where(ToolInstance.workspace_id == Workspace.id) .correlate(Workspace) .scalar_subquery, call:func.count, call:session.execute, call:select( Workspace, GitRepository.name.label("repo_name"), GitRepository.project_id, GitRepository.ssh_key_id.label("repo_ssh_key_id"), instance_count.label("instance_count"), ) .join(GitRepository, Workspace.repo_id == GitRepository.id) .where(Workspace.user_id == user_id) .order_by, call:GitRepository.name.label, call:GitRepository.ssh_key_id.label, call:instance_count.label, call:Workspace.created_at.desc, call:result.all, call:str, call:ws.last_sync_at.isoformat, call:ws.created_at.isoformat, call:ws.updated_at.isoformat, func:delete_workspace_top_level(workspace_id: uuid.UUID, force, user_id, session) → dict, call:session.get, call:WorkspaceManager, call:manager.delete, call:session.commit, call:session.rollback, call:logger.error, raise:HTTPException, func:create_workspace_top_level(data: dict, user_id, session) → dict, call:data.get("repo_id", "").strip, call:uuid.UUID, call:session.get, call:data.get("name", "").strip, call:data.get("branch", "main").strip, call:WorkspaceManager, call:manager.create, call:session.add, call:session.commit, call:session.rollback, call:logger.error, call:session.refresh, call:str, call:workspace.created_at.isoformat, raise:HTTPException, func:list_workspaces(project_id: uuid.UUID, repo_id: uuid.UUID, user_id, session) → list[dict], call:_get_repo, call:select(func.count(ToolInstance.id)) .where(ToolInstance.workspace_id == Workspace.id) .correlate(Workspace) .scalar_subquery, call:func.count, call:session.execute, call:select( Workspace, instance_count.label("instance_count"), ) .where(Workspace.repo_id == repo_id) .order_by, call:instance_count.label, call:Workspace.created_at.desc, call:result.all, call:str, call:ws.last_sync_at.isoformat, call:ws.created_at.isoformat, call:ws.updated_at.isoformat, func:create_workspace(project_id: uuid.UUID, repo_id: uuid.UUID, data: dict, user_id, session) → dict, call:_get_repo, call:data.get("name", "").strip, call:data.get("branch", "main").strip, call:WorkspaceManager, call:manager.create, call:session.add, call:session.commit, call:session.rollback, call:logger.error, call:session.refresh, call:str, call:workspace.created_at.isoformat, raise:HTTPException, func:get_workspace_detail(project_id: uuid.UUID, repo_id: uuid.UUID, workspace_id: uuid.UUID, user_id, session) → dict, call:_get_repo, call:_get_workspace, call:session.execute, call:select(func.count(ToolInstance.id)).where, call:func.count, call:result.scalar, call:str, call:workspace.last_sync_at.isoformat, call:workspace.created_at.isoformat, call:workspace.updated_at.isoformat, func:update_workspace(project_id: uuid.UUID, repo_id: uuid.UUID, workspace_id: uuid.UUID, data: dict, user_id, session) → dict, call:_get_repo, call:_get_workspace, call:data.get("name", "").strip, call:data.get("branch", "").strip, call:session.commit, call:session.rollback, call:logger.error, call:str, raise:HTTPException, func:delete_workspace(project_id: uuid.UUID, repo_id: uuid.UUID, workspace_id: uuid.UUID, force, user_id, session) → dict, call:_get_repo, call:_get_workspace, call:WorkspaceManager, call:manager.delete, call:session.commit, call:session.rollback, call:logger.error, raise:HTTPException, func:sync_workspace(project_id: uuid.UUID, repo_id: uuid.UUID, workspace_id: uuid.UUID, user_id, session) → dict, call:_get_repo, call:_get_workspace, call:WorkspaceManager, call:manager.sync, call:session.commit, call:workspace.last_sync_at.isoformat, raise:HTTPException, func:_get_repo(session: AsyncSession, repo_id: uuid.UUID, project_id: uuid.UUID, user_id: uuid.UUID) → GitRepository, call:session.execute, call:select(GitRepository) .where( GitRepository.id == repo_id, GitRepository.project_id == project_id, ) .options, call:selectinload, call:result.scalar_one_or_none, raise:HTTPException, func:_get_workspace(session: AsyncSession, workspace_id: uuid.UUID, repo_id: uuid.UUID) → Workspace, call:session.execute, call:select(Workspace).where, call:result.scalar_one_or_none, raise:HTTPException | dep: logging, uuid, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, sqlalchemy.orm, src.auth.dependencies, src.models, src.services.shared.workspace_manager
- __init__.py | Aggregates and re-exports workspace-related API routers from submodules for centralized access. | dep: src.api.workspace.workspace_files, src.api.workspace.workspace_git, src.api.workspace.workspace_instances, src.api.workspace.workspaces
- workspace_files.py | Provides FastAPI endpoints for listing, reading, and writing files within user-owned workspaces with optional Git commit integration. | exp: func:_get_workspace(session: AsyncSession, workspace_id: uuid.UUID, user_id: uuid.UUID) → Workspace, call:session.execute, call:select(Workspace).where, call:result.scalar_one_or_none, raise:HTTPException, func:list_files(workspace_id: uuid.UUID, path, user_id, session) → dict, call:_get_workspace, call:FileService, call:service.list_directory, raise:HTTPException, func:get_file_content(workspace_id: uuid.UUID, path: str, user_id, session) → dict, call:_get_workspace, call:FileService, call:service.read_file, raise:HTTPException, func:write_file(workspace_id: uuid.UUID, data: dict, user_id, session) → dict, call:_get_workspace, call:FileService, call:data.get("path", "").strip, call:data.get("message", "").strip, call:service.write_file, call:GitOperations, call:git.commit, raise:HTTPException | dep: uuid, fastapi, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models, src.services.shared.file_service, sqlalchemy, src.services.git.git_operations
- workspace_git.py | Provides FastAPI REST endpoints for executing git operations (status, branches, commit, push, pull, fetch, checkout, history) on user workspaces with authentication and database persistence. | exp: func:_get_workspace(session: AsyncSession, workspace_id: uuid.UUID, user_id: uuid.UUID) → Workspace, call:session.execute, call:select(Workspace).where, call:result.scalar_one_or_none, raise:HTTPException, func:git_status(workspace_id: uuid.UUID, user_id, session) → dict, call:_get_workspace, call:GitOperations, call:git.status, raise:HTTPException, func:git_branches(workspace_id: uuid.UUID, user_id, session) → dict, call:_get_workspace, call:GitOperations, call:git.branches, raise:HTTPException, func:git_commit(workspace_id: uuid.UUID, data: dict, user_id, session) → dict, call:_get_workspace, call:data.get("message", "").strip, call:GitOperations, call:git.commit, raise:HTTPException, func:git_push(workspace_id: uuid.UUID, user_id, session) → dict, call:_get_workspace, call:GitOperations, call:git.push, raise:HTTPException, func:git_pull(workspace_id: uuid.UUID, user_id, session) → dict, call:_get_workspace, call:GitOperations, call:git.pull, raise:HTTPException, func:git_fetch(workspace_id: uuid.UUID, user_id, session) → dict, call:_get_workspace, call:GitOperations, call:git.fetch, raise:HTTPException, func:git_checkout(workspace_id: uuid.UUID, data: dict, user_id, session) → dict, call:_get_workspace, call:data.get("branch", "").strip, call:GitOperations, call:git.checkout, call:session.commit, raise:HTTPException, func:git_history(workspace_id: uuid.UUID, path, limit, user_id, session) → dict, call:_get_workspace, call:GitOperations, call:git.history, raise:HTTPException | dep: uuid, fastapi, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models, src.services.git.git_operations, sqlalchemy
- workspace_instances.py | Defines FastAPI endpoints for creating and listing tool instances associated with a specific workspace. | exp: func:_get_workspace(session: AsyncSession, workspace_id: uuid.UUID, user_id: uuid.UUID) → Workspace, call:session.execute, call:select(Workspace).where, call:result.scalar_one_or_none, raise:HTTPException, func:create_workspace_instance(workspace_id: uuid.UUID, data: CreateWorkspaceInstanceRequest, user_id, session) → dict, call:_get_workspace, call:session.get, call:CreateInstanceRequest, call:str, call:create_tool_instance, call:instance.created_at.isoformat, raise:HTTPException, func:list_workspace_instances(workspace_id: uuid.UUID, user_id, session) → list[dict], call:_get_workspace, call:session.execute, call:select(ToolInstance) .where(ToolInstance.workspace_id == workspace_id) .order_by, call:ToolInstance.created_at.desc, call:result.scalars().all, call:str, call:i.created_at.isoformat | dep: uuid, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models, src.schemas.tool, src.services.tool.instance_service
- workspaces.py | FastAPI router providing CRUD endpoints for managing Git workspaces with repository integration, instance tracking, and sync capabilities. | exp: func:list_all_workspaces(user_id, session) → list[dict], call:select(func.count(ToolInstance.id)) .where(ToolInstance.workspace_id == Workspace.id) .correlate(Workspace) .scalar_subquery, call:func.count, call:session.execute, call:select( Workspace, GitRepository, instance_count.label("instance_count"), ) .join(GitRepository, Workspace.repo_id == GitRepository.id) .options(selectinload(GitRepository.project)) .where(Workspace.user_id == user_id) .order_by, call:instance_count.label, call:selectinload, call:Workspace.created_at.desc, call:result.all, call:str, call:ws.last_sync_at.isoformat, call:ws.created_at.isoformat, call:ws.updated_at.isoformat, func:delete_workspace_top_level(workspace_id: uuid.UUID, force, user_id, session) → dict, call:session.get, call:WorkspaceManager, call:manager.delete, call:session.commit, call:session.rollback, call:logger.error, raise:HTTPException, func:create_workspace_top_level(data: dict, user_id, session) → dict, call:data.get("repo_id", "").strip, call:uuid.UUID, call:session.get, call:data.get("name", "").strip, call:data.get("branch", "main").strip, call:WorkspaceManager, call:manager.create, call:session.add, call:session.commit, call:session.rollback, call:logger.error, call:session.refresh, call:str, call:workspace.created_at.isoformat, raise:HTTPException, func:list_workspaces(project_id: uuid.UUID, repo_id: uuid.UUID, user_id, session) → list[dict], call:_get_repo, call:select(func.count(ToolInstance.id)) .where(ToolInstance.workspace_id == Workspace.id) .correlate(Workspace) .scalar_subquery, call:func.count, call:session.execute, call:select( Workspace, instance_count.label("instance_count"), ) .where(Workspace.repo_id == repo_id) .order_by, call:instance_count.label, call:Workspace.created_at.desc, call:result.all, call:str, call:ws.last_sync_at.isoformat, call:ws.created_at.isoformat, call:ws.updated_at.isoformat, func:create_workspace(project_id: uuid.UUID, repo_id: uuid.UUID, data: dict, user_id, session) → dict, call:_get_repo, call:data.get("name", "").strip, call:data.get("branch", "main").strip, call:WorkspaceManager, call:manager.create, call:session.add, call:session.commit, call:session.rollback, call:logger.error, call:session.refresh, call:str, call:workspace.created_at.isoformat, raise:HTTPException, func:get_workspace_detail(project_id: uuid.UUID, repo_id: uuid.UUID, workspace_id: uuid.UUID, user_id, session) → dict, call:_get_repo, call:_get_workspace, call:session.execute, call:select(func.count(ToolInstance.id)).where, call:func.count, call:result.scalar, call:str, call:workspace.last_sync_at.isoformat, call:workspace.created_at.isoformat, call:workspace.updated_at.isoformat, func:update_workspace(project_id: uuid.UUID, repo_id: uuid.UUID, workspace_id: uuid.UUID, data: dict, user_id, session) → dict, call:_get_repo, call:_get_workspace, call:data.get("name", "").strip, call:data.get("branch", "").strip, call:session.commit, call:session.rollback, call:logger.error, call:str, raise:HTTPException, func:delete_workspace(project_id: uuid.UUID, repo_id: uuid.UUID, workspace_id: uuid.UUID, force, user_id, session) → dict, call:_get_repo, call:_get_workspace, call:WorkspaceManager, call:manager.delete, call:session.commit, call:session.rollback, call:logger.error, raise:HTTPException, func:sync_workspace(project_id: uuid.UUID, repo_id: uuid.UUID, workspace_id: uuid.UUID, user_id, session) → dict, call:_get_repo, call:_get_workspace, call:WorkspaceManager, call:manager.sync, call:session.commit, call:workspace.last_sync_at.isoformat, raise:HTTPException, func:_get_repo(session: AsyncSession, repo_id: uuid.UUID, project_id: uuid.UUID, user_id: uuid.UUID) → GitRepository, call:session.execute, call:select(GitRepository) .where( GitRepository.id == repo_id, GitRepository.project_id == project_id, ) .options, call:selectinload, call:result.scalar_one_or_none, raise:HTTPException, func:_get_workspace(session: AsyncSession, workspace_id: uuid.UUID, repo_id: uuid.UUID) → Workspace, call:session.execute, call:select(Workspace).where, call:result.scalar_one_or_none, raise:HTTPException | dep: logging, uuid, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, sqlalchemy.orm, src.auth.dependencies, src.models, src.services.shared.workspace_manager
## arch
Modular router composition with domain-separated endpoint modules (files, git, instances, workspaces) using FastAPI dependency injection for workspace ownership verification and nested resource URL patterns.
Modular FastAPI router decomposition with domain-driven vertical slicing (files/git/instances/workspaces as separate modules), aggregated through __init__.py barrel exports, combining database persistence with external Git repository integration.
## tags
workspace, get, raise:httpexception, call:, at.isoformat, git, call:select, call:data.get
## symbols
+27 -15
View File
@@ -10,6 +10,7 @@ from sqlalchemy.orm import selectinload
from src.auth.dependencies import get_current_user_id, get_db_session
from src.models import GitRepository
from src.models import Project
from src.models import ToolInstance
from src.models import Workspace
from src.services.shared.workspace_manager import WorkspaceHasInstancesError, WorkspaceManager
@@ -36,12 +37,11 @@ async def list_all_workspaces(
result = await session.execute(
select(
Workspace,
GitRepository.name.label("repo_name"),
GitRepository.project_id,
GitRepository.ssh_key_id.label("repo_ssh_key_id"),
GitRepository,
instance_count.label("instance_count"),
)
.join(GitRepository, Workspace.repo_id == GitRepository.id)
.options(selectinload(GitRepository.project))
.where(Workspace.user_id == user_id)
.order_by(Workspace.created_at.desc())
)
@@ -51,11 +51,11 @@ async def list_all_workspaces(
{
"id": str(ws.id),
"name": ws.name,
"repo_id": str(ws.repo_id),
"repo_name": repo_name or "",
"repo_ssh_key_id": str(ssh_key_id) if ssh_key_id else None,
"project_id": str(project_id) if project_id else "",
"project_name": "",
"repo_id": str(repo.id),
"repo_name": repo.name or "",
"repo_ssh_key_id": str(repo.ssh_key_id) if repo.ssh_key_id else None,
"project_id": str(repo.project_id) if repo.project_id else "",
"project_name": repo.project.name if repo.project else "",
"user_id": str(ws.user_id),
"branch": ws.branch,
"path": ws.path,
@@ -65,7 +65,7 @@ async def list_all_workspaces(
"updated_at": ws.updated_at.isoformat() if ws.updated_at else None,
"instance_count": count or 0,
}
for ws, repo_name, project_id, ssh_key_id, count in rows
for ws, repo, count in rows
]
@@ -130,17 +130,26 @@ async def create_workspace_top_level(
if not name:
raise HTTPException(status_code=400, detail="Workspace name is required")
project = await session.get(Project, repo.project_id) if repo.project_id else None
manager = WorkspaceManager()
try:
workspace = await manager.create(repo, user_id, name, branch, session=session)
workspace = await manager.create(
repo, user_id, name, branch, session=session, project=project
)
session.add(workspace)
await session.commit()
except HTTPException:
raise
except ValueError as exc:
await session.rollback()
logger.error("Failed to create workspace: %s", exc)
raise HTTPException(status_code=400, detail=str(exc)) from exc
except Exception as exc:
await session.rollback()
logger.error("Failed to create workspace: %s", exc)
raise HTTPException(
status_code=409,
detail="Workspace name already exists for this repository",
status_code=500,
detail=f"Failed to create workspace: {exc}",
) from exc
await session.refresh(workspace)
@@ -227,9 +236,12 @@ async def create_workspace(
if not branch:
raise HTTPException(status_code=400, detail="Branch is required")
project = await session.get(Project, project_id)
manager = WorkspaceManager()
try:
workspace = await manager.create(repo, user_id, name, branch, session=session)
workspace = await manager.create(
repo, user_id, name, branch, session=session, project=project
)
session.add(workspace)
await session.commit()
except HTTPException:
@@ -242,8 +254,8 @@ async def create_workspace(
await session.rollback()
logger.error("Failed to create workspace: %s", exc)
raise HTTPException(
status_code=409,
detail="Workspace name already exists for this repository",
status_code=500,
detail=f"Failed to create workspace: {exc}",
) from exc
await session.refresh(workspace)
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/api/src/auth
## role
Provides authentication and authorization infrastructure for the API, including OIDC/OAuth2 integration, session management, and FastAPI dependency injection.
Provides authentication and authorization infrastructure for the API, including OIDC login, session management, and dependency injection for protected routes.
## parent
index: apps/api/src/.pi-map.index.md
map: apps/api/src/.pi-map.md
+7 -7
View File
@@ -4,15 +4,15 @@ dir: apps/api/src/auth
index: apps/api/src/auth/.pi-map.index.md
## role
Provides authentication and authorization infrastructure for the API, including OIDC/OAuth2 integration, session management, and FastAPI dependency injection.
Provides authentication and authorization infrastructure for the API, including OIDC login, session management, and dependency injection for protected routes.
## files
- __init__.py | Re-exports authentication utility functions from submodules to provide a unified public API for the auth package | dep: src.auth.cookies, src.auth.oidc, src.auth.session
- cookies.py | Builds a dictionary of HTTP cookie options from application settings for secure cookie configuration. | exp: func:build_cookie_options(settings: Settings) → dict[str, str | bool | None] | dep: src.config, src.config.Settings
- dependencies.py | Provides FastAPI dependency functions for database sessions, user authentication, and project authorization. | exp: func:get_db_session(), call:SessionLocal, func:get_current_user_id(session_cookie) → uuid.UUID, call:Settings, call:decode_session_cookie, call:uuid.UUID, call:str, raise:HTTPException, func:get_current_user(session_cookie, db_session) → User, call:Settings, call:decode_session_cookie, call:uuid.UUID, call:str, call:db_session.get, raise:HTTPException, func:_get_user(session: AsyncSession, user_id: uuid.UUID) → User, call:session.get, raise:HTTPException, func:_get_owned_project(project_id: uuid.UUID, user_id: uuid.UUID, session: AsyncSession) → "Project", call:session.get, raise:HTTPException | dep: uuid, typing, fastapi, sqlalchemy.ext.asyncio, src.auth.session, src.config, src.database, src.models.project, src.models.user, sqlalchemy
- oidc.py | Implements OIDC/OAuth2 authentication flow with Authentik, including login URL construction, authorization code exchange for tokens, and user info retrieval. | exp: func:build_login_redirect_url(settings: Settings, redirect_uri: str, state: str) → str, call:urlencode, func:exchange_code_for_tokens(settings: Settings, code: str, redirect_uri: str, client: httpx.AsyncClient) → dict[str, str], call:client.post, call:response.raise_for_status, call:response.json, call:payload.get, func:fetch_user_info(settings: Settings, access_token: str, client: httpx.AsyncClient) → dict[str, Any], call:client.get, call:response.raise_for_status, call:response.json | dep: typing, urllib.parse, httpx, src.config
- session.py | Implements creation and verification of signed session cookies using HMAC-SHA256 in a JWS-like format. | exp: func:_base64url_encode(data: bytes) → str, call:base64.urlsafe_b64encode(data).rstrip(b"=").decode, func:_base64url_decode(data: str) → bytes, call:len, call:base64.urlsafe_b64decode, func:create_session_cookie(settings: Settings, user_id: str) → str, call:int, call:(datetime.now(timezone.utc) + timedelta(hours=settings.session_ttl_hours)).timestamp, call:datetime.now, call:timedelta, call:_base64url_encode, call:json.dumps({"alg": "HS256", "typ": "session"}).encode, call:json.dumps(payload).encode, call:hmac.new( settings.session_secret.encode(), message.encode(), hashlib.sha256, ).digest, call:settings.session_secret.encode, call:message.encode, func:decode_session_cookie(settings: Settings, cookie_value: str) → dict[str, Any], call:cookie_value.split, call:len, call:hmac.new( settings.session_secret.encode(), message.encode(), hashlib.sha256, ).digest, call:settings.session_secret.encode, call:message.encode, call:_base64url_encode, call:hmac.compare_digest, call:_base64url_decode, call:json.loads, call:payload.get, call:int, call:datetime.now(timezone.utc).timestamp, raise:ValueError | dep: hmac, hashlib, json, base64, datetime, typing, src.config, src.config.Settings
- __init__.py | Exposes authentication utility functions as a public API through module-level imports | dep: src.auth.cookies, src.auth.oidc, src.auth.session
- cookies.py | Builds a dictionary of HTTP cookie options from application settings | exp: func:build_cookie_options(settings: Settings) → dict[str, str | bool | None] | dep: src.config, src.config.Settings
- dependencies.py | Provides FastAPI dependency injection functions for database sessions, user authentication, and project ownership verification. | exp: func:get_db_session(), call:SessionLocal, func:get_current_user_id(session_cookie) → uuid.UUID, call:Settings, call:decode_session_cookie, call:uuid.UUID, call:str, raise:HTTPException, func:get_current_user(session_cookie, db_session) → User, call:Settings, call:decode_session_cookie, call:uuid.UUID, call:str, call:db_session.get, raise:HTTPException, func:_get_user(session: AsyncSession, user_id: uuid.UUID) → User, call:session.get, raise:HTTPException, func:_get_owned_project(project_id: uuid.UUID, user_id: uuid.UUID, session: AsyncSession) → "Project", call:session.get, raise:HTTPException | dep: uuid, typing, fastapi, sqlalchemy.ext.asyncio, src.auth.session, src.config, src.database, src.models.project, src.models.user, sqlalchemy
- oidc.py | Implements OIDC authentication flow with Authentik for login redirect URL construction, authorization code exchange, and user info retrieval. | exp: func:build_login_redirect_url(settings: Settings, redirect_uri: str, state: str) → str, call:urlencode, func:exchange_code_for_tokens(settings: Settings, code: str, redirect_uri: str, client: httpx.AsyncClient) → dict[str, str], call:client.post, call:response.raise_for_status, call:response.json, call:payload.get, func:fetch_user_info(settings: Settings, access_token: str, client: httpx.AsyncClient) → dict[str, Any], call:client.get, call:response.raise_for_status, call:response.json | dep: typing, urllib.parse, httpx, src.config
- session.py | Creates and verifies signed JWT-like session cookies with HMAC-SHA256 signatures and expiration checks. | exp: func:_base64url_encode(data: bytes) → str, call:base64.urlsafe_b64encode(data).rstrip(b"=").decode, func:_base64url_decode(data: str) → bytes, call:len, call:base64.urlsafe_b64decode, func:create_session_cookie(settings: Settings, user_id: str) → str, call:int, call:(datetime.now(timezone.utc) + timedelta(hours=settings.session_ttl_hours)).timestamp, call:datetime.now, call:timedelta, call:_base64url_encode, call:json.dumps({"alg": "HS256", "typ": "session"}).encode, call:json.dumps(payload).encode, call:hmac.new( settings.session_secret.encode(), message.encode(), hashlib.sha256, ).digest, call:settings.session_secret.encode, call:message.encode, func:decode_session_cookie(settings: Settings, cookie_value: str) → dict[str, Any], call:cookie_value.split, call:len, call:hmac.new( settings.session_secret.encode(), message.encode(), hashlib.sha256, ).digest, call:settings.session_secret.encode, call:message.encode, call:_base64url_encode, call:hmac.compare_digest, call:_base64url_decode, call:json.loads, call:payload.get, call:int, call:datetime.now(timezone.utc).timestamp, raise:ValueError | dep: hmac, hashlib, json, base64, datetime, typing, src.config, src.config.Settings
## arch
Modular utility package with separation of concerns across cookies, sessions, OIDC flows, and FastAPI dependencies; uses HMAC-SHA256 JWS-like tokens and external Authentik identity provider.
Modular security layer using FastAPI dependency injection pattern, with separation of concerns across cookie handling, OIDC flow, session state, and reusable auth dependencies.
## tags
session, src, cookie, user, get, base64url, raise:httpexception, call:
## symbols
+6 -1
View File
@@ -47,6 +47,11 @@ class Settings(BaseSettings):
session_secret: str = "change-me-session-secret"
session_ttl_hours: int = 24
# Internal JWT configuration (used for service-to-service tokens)
jwt_algorithm: str = "HS256"
access_token_ttl_minutes: int = 15
refresh_token_ttl_days: int = 7
# Repository storage
repo_base_path: str = "/data/repos"
@@ -122,7 +127,7 @@ class Settings(BaseSettings):
@property
def cookie_samesite(self) -> str:
if self.app_env == "production":
return "none"
return "strict"
return "lax"
+3 -2
View File
@@ -1,5 +1,6 @@
import asyncio
import logging
import os
import subprocess
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
@@ -54,10 +55,10 @@ async def init_database(
result = await asyncio.get_event_loop().run_in_executor(
None,
lambda: subprocess.run(
["alembic", "upgrade", "head"],
["python3", "-m", "alembic", "upgrade", "head"],
capture_output=True,
text=True,
cwd="/app",
cwd=os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
),
)
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/api/src/models
## role
Provides the core database model layer with a centralized public API, defining SQLAlchemy base classes and reusable mixins for consistent schema patterns across the API application.
Provides the foundational database model layer and unified public API for all SQLAlchemy ORM entities in the API application.
## parent
index: apps/api/src/.pi-map.index.md
map: apps/api/src/.pi-map.md
+3 -3
View File
@@ -4,12 +4,12 @@ dir: apps/api/src/models
index: apps/api/src/models/.pi-map.index.md
## role
Provides the core database model layer with a centralized public API, defining SQLAlchemy base classes and reusable mixins for consistent schema patterns across the API application.
Provides the foundational database model layer and unified public API for all SQLAlchemy ORM entities in the API application.
## files
- __init__.py | Re-exports model classes from submodules to provide a centralized public API for the src.models package | dep: src.models.base, src.models.config.config_profile, src.models.project.git_repository, src.models.project.project, src.models.project.workspace, src.models.system.health_check, src.models.system.instance_event, src.models.system.notification, src.models.system.terminal_session, src.models.tool.tool_definition_manifest, src.models.tool.tool_instance, src.models.tool.tool_type, src.models.user.ssh_key, src.models.user.user, src.models.user.user_config
- __init__.py | Re-exports database models from submodules to provide a unified public API for the models package | dep: src.models.base, src.models.config.config_profile, src.models.project.git_repository, src.models.project.project, src.models.project.workspace, src.models.system.health_check, src.models.system.instance_event, src.models.system.notification, src.models.system.terminal_session, src.models.tool.tool_definition_manifest, src.models.tool.tool_instance, src.models.tool.tool_type, src.models.user.ssh_key, src.models.user.user, src.models.user.user_config
- base.py | Defines SQLAlchemy base model and reusable mixins for UUID primary keys and automatic timestamp tracking in database models. | exp: class:Base, class:UUIDPrimaryKeyMixin, class:TimestampMixin | dep: uuid, datetime, sqlalchemy, sqlalchemy.orm
## arch
Uses the declarative base pattern with mixin composition for cross-cutting concerns (UUIDs, timestamps), and a facade/aggregate module pattern via __init__.py re-exports to simplify imports for consumers.
Implements a layered model architecture using SQLAlchemy declarative base with reusable mixins (UUID primary keys, timestamp tracking) and a facade pattern via `__init__.py` to centralize model imports.
## tags
models, src, base, project, system, user, mixin, tool
## symbols
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/api/src/models/config
## role
Provides SQLAlchemy ORM models for managing configuration profiles and their hierarchical includes in the API application.
Provides SQLAlchemy ORM models for configuration profiles that store deployment settings like environment variables, mounts, and runtime hints.
## parent
index: apps/api/src/models/.pi-map.index.md
map: apps/api/src/models/.pi-map.md
+4 -4
View File
@@ -4,14 +4,14 @@ dir: apps/api/src/models/config
index: apps/api/src/models/config/.pi-map.index.md
## role
Provides SQLAlchemy ORM models for managing configuration profiles and their hierarchical includes in the API application.
Provides SQLAlchemy ORM models for configuration profiles that store deployment settings like environment variables, mounts, and runtime hints.
## files
- __init__.py | Exports config model classes from a subpackage for simplified importing | dep: src.models.config.config_profile
- config_profile.py | Defines SQLAlchemy ORM models for configuration profiles and their includes, storing environment variables, runtime hints, mounts, files, and git mounts with user/project/tool associations and self-referential inclusion relationships. | exp: class:ConfigProfile, class:ConfigProfileInclude | dep: uuid, typing, sqlalchemy, sqlalchemy.orm, src.models.base, src.models.project, src.models, src.models.user, src.models (ToolType)
- config_profile.py | Defines SQLAlchemy ORM models for configuration profiles and their include relationships, storing deployment settings like environment variables, mounts, and runtime hints. | exp: class:ConfigProfile, class:ConfigProfileInclude | dep: uuid, typing, sqlalchemy, sqlalchemy.orm, src.models.base, src.models.project, src.models, src.models.user, src.models.ToolType
## arch
Uses SQLAlchemy declarative ORM pattern with self-referential many-to-many relationships for profile inclusion, and associations to users, projects, and tools.
Package-level facade pattern with __init__.py exposing subpackage classes; domain model using SQLAlchemy ORM with relationship-based composition for profile includes.
## tags
config, src, models, profile, sqlalchemy, init, orm, mounts
config, models, src, profile, sqlalchemy, include, init, orm
## symbols
- ConfigProfile
- ConfigProfileInclude
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/api/src/models/project
## role
Defines the core data models for project-related entities (GitRepository, Project, Workspace) using SQLAlchemy ORM.
Provides SQLAlchemy ORM models for core project-related domain entities (GitRepository, Project, Workspace) with their relationships.
## parent
index: apps/api/src/models/.pi-map.index.md
map: apps/api/src/models/.pi-map.md
+7 -7
View File
@@ -4,16 +4,16 @@ dir: apps/api/src/models/project
index: apps/api/src/models/project/.pi-map.index.md
## role
Defines the core data models for project-related entities (GitRepository, Project, Workspace) using SQLAlchemy ORM.
Provides SQLAlchemy ORM models for core project-related domain entities (GitRepository, Project, Workspace) with their relationships.
## files
- __init__.py | Exports the three main project model classes (GitRepository, Project, Workspace) as the public API for the models.project package. | dep: src.models.project.git_repository, src.models.project.project, src.models.project.workspace
- git_repository.py | Defines a SQLAlchemy ORM model for Git repositories with relationships to projects, users, and SSH keys. | exp: class:GitRepository | dep: uuid, datetime, typing, sqlalchemy, sqlalchemy.orm, src.models.base, src.models.project, src.models, src.models.user, src.models.ssh_key
- project.py | Defines a SQLAlchemy ORM model for a Project entity with relationships to users, Git repositories, and SSH keys. | exp: class:Project | dep: uuid, typing, sqlalchemy, sqlalchemy.orm, src.models.base, src.models, src.models.user, src.models.GitRepository, src.models.SSHKey, src.models.user.User
- workspace.py | Defines a SQLAlchemy ORM model for persistent writable Git repository clones associated with users. | exp: class:Workspace | dep: uuid, datetime, typing, sqlalchemy, sqlalchemy.orm, src.models.base, src.models, src.models.user, src.models.GitRepository, src.models.user.User
- __init__.py | Exports the core project model classes (GitRepository, Project, Workspace) for the models package | dep: src.models.project.git_repository, src.models.project.project, src.models.project.workspace
- git_repository.py | Defines a SQLAlchemy ORM model for Git repositories with relationships to projects, users, and SSH keys. | exp: class:GitRepository | dep: uuid, datetime, typing, sqlalchemy, sqlalchemy.orm, src.models.base, src.models.project, src.models, src.models.user, src.models.SSHKey
- project.py | Defines a SQLAlchemy ORM model for projects with relationships to users, Git repositories, and SSH keys. | exp: class:Project | dep: uuid, typing, sqlalchemy, sqlalchemy.orm, src.models.base, src.models, src.models.user, src.models.GitRepository, src.models.SSHKey, src.models.user.User
- workspace.py | Defines a SQLAlchemy ORM model for persistent writable Git repository clones associated with users | exp: class:Workspace | dep: uuid, datetime, typing, sqlalchemy, sqlalchemy.orm, src.models.base, src.models, src.models.user, src.models.GitRepository
## arch
Standard SQLAlchemy ORM pattern with declarative models, package-level public API abstraction via __init__.py, and bidirectional relationships between entities.
Data access layer using SQLAlchemy ORM with declarative models, explicit table relationships (many-to-many and foreign keys), and package-level facade pattern via __init__.py exports.
## tags
models, src, project, sqlalchemy, git, user, workspace, orm
models, src, project, sqlalchemy, git, workspace, orm, repository
## symbols
- GitRepository
- Project
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/api/src/models/system
## role
Provides SQLAlchemy ORM models for system-level operational and audit data including health checks, instance events, notifications, and terminal sessions.
Provides system-level SQLAlchemy ORM models for operational concerns including health monitoring, audit logging, notifications, and terminal session management.
## parent
index: apps/api/src/models/.pi-map.index.md
map: apps/api/src/models/.pi-map.md
+6 -6
View File
@@ -4,17 +4,17 @@ dir: apps/api/src/models/system
index: apps/api/src/models/system/.pi-map.index.md
## role
Provides SQLAlchemy ORM models for system-level operational and audit data including health checks, instance events, notifications, and terminal sessions.
Provides system-level SQLAlchemy ORM models for operational concerns including health monitoring, audit logging, notifications, and terminal session management.
## files
- __init__.py | Exports system-related model classes from a package for convenient importing. | dep: src.models.system.health_check, src.models.system.instance_event, src.models.system.notification, src.models.system.terminal_session
- __init__.py | Package initialization file that exports system-related model classes for the models.system module | dep: src.models.system.health_check, src.models.system.instance_event, src.models.system.notification, src.models.system.terminal_session
- health_check.py | Defines a SQLAlchemy ORM model for storing health check snapshots of tool instances with various status and probe fields. | exp: class:HealthCheck | dep: uuid, datetime, sqlalchemy, sqlalchemy.orm, src.models.base
- instance_event.py | Defines a SQLAlchemy ORM model for auditing instance lifecycle events with metadata, status tracking, and user attribution. | exp: class:InstanceEvent | dep: uuid, datetime, typing, sqlalchemy, sqlalchemy.orm, src.models.base
- notification.py | Defines a SQLAlchemy ORM model for storing user notifications with metadata, read/dismissed tracking, and polymorphic source references. | exp: class:Notification | dep: datetime, typing, uuid, sqlalchemy, sqlalchemy.orm, sqlalchemy.sql, src.models.base
- instance_event.py | Defines a SQLAlchemy model for auditing instance lifecycle events with metadata, status tracking, and user attribution. | exp: class:InstanceEvent | dep: uuid, datetime, typing, sqlalchemy, sqlalchemy.orm, src.models.base
- notification.py | Defines a SQLAlchemy ORM model for storing user notifications with support for categorization, severity levels, read/dismissed tracking, and polymorphic source references. | exp: class:Notification | dep: datetime, typing, uuid, sqlalchemy, sqlalchemy.orm, sqlalchemy.sql, src.models.base
- terminal_session.py | Defines a SQLAlchemy database model for storing terminal session metadata with lifecycle tracking. | exp: class:TerminalSessionModel | dep: uuid, datetime, sqlalchemy, sqlalchemy.orm, src.models.base
## arch
Standard SQLAlchemy declarative ORM pattern with timestamped base models, polymorphic relationships (notification sources), lifecycle state tracking, and audit fields (user attribution, status enums).
Standard SQLAlchemy declarative model pattern with polymorphic associations (notification sources), lifecycle state tracking, and timestamped audit fields across all models.
## tags
sqlalchemy, src, models, model, orm, system, notification, health
sqlalchemy, models, src, model, system, orm, notification, health
## symbols
- HealthCheck
- InstanceEvent
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/api/src/models/tool
## role
Defines SQLAlchemy ORM models for managing tool definitions, instances, and types in a container-based deployment system.
Provides SQLAlchemy ORM models for managing containerized tool definitions, types, and deployed instances in the API.
## parent
index: apps/api/src/models/.pi-map.index.md
map: apps/api/src/models/.pi-map.md
+7 -7
View File
@@ -4,16 +4,16 @@ dir: apps/api/src/models/tool
index: apps/api/src/models/tool/.pi-map.index.md
## role
Defines SQLAlchemy ORM models for managing tool definitions, instances, and types in a container-based deployment system.
Provides SQLAlchemy ORM models for managing containerized tool definitions, types, and deployed instances in the API.
## files
- __init__.py | Exports the public API for the tool models module by re-exporting three key classes. | dep: src.models.tool.tool_definition_manifest, src.models.tool.tool_instance, src.models.tool.tool_type
- tool_definition_manifest.py | Defines a SQLAlchemy ORM model for storing tool definition manifests that compile to Dockerfiles and Compose files, supporting both base definitions and tool-specific definitions with inheritance. | exp: class:ToolDefinitionManifest | dep: uuid, typing, sqlalchemy, sqlalchemy.orm, src.models.base, src.models.user
- tool_instance.py | Defines a SQLAlchemy ORM model for tool instances that represent deployed tools with container/runtime metadata and relationships to users, projects, repositories, and workspaces. | exp: class:ToolInstance | dep: uuid, datetime, typing, sqlalchemy, sqlalchemy.orm, src.models.base, src.models, src.models.project, src.models.user, src.models (ConfigProfile, GitRepository, Project, ToolType, User, Workspace)
- tool_type.py | Defines a SQLAlchemy ORM model for tool types that represent configurable categories of tools with deployment templates, manifest references, and metadata. | exp: class:ToolType | dep: uuid, typing, sqlalchemy, sqlalchemy.orm, src.models.base, src.models.tool.tool_definition_manifest, src.models.user
- __init__.py | Package initialization file that exports the public API for the tool models module. | dep: src.models.tool.tool_definition_manifest, src.models.tool.tool_instance, src.models.tool.tool_type
- tool_definition_manifest.py | Defines a SQLAlchemy model for tool definition manifests that can represent either base Docker images or tool definitions that inherit from bases, storing manifest JSON and cached Dockerfile/Compose outputs. | exp: class:ToolDefinitionManifest | dep: uuid, typing, sqlalchemy, sqlalchemy.orm, src.models.base, src.models.user
- tool_instance.py | Defines a SQLAlchemy ORM model representing a deployed tool instance with container/runtime metadata and relationships to users, projects, and workspaces. | exp: class:ToolInstance | dep: uuid, datetime, typing, sqlalchemy, sqlalchemy.orm, src.models.base, src.models, src.models.project, src.models.user, src.models (ConfigProfile, GitRepository, Project, ToolType, User, Workspace)
- tool_type.py | Defines a SQLAlchemy ORM model for tool types that specify metadata, configuration, and deployment templates for containerized tools. | exp: class:ToolType | dep: uuid, typing, sqlalchemy, sqlalchemy.orm, src.models.base, src.models.tool.tool_definition_manifest, src.models.user
## arch
Domain-driven SQLAlchemy ORM models with inheritance patterns (base/tool-specific manifests), rich relationships (users, projects, repositories, workspaces), and template-based deployment abstractions.
Uses SQLAlchemy ORM with declarative models, inheritance patterns (base vs. derived tool definitions), and relational associations linking tools to users, projects, and workspaces.
## tags
tool, models, src, sqlalchemy, orm, definition, manifest, base
tool, models, src, sqlalchemy, definition, manifest, orm, instance
## symbols
- ToolDefinitionManifest
- ToolInstance
+3
View File
@@ -39,6 +39,9 @@ class ToolType(UUIDPrimaryKeyMixin, TimestampMixin, Base):
)
readiness_probe: Mapped[dict | None] = mapped_column(JSON, nullable=True)
startup_command: Mapped[str | None] = mapped_column(Text, nullable=True)
home_directory: Mapped[str] = mapped_column(
String(255), nullable=False, default="/home/user"
)
required_variables: Mapped[list[str]] = mapped_column(
JSON, default=list, nullable=False
)
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/api/src/models/user
## role
Provides the core data models for user identity, authentication credentials, and personal configuration in the API's database layer.
Database models package defining user-related entities including users, SSH keys, and user configuration with their relationships.
## parent
index: apps/api/src/models/.pi-map.index.md
map: apps/api/src/models/.pi-map.md
+6 -6
View File
@@ -4,16 +4,16 @@ dir: apps/api/src/models/user
index: apps/api/src/models/user/.pi-map.index.md
## role
Provides the core data models for user identity, authentication credentials, and personal configuration in the API's database layer.
Database models package defining user-related entities including users, SSH keys, and user configuration with their relationships.
## files
- __init__.py | Exports the user-related model classes (SSHKey, User, UserConfig) from a package for convenient importing. | dep: src.models.user.ssh_key, src.models.user.user, src.models.user.user_config
- ssh_key.py | Defines an SSHKey SQLAlchemy ORM model for storing encrypted SSH key pairs with user and optional project associations. | exp: class:SSHKey | dep: uuid, typing, sqlalchemy, sqlalchemy.orm, src.models.base, src.models.project, src.models.user
- __init__.py | Exports user-related model classes from a package for clean external imports. | dep: src.models.user.ssh_key, src.models.user.user, src.models.user.user_config
- ssh_key.py | Defines an SSHKey database model for storing encrypted SSH key pairs with associations to users and optional projects. | exp: class:SSHKey | dep: uuid, typing, sqlalchemy, sqlalchemy.orm, src.models.base, src.models.project, src.models.user
- user.py | Defines a SQLAlchemy ORM model for a User entity with authentication fields and relationships to projects, SSH keys, and user configuration. | exp: class:User | dep: typing, sqlalchemy, sqlalchemy.orm, src.models.base, src.models.project, src.models, src.models.SSHKey, src.models.UserConfig
- user_config.py | Defines a SQLAlchemy model for storing per-user JSON configuration with typed property accessors for default profile IDs. | exp: class:UserConfig | dep: uuid, typing, sqlalchemy, sqlalchemy.orm, src.models.base, src.models.user
- user_config.py | Defines a SQLAlchemy model for storing per-user JSON configuration data with typed property accessors for profile IDs. | exp: class:UserConfig | dep: uuid, typing, sqlalchemy, sqlalchemy.orm, src.models.base, src.models.user
## arch
Follows SQLAlchemy ORM declarative model pattern with package-level aggregation via __init__.py, using explicit table relationships (foreign keys, one-to-many associations) and a JSON-backed configuration store with typed property accessors.
SQLAlchemy ORM with declarative models, one-to-many relationships (User→SSHKey, User→Project, User→UserConfig), encrypted field storage for SSH keys, and JSON-typed configuration with property accessors for schema flexibility.
## tags
user, src, models, sqlalchemy, sshkey, orm, model, ssh
user, src, models, sqlalchemy, model, sshkey, ssh, orm
## symbols
- SSHKey
- User
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/api/src/schemas
## role
Defines Pydantic schemas for data validation, serialization, and API request/response models in the FastAPI application.
Provides data validation schemas for the API application
## parent
index: apps/api/src/.pi-map.index.md
map: apps/api/src/.pi-map.md
+4 -4
View File
@@ -4,13 +4,13 @@ dir: apps/api/src/schemas
index: apps/api/src/schemas/.pi-map.index.md
## role
Defines Pydantic schemas for data validation, serialization, and API request/response models in the FastAPI application.
Provides data validation schemas for the API application
## files
- __init__.py | Empty file with no functionality
- __init__.py | Swaps two tmux panes between windows, preserving active pane state and layout | dep: tmux
## arch
Modular schema organization following FastAPI/Pydantic patterns, with empty __init__.py indicating package initialization for future schema modules.
Package initialization with modular schema definitions using Pydantic or similar validation framework
## tags
init, empty, functionality
tmux, init, swaps, two, panes, windows, preserving, active
## symbols
-
## workflows
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/api/src/schemas/config
## role
Provides Pydantic validation schemas for configuration profile API endpoints, ensuring data integrity for mounts, git URLs, UUIDs, and file paths.
Provides Pydantic schemas for configuration profile API operations with validated request/response models.
## parent
index: apps/api/src/schemas/.pi-map.index.md
map: apps/api/src/schemas/.pi-map.md
+5 -5
View File
@@ -4,14 +4,14 @@ dir: apps/api/src/schemas/config
index: apps/api/src/schemas/config/.pi-map.index.md
## role
Provides Pydantic validation schemas for configuration profile API endpoints, ensuring data integrity for mounts, git URLs, UUIDs, and file paths.
Provides Pydantic schemas for configuration profile API operations with validated request/response models.
## files
- __init__.py | Re-exports Pydantic schemas for configuration profile operations from a submodule to provide a clean public API. | dep: src.schemas.config.config_profile
- config_profile.py | Defines Pydantic request/response schemas for config profile API operations with validation for mounts, git URLs, UUIDs, and file paths. | exp: class:GitMountMapping, class:GitMountItem, class:MountItem, class:ConfigProfileCreate, class:ConfigProfileUpdate, class:ConfigProfileIncludeUpdate, class:ConfigProfileResponse, class:DefaultProfilesUpdate, class:ValidateGitUrlRequest, class:ValidateGitUrlResponse, func:_validate_uuid(v: str | None) → str | None, call:uuid.UUID, raise:ValueError | dep: uuid, pydantic, src.api.shared_validators
- __init__.py | Re-exports config profile schema classes from a submodule to provide a unified public interface for the config schemas module. | dep: src.schemas.config.config_profile
- config_profile.py | Defines Pydantic request/response schemas for configuration profile API operations with comprehensive input validation. | exp: class:GitMountMapping, class:GitMountItem, class:MountItem, class:ConfigProfileCreate, class:ConfigProfileUpdate, class:ConfigProfileIncludeUpdate, class:ConfigProfileResponse, class:DefaultProfilesUpdate, class:ValidateGitUrlRequest, class:ValidateGitUrlResponse, func:_validate_uuid(v: str | None) → str | None, call:uuid.UUID, raise:ValueError | dep: uuid, pydantic, src.api.shared_validators
## arch
Simple schema-as-code pattern using Pydantic models with field validators; clean public API via explicit re-exports in `__init__.py`.
Layered schema module using submodule organization with clean public interface via __init__.py re-exports, following Pydantic-based validation pattern for API contract definitions.
## tags
profile, config, git, mount, update, validate, response, item
config, profile, git, mount, update, validate, response, item
## symbols
- GitMountMapping
- GitMountItem
@@ -240,6 +240,12 @@ class ConfigProfileIncludeUpdate(BaseModel):
return v
class ConfigProfileRefreshOutcome(BaseModel):
instance_id: str
status: str
reason: str | None = None
class ConfigProfileResponse(BaseModel):
id: str
user_id: str
@@ -256,6 +262,7 @@ class ConfigProfileResponse(BaseModel):
includes: list[dict]
created_at: str
updated_at: str
refresh_outcomes: list[ConfigProfileRefreshOutcome] = Field(default_factory=list)
class DefaultProfilesUpdate(BaseModel):
@@ -2,7 +2,7 @@
dir: apps/api/src/schemas/project
## role
Provides Pydantic data validation schemas for project-related API endpoints including projects, Git repositories, and SSH keys.
Provides Pydantic data validation schemas for project-related API endpoints, serving as the contract layer between API requests/responses and internal business logic.
## parent
index: apps/api/src/schemas/.pi-map.index.md
map: apps/api/src/schemas/.pi-map.md
+6 -6
View File
@@ -4,16 +4,16 @@ dir: apps/api/src/schemas/project
index: apps/api/src/schemas/project/.pi-map.index.md
## role
Provides Pydantic data validation schemas for project-related API endpoints including projects, Git repositories, and SSH keys.
Provides Pydantic data validation schemas for project-related API endpoints, serving as the contract layer between API requests/responses and internal business logic.
## files
- __init__.py | Re-exports Pydantic schema classes from submodules to provide a unified public API for the project schemas module | dep: src.schemas.project.git_repository, src.schemas.project.project, src.schemas.project.ssh_key
- __init__.py | Re-exports Pydantic schema classes from submodules to provide a unified public API for project-related data models. | dep: src.schemas.project.git_repository, src.schemas.project.project, src.schemas.project.ssh_key
- git_repository.py | Defines Pydantic request/response schemas for Git repository operations in an API. | exp: class:GitRepositoryCreate, class:URLParseRequest, class:URLParseResponse, class:GitRepositoryResponse, class:UpdateSSHKeyRequest, class:FileListResponse, class:FileContentResponse, class:BranchesResponse, class:FileUpdateRequest, class:FileUpdateResponse, class:StatusResponse, class:BranchCreateRequest, class:CheckoutRequest, class:CommitRequest, class:CommitResponse, class:FetchResponse, class:PullResponse, class:PushResponse, class:MergeRequest, class:MergeResponse | dep: uuid, datetime, pydantic
- project.py | Defines Pydantic request/response schemas for project-related API operations. | exp: class:ProjectCreate, class:ProjectUpdate, class:ProjectResponse, class:SetDefaultSSHKeyRequest | dep: uuid, pydantic
- ssh_key.py | Defines Pydantic schemas for SSH key creation, response, signing, and signature verification in an API. | exp: class:SSHKeyCreate, class:SSHKeyResponse, class:SignPayloadRequest, class:SignatureResponse, class:VerifySignatureRequest, class:VerifySignatureResponse | dep: uuid, datetime, pydantic
- project.py | Defines Pydantic request/response schemas for project-related API operations including creation, updates, responses, and default SSH key assignment. | exp: class:ProjectCreate, class:ProjectUpdate, class:ProjectResponse, class:SetDefaultSSHKeyRequest | dep: uuid, pydantic
- ssh_key.py | Defines Pydantic request/response schemas for SSH key management and signature operations in an API. | exp: class:SSHKeyCreate, class:SSHKeyResponse, class:SignPayloadRequest, class:SignatureResponse, class:VerifySignatureRequest, class:VerifySignatureResponse | dep: uuid, datetime, pydantic
## arch
Modular schema-per-domain pattern with clean separation of concerns, using Pydantic models for request/response validation and an `__init__.py` facade for unified public API access.
Modular schema-per-domain pattern with clean separation of concerns (git repositories, projects, SSH keys) and centralized public API via `__init__.py` re-exports, following standard Pydantic model hierarchy with explicit request/response bifurcation.
## tags
response, request, project, create, update, sshkey, pydantic, schemas
response, request, project, create, update, sshkey, pydantic, git
## symbols
- GitRepositoryCreate
- URLParseRequest
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/api/src/schemas/system
## role
Provides Pydantic schemas for system health monitoring endpoints in the API.
Provides Pydantic data models for system health monitoring API responses, enabling standardized health check serialization across the API.
## parent
index: apps/api/src/schemas/.pi-map.index.md
map: apps/api/src/schemas/.pi-map.md
+5 -5
View File
@@ -4,14 +4,14 @@ dir: apps/api/src/schemas/system
index: apps/api/src/schemas/system/.pi-map.index.md
## role
Provides Pydantic schemas for system health monitoring endpoints in the API.
Provides Pydantic data models for system health monitoring API responses, enabling standardized health check serialization across the API.
## files
- __init__.py | Re-exports health check schema classes from a submodule to provide a unified interface for the system schemas module. | dep: src.schemas.system.health
- health.py | Defines Pydantic data models for health check API responses including database, disk, and overall system health status. | exp: class:DatabaseHealth, class:DiskHealth, class:HealthChecks, class:HealthResponse, class:DatabaseHealthResponse | dep: pydantic
- __init__.py | Re-exports health-related schema classes from a submodule to provide a unified interface for the system schemas module. | dep: src.schemas.system.health
- health.py | Defines Pydantic data models for health check API responses including database, disk, and overall system health metrics. | exp: class:DatabaseHealth, class:DiskHealth, class:HealthChecks, class:HealthResponse, class:DatabaseHealthResponse | dep: pydantic
## arch
Simple submodule pattern with explicit re-exports in __init__.py, using Pydantic models for structured API response validation.
Simple submodule-based schema organization with explicit re-exports in __init__.py following a flat namespace pattern for cleaner imports.
## tags
health, database, response, system, disk, check, schemas, init
health, database, response, system, disk, schemas, init, pydantic
## symbols
- DatabaseHealth
- DiskHealth
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/api/src/schemas/tool
## role
Provides Pydantic data validation schemas for tool-related API operations including tool types and instances.
Provides Pydantic data validation schemas for tool-related API request/response models.
## parent
index: apps/api/src/schemas/.pi-map.index.md
map: apps/api/src/schemas/.pi-map.md
+7 -6
View File
@@ -4,17 +4,18 @@ dir: apps/api/src/schemas/tool
index: apps/api/src/schemas/tool/.pi-map.index.md
## role
Provides Pydantic data validation schemas for tool-related API operations including tool types and instances.
Provides Pydantic data validation schemas for tool-related API request/response models.
## files
- __init__.py | Re-exports tool-related schema classes from submodules to provide a unified import interface for the tool schemas module. | dep: src.schemas.tool.tool_instance, src.schemas.tool.tool_type
- tool_instance.py | Defines Pydantic request/response schemas for tool instance lifecycle operations (create and start). | exp: class:CreateInstanceRequest, class:StartInstanceRequest | dep: pydantic
- tool_type.py | Defines Pydantic request/response schemas for creating, updating, and validating tool types with Docker compose/dockerfile/manifest definitions | exp: class:ToolTypeCreate, class:ToolTypeUpdate, class:ToolTypeResponse, class:ToolTypeValidateRequest | dep: uuid, datetime, pydantic, src.api.tool.tool_types_validation
- __init__.py | Re-exports tool-related Pydantic schemas from submodules to provide a unified public interface for the schemas.tool package. | dep: src.schemas.tool.tool_instance, src.schemas.tool.tool_type
- tool_instance.py | Defines Pydantic request/response schemas for tool instance lifecycle operations (create, create-on-workspace, start). | exp: class:CreateInstanceRequest, class:CreateWorkspaceInstanceRequest, class:StartInstanceRequest | dep: pydantic
- tool_type.py | Defines Pydantic request/response schemas for tool type CRUD operations with conditional validation logic based on definition type | exp: class:ToolTypeCreate, class:ToolTypeUpdate, class:ToolTypeResponse, class:ToolTypeValidateRequest | dep: uuid, datetime, pydantic, src.api.tool.tool_types_validation
## arch
Modular schema organization with submodule-per-entity pattern and unified re-export interface via `__init__.py`, following standard Python package structure for clean API imports.
Modular schema organization with submodule-per-entity pattern, re-exported through __init__.py for clean imports; uses conditional Pydantic validators for polymorphic definition type handling.
## tags
tool, type, request, instance, schemas, create, pydantic, response
tool, type, request, instance, create, schemas, pydantic, response
## symbols
- CreateInstanceRequest
- CreateWorkspaceInstanceRequest
- StartInstanceRequest
- ToolTypeCreate
- ToolTypeUpdate
+3 -3
View File
@@ -82,8 +82,8 @@ class ToolTypeCreate(BaseModel):
@classmethod
def validate_default_port(cls, v: int, info) -> int:
data = info.data
requires_port = data.get("requires_port", True)
if not requires_port:
requires_port = data.get("requires_port")
if requires_port is False:
return v
if v <= 0 or v > 65535:
raise ValueError("Port must be between 1 and 65535")
@@ -140,7 +140,7 @@ class ToolTypeCreate(BaseModel):
if not check_port_exposed(parsed, self.default_port):
raise ValueError(
f"Port {self.default_port} is not exposed in the compose template. Add it to the 'ports' section."
f"default_port {self.default_port} is not exposed in the compose template. Add it to the 'ports' section."
)
return self
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/api/src/schemas/user
## role
Provides centralized Pydantic data validation schemas for user-related API requests and responses.
Defines Pydantic schemas for user-related request/response data serialization and validation in the API layer.
## parent
index: apps/api/src/schemas/.pi-map.index.md
map: apps/api/src/schemas/.pi-map.md
+6 -6
View File
@@ -4,15 +4,15 @@ dir: apps/api/src/schemas/user
index: apps/api/src/schemas/user/.pi-map.index.md
## role
Provides centralized Pydantic data validation schemas for user-related API requests and responses.
Defines Pydantic schemas for user-related request/response data serialization and validation in the API layer.
## files
- __init__.py | Re-exports user-related Pydantic schemas from submodules to provide a unified public API for the user schemas package. | dep: src.schemas.user.user, src.schemas.user.user_config
- user.py | Defines Pydantic response and update schemas for user profile data | exp: class:UserProfileResponse, class:UserProfileUpdate | dep: uuid, pydantic
- user_config.py | Defines Pydantic models for user configuration data transfer objects (response and update schemas). | exp: class:UserConfigResponse, class:UserConfigUpdate | dep: pydantic
- __init__.py | Exports user-related Pydantic schemas from submodules to provide a clean public API for the user schemas package. | dep: src.schemas.user.user, src.schemas.user.user_config
- user.py | Defines Pydantic request/response schemas for user profile data in an API | exp: class:UserProfileResponse, class:UserProfileUpdate | dep: uuid, pydantic
- user_config.py | Defines Pydantic request/response schemas for user configuration data in an API. | exp: class:UserConfigResponse, class:UserConfigUpdate | dep: pydantic
## arch
Modular package pattern with submodule separation of concerns (user profile vs. user config) and unified public API via `__init__.py` re-exports.
Modular package pattern with clean public API via __init__.py exports, separating domain concerns (user profile vs. user configuration) into distinct schema modules.
## tags
user, schemas, response, update, config, pydantic, profile, src
user, schemas, response, config, pydantic, profile, update, api
## symbols
- UserProfileResponse
- UserProfileUpdate
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/api/src/scripts
## role
Provides utility scripts for administrative and setup tasks for the API application.
Provides utility scripts for database initialization and maintenance tasks for the API.
## parent
index: apps/api/src/.pi-map.index.md
map: apps/api/src/.pi-map.md
+5 -5
View File
@@ -4,14 +4,14 @@ dir: apps/api/src/scripts
index: apps/api/src/scripts/.pi-map.index.md
## role
Provides utility scripts for administrative and setup tasks for the API application.
Provides utility scripts for database initialization and maintenance tasks for the API.
## files
- __init__.py | Marks the directory as a Python package and provides a docstring describing it as utility scripts for the API package.
- seed.py | Seeds a development user into the database using SQLAlchemy async operations, with idempotency check to avoid duplicates. | exp: func:build_seed_user() → Mapping[str, str | None], func:seed_database(session: AsyncSession) → User, call:build_seed_user, call:session.scalar, call:select(User).where, call:User, call:session.add, call:session.commit, call:session.refresh, func:run() → None, call:SessionLocal, call:seed_database, call:print, call:str, func:main() → None, call:asyncio.run, call:run | dep: collections.abc, sqlalchemy, sqlalchemy.ext.asyncio, src.database, src.models.user, asyncio
- __init__.py | Marks the directory as a Python package and provides a module docstring for API utility scripts.
- seed.py | Seeds a development user into the database, creating it if it doesn't already exist based on email. | exp: func:build_seed_user() → Mapping[str, str | None], func:seed_database(session: AsyncSession) → User, call:build_seed_user, call:session.scalar, call:select(User).where, call:User, call:session.add, call:session.commit, call:session.refresh, func:run() → None, call:SessionLocal, call:seed_database, call:print, call:str, func:main() → None, call:asyncio.run, call:run | dep: collections.abc, sqlalchemy, sqlalchemy.ext.asyncio, src.database, src.models.user, asyncio
## arch
Simple procedural scripts using async SQLAlchemy for database operations, with idempotency guards for safe re-runs.
Simple procedural script pattern with direct database access via SQLAlchemy models, no service layer abstraction.
## tags
seed, user, database, sqlalchemy, package, init, call:build, call:session.scalar
seed, user, database, init, call:build, call:session.scalar, call:select, call:user
## symbols
- build_seed_user
- seed_database
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/api/src/seeds
## role
Provides database seeding utilities for initializing built-in tool types in the API application.
Provides database seeding utilities to populate initial built-in tool type configurations for the API application.
## parent
index: apps/api/src/.pi-map.index.md
map: apps/api/src/.pi-map.md
+4 -4
View File
@@ -4,12 +4,12 @@ dir: apps/api/src/seeds
index: apps/api/src/seeds/.pi-map.index.md
## role
Provides database seeding utilities for initializing built-in tool types in the API application.
Provides database seeding utilities to populate initial built-in tool type configurations for the API application.
## files
- __init__.py | Marks the directory as a Python package for database seeding utilities.
- builtin_tool_types.py | Seeds built-in Docker Compose-based tool types (VS Code Server, Jupyter Notebook, OpenCode) into a database with idempotent create-or-update logic. | exp: func:_table_exists(session, table_name: str) → bool, call:session.execute, call:text, call:result.scalar, func:seed_builtin_tool_types(), call:SessionLocal, call:_table_exists, call:logger.warning, call:session.scalar, call:select(ToolType).where, call:ToolType, call:tool_data.get, call:session.add, call:logger.info, call:session.commit | dep: logging, sqlalchemy, src.database, src.models
- __init__.py | Marks directory as Python package for database seeding utilities
- builtin_tool_types.py | Seeds built-in tool types (code-server, jupyter-notebook, opencode) into the database with Docker Compose templates, creating or updating records as needed. | exp: func:_table_exists(session, table_name: str) → bool, call:session.execute, call:text, call:result.scalar, func:seed_builtin_tool_types(), call:SessionLocal, call:_table_exists, call:logger.warning, call:session.scalar, call:select(ToolType).where, call:ToolType, call:tool_data.get, call:session.add, call:logger.info, call:session.commit | dep: logging, sqlalchemy, src.database, src.models
## arch
Simple procedural seeding scripts with idempotent create-or-update logic using direct database operations, organized as a Python package with one seed module per entity type.
Simple procedural seeding pattern with idempotent upsert operations, directly importing and executing database model creation logic with embedded Docker Compose template definitions.
## tags
tool, types, table, exists, builtin, call:tool, database, init
## symbols

Some files were not shown because too many files have changed in this diff Show More