exposes the duress lockdown as an endpoint (needs the distress password in the
body, rate-limited) so it can be tripped from a shortcut/webhook, not only by
typing the distress password at the login form.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a duress password (DISTRESS_PASSWORD env): entering it at login trips an
in-memory full lockdown — all new logins (password + passkey) and every existing
session are refused until the server is restarted, and the login itself returns a
normal "invalid credentials" so it gives nothing away. Also add
POST /api/auth/blacklist-token as a clearly-named alias for revoking the current
JWT (same effect as signout).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
APP_ORIGINS now includes EXPO_PUBLIC_CLIENT_ORIGIN when set, so the mobile app's
officer:// custom-scheme origin passes origin validation without weakening it for
anyone else. The origin (with its embedded token) lives in .env, not the repo.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
/download-video now delegates to the ReClip service (its own yt-dlp) and runs as
a background job: POST returns a jobId immediately and the client polls
GET /download-video/:jobId, so a large download no longer holds one long request
open (which was 504-ing behind the reverse proxy). the finished file is streamed
into the user's folder with a title-based name.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
a script task that goes silent for a while (e.g. ffmpeg's faststart pass rewrites
a huge file for minutes with no output) would hit Bun's default 120s websocket
idle timeout → close(ws) → killTree killed the task mid-run, corrupting the
output. now the executor pings the socket every 30s while a task runs, resetting
the idle timer.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AUTOMATION_CONTEXT.md and SIDECAR.md described the multi-tenant scope
model, the seed/ tree, the marketplace, and a single sidecar owning the
queue — all superseded. Fix the stale doc pointers in opencode.json and
CLAUDE.md, and point at TODO.md as the source of truth on direction.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
proc.kill() only signalled the direct child (bash), leaving a running ffmpeg
grandchild orphaned and still encoding. now killTree() walks /proc, SIGTERMs the
whole subtree (bash + ffmpeg), then SIGKILLs any straggler after 2s.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
when a folder has mixed audio/subtitle layouts, the mismatch banner now offers a
toggle: convert the matching group (pick tracks, skip the rest) OR convert every
video keeping all audio + all subtitles (no picking, nothing skipped). the second
mode hides the pickers and clears the track-selection inputs so run.sh keeps
everything. only shown for tasks with track pickers (Convert Video).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
selecting several files/folders and running a task now runs it on the whole
selection instead of just the right-clicked item. reuses the folder + include
mechanism: the run targets the current folder scoped to the selected files
(and everything under selected folders). track-picker tasks scope their probe
to the selection too.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
the video used max-w/max-h so a low-res source stayed at native size surrounded
by black. switch to w-full h-full object-contain so it scales up to fill the
player (windowed and fullscreen) while preserving aspect ratio.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
renders each subtitle as a keep checkbox + editable label field, seeded from the
file's current tracks. reuses the existing probe / folder-grouping / include
plumbing, so it works on a single file or a whole season.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
converting a folder now recursively probes every video and groups episodes by
track layout (audio language+channels, subtitle language). the pickers are
driven off the largest matching group; episodes with a different layout are
listed as skipped and converted separately. adds a /probe-folder endpoint and
passes the majority file list to run.sh as INPUT_INCLUDE.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
convert video's task modal now probes a single file on open, lists its audio
and subtitle tracks as inline checkboxes, and passes the selection to the
script. audio defaults to all kept, subtitles to none; a "none" sentinel keeps
empty (folder mode) meaning keep-all.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add /file-browser/audio-tracks (list) and raw?audio=N (serve the video with
a chosen audio track via a cached -c copy remux, byte-range seekable). The
player shows an always-visible selector outside the controls; switching
reloads the source and restores the playhead. Works in Chromium, which
doesn't expose the audioTracks API.
Temp remux file keeps the real extension (a .tmp suffix breaks ffmpeg's
muxer selection); concurrent requests for a track share one remux.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add /file-browser/subtitles (list text subtitle tracks) and /subtitle-vtt
(extract one as WebVTT on demand); the VideoRenderer fetches the list,
renders <track> elements, and shows a CC selector to switch/turn off subs.
Track labels prefer title, then handler_name (mp4), then a real language,
falling back to Track N — so untagged tracks aren't shown as "und".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New POST /api/rescan re-runs the boot item setup (ensureItemDirs +
ensureToolLoader) and returns live item counts; the header button calls it
and invalidates the item query caches so the UI refetches from disk.
Also fix ensure-tool-loader to write into OFFICER_ITEMS_DIR/extensions (the
runtime read path) instead of the now-unread DATA_PATH/extensions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the marketplace service dependency and the native/global/user
scope tiers with a single external directory ($OFFICER_ITEMS_DIR) holding
skills, tools, tasks, processes and extensions as plain files.
- tasks move from Postgres to TASK.md files (new file-backed task layer);
task editing now works, which the DB path never supported
- skills/tools/processes collapse into one shared file router (single dir)
- remove the marketplace client (sync-marketplace/sync-version) and the
boot-time sync; pi-bridge/pi-manager/sandbox point at the flat store
- drop the dead tasks + vestigial skills/tools/processes/extensions +
item_chats tables (migration 0004)
- one-time migration script exports DB tasks and consolidates disk items
Migration verified: all 6 tasks round-trip through the runtime parser
identically to their DB rows (pipeline steps, triggers, script impls and
agentic bodies all intact).
NOTE: not yet functionally tested end-to-end — every item (each task mode,
tool, skill, extension) still needs to be run/exercised in the app before
this is trusted. To be done manually.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Navigation is now session-local state that always starts at home. The
unscoped browser still mirrors its folder into files/currentPath so the
Create Dashboard flow keeps defaulting a new dashboard's cwd to it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the /transcode (and dead /transcode-audio) routes and the frontend
mkv/avi transcode wiring. Only browser-native formats (mp4, webm, mov,
m4v, ogv) are classified as video now; other containers fall through to
the generic file view.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
the file browser sends msg.cwd relative to the user's home and it went straight to
Bun.spawn, which resolves it against the server's cwd. when that directory does not
exist posix_spawn reports ENOENT naming the binary rather than the directory, so
"Convert Audio" failed with the misleading "posix_spawn 'bash'".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
the hand-installed systemd unit kept a parallel xfce session on :1 alive and
respawned it on every kill, independently of the platform. obsolete now that the
desktop panel mirrors :0; disabled, but the unit file is still on disk.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- vnc-manager now runs x11vnc against :0 rather than vncserver on a fresh display,
so the browser shows the same session as the tv instead of a parallel one
- x11vnc reads :0's cookie from the logged-in user's own .Xauthority, so no root is
needed; mirroring only works while someone is logged in (the greeter's cookie
belongs to lightdm)
- -scale 0.5 halves the 4k framebuffer to 1080p for the stream, -shared -forever
keeps it up across browser disconnects, -localhost keeps it behind the ws bridge
- readiness is now the listening port, not exit code: x11vnc stays in the
foreground where vncserver daemonized and exited
- drops findFreeDisplay and per-email session tracking; there is exactly one :0
- the parallel desktops this replaces caused real breakage: a ghost logind session
that broke lightdm login, and a brave profile lock held on :2
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- the sandbox already drops privileges to the OS user (id -un), so pi-bridge was
the only code path that switched to a per-user unix account
- the four accounts it targeted (andrepadez, john-wick, fedra, miguelbenoliel)
are vestigial: created by scripts/provision-existing-users.sh, with no home
dirs, no files, no processes. the only live account is pastilhas@officer.dev,
which maps to the service user, so isServiceUser was always true and the
sudo -u branch could never fire
- add TODO.md tracking the leftover username plumbing, the useradd script, known
bugs (task-executor relative cwd, bootstrap pi EEXIST), and the ufw 9010 rule
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- add getValidGoogleAccessToken helper and use it in email-cron, email account auth resolver, and the new gmail proxy
- POST /api/integrations/google/gmail-proxy forwards arbitrary gmail rest calls server-side, with auto-refreshed oauth
- pi-manager and claude user-instance inject OFFICER_API_URL + per-session JWT so tools can call back as the user
- claude anthropic proxy uses Authorization: Bearer + preserves any anthropic-beta headers (pro oauth tokens are rejected via x-api-key, and overwriting the beta header broke context_management)
- pi --list-models: fall back to stderr when stdout is empty (pi v0.73.1 writes the table to stderr)
- mcp tool server + pi tool loader: accept type: object inputs so json bodies stay structured
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Agentic steps now have a 5-minute inactivity timeout (non-Claude-Code
models only) so stalled local LLM requests fail with a clear error
instead of hanging forever. A "Waiting for model" indicator is emitted
every 10s and shown in the pipeline UI stats bar, clearing as soon as
the model starts responding.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace 8-category layout with flat task list and detail view using
WorkspaceView. Searchable list with mode badges, run/delete actions,
create dialog. Remove 13 unused component files.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
First sync still uses IMAP with app password. Subsequent syncs use
Gmail API history.list + messages.get with OAuth for faster, more
reliable incremental sync. Dispatch gmail-sync handler for gmail
accounts instead of generic email-sync. Show sync button for synced
accounts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix stop: abort poll now rejects the promise after killing the agent process
- Fix jobs link: close modal before navigating, use react-router navigate
- Fix seed parser: strip quotes from step input values (delete_source: "true")
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Pipeline jobs now persist to DB with progress tracking and cost accumulation
- Jobs survive WebSocket disconnects with in-memory event buffer replay
- New /jobs list page with search, status badges, and cost display
- New /jobs/:id detail page with live WebSocket attachment and REST fallback
- Two-column layout using WorkspaceLayout for resizable steps/output panels
- Streaming messages tagged with stepIndex/iterationLabel for per-step output grouping
- TaskRunnerModal links to job detail page once job is created
- Dock entry added for Jobs page
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Pipeline mode: new task mode that chains agentic tasks sequentially with
foreach/subdirectory iteration and skip_if conditions
- Pipeline executor backend (WebSocket at /api/tasks/pipeline/ws) with
support for both Pi and Claude Code models
- Frontend PipelineRunner component with step progress, streaming output,
and aggregate cost tracking
- New agentic tasks: prepare-discography, fetch-album-info, build-discography
(pipeline combining both)
- Seed parser extended to handle pipeline steps in frontmatter config
- CopyButton component added to assistant bubbles, error bubbles, and
tool input/output sections
- Removed obsolete SearXNG/Apify/browser relay code from pi-bridge
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- rename task to convert-audio, support mp3/flac/wav/ogg/aac/opus targets
- add options input type with selectable pill buttons in UI
- extend seed script parser to handle YAML list properties (options)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- fetch task input definitions from API and render configurable inputs
- boolean inputs render as No/Yes toggle (e.g. delete_source)
- auto-filled inputs (file_path) are hidden from the form
- wrap script output in dark pre/code block with copy button
- fix ffmpeg -nostdin for batch directory processing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tasks now live in the database (mode: script or agentic). Script-mode tasks
bypass the agent entirely — the implementation is materialized to a temp file
and executed directly, with stdout/stderr streamed to the UI via WebSocket.
Includes convert-to-mp3 as the first native script task.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sandbox now mounts global content at short /officer/* paths to avoid
bwrap intermediate directory traversal issues. Pi uses NODE_PATH for
extension dependency resolution.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Capture HOST_HOME before user-instance overrides process.env.HOME so
Super Admin spawns claude in /home/pastilhas. Generate separate MCP
configs for sandbox (sandbox paths) and host (real filesystem paths),
pick based on role at spawn time.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move claude and pi from sudo global installs to ~/.local. Claude
binary is copied to /usr/local/bin for sandbox visibility, pi runs
via node from ~/.local/lib (ro-mounted). Fix bwrap intermediate dir
traversal by setting 0755 perms on auto-created HOME dirs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- extract buildSandboxPrefix/buildRunuserSuffix into shared sandbox.ts
- super admin bypasses bwrap for full host access (claude, pi, terminal)
- member pi processes now use bwrap instead of sudo -u
- member terminals now use bwrap instead of sudo -u
- mount /run for systemd-resolved DNS inside sandbox
- pass role through claude spawn params and channel types
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces prompt injection workaround with a proper MCP server that dynamically
discovers marketplace tools and exposes them as callable tools to Claude Code.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add agent-agnostic tool registry (tool-registry.ts) that discovers tools from disk
- Embed tool-loader extension as platform infrastructure (ensure-tool-loader.ts)
- Inject tool context into Claude prompts on first message
- Add marketplace tool sync (sync-marketplace.ts)
- Fix model selector defaulting to claude-code when no model explicitly selected
- Exclude tool-loader-source.ts from tsconfig (Pi-specific deps)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The PUT endpoint was re-merging old DB messages (which already included
previous merges) with disk messages on every turn, causing duplication.
Now the client passes resumedMessageCount so the server always slices
only the original pre-resume messages before concatenating.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>