Attachments copied into a local computer's workspace were read whole from
the artifact store first, so a 500 MiB video sat in memory before it was
written to disk. attach_file did the same in the other direction: a large
file on a local computer was read whole before it was stored.
SandboxProvider gains optional readFileStream/writeFileStream. The desktop
(host workspace) provider implements them with the same path confinement
as its byte methods, one chunk in memory at a time, and removes a file
whose write fails or is aborted, including one that replaced an earlier
copy. The host-aware wrapper routes them, and providers without them
(remote computers, still capped at 12 MiB) receive the collected bytes.
Current-turn staging and attach_file now go through these helpers, and
unread streams are cancelled so the stored file is released. Media job
inputs stop copying when the job is cancelled.
The stream helpers move from the local artifact store into a shared
module so both use one implementation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
Since every project has its own main conversation, the journey that
schedules for a specialist found the user's main conversation with
findFirst over all of them. With the Lab project's conversation also
matching, it picked either one, so the check failed about one run in
three. The lookup now names the home space, where the specialist joined
and the routine is created.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* chore: remove legacy orchestrator, trading and one-off script leftovers
The repository still carried the pre-evOo prototype: a Python orchestrator,
the old HTML "evoo-platform" cockpit (whose test ran in the root unit
suite), an apex-trading package, the hot-reload deploy.sh for it, a
permissions manifest, an architecture note with local file links, agent
handoff notes, a Steve Jobs persona template and about ninety one-off
scripts that probe a developer database with hard-coded local URLs. None
of it is used by the product, CI or the real deploy in ops/evo-deploy.
Delete those files, drop the evoo-platform test glob from the root vitest
config, the Cockpit section from .env.example and the dead biome and
gitignore entries, and update the docs that listed the removed tools.
scripts/start-tmux.sh no longer starts the removed sentinel and resolves
the checkout from its own location instead of a machine-specific path;
scripts/seed-ollama.ts requires DATABASE_URL instead of falling back to a
local URL with a username.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* fix(web): drop trading and persona prompts from the conversation shell
The web shell still showed leftovers from the trading prototype: composer
chips with German trading and "Steve Jobs" prompts, trading suggestion
chips under the empty conversation line, a German trading hero for the
empty transcript, and a German "data protection" status panel in the
sidebar. They suggested actions the product does not have and added
persistent chrome.
Remove the composer chips and their helper, the empty-state chips and the
hero (the empty conversation keeps its existing one-line start hint), the
sidebar trading indicator and the data trust panel. The new-bot E2E now
asserts the plain empty state; the sidebar spec no longer expects the
trading status, and the placeholder Steve Jobs spec with no assertions is
gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* refactor: remove the trading bot status integration
With the sidebar trading indicator gone, the only remaining consumer of
the trading bot status was a banner in General settings that reused the
same component. evOo is a personal assistant; a status proxy and poller
for one external trading service, configured through service-specific
TRADING_BOT_URL and VITE_TRADING_BOT_URL variables, does not belong in
the core product.
Remove the settings banner and its prop plumbing, the web poller, the
indicator component, the /api/trading-bot/status proxy, the shared core
contract, the env loading and both variables from .env.example, together
with their tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* chore: remove one-off database scripts and a stale review note
packages/db carried four hand-run scripts outside the package build that
query or seed a developer database with a hard-coded connection string,
one of them for a specific account. docs/VOICE-FIRST-REVIEW.md was a
one-time verification note for a workflow that no longer exists and
named private machines and local paths. Nothing references them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* chore: replace machine-specific paths and addresses with placeholders
ORIGIN.md pinned the intended checkout to an absolute path under a
personal home directory, docs/LOCAL-SETUP.md listed folders on one
developer's machine, and two tests used what looks like a real tailnet
address. Use a home-relative path, drop the machine-specific folder list,
and use 100.100.100.100 in the tests; it is still inside 100.64.0.0/10,
so both tests check the same thing as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* test(web): drop the trading status from the shell contrast test
main's contrast test now waits for the sidebar trading status before
measuring; this branch removes that status.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
Each project (space) now has a main agent and a main conversation of its
own. The user talks to it, and it delegates to the project's specialists.
Creating a project sets both up and opens the conversation, and deleting
an otherwise empty project removes them with it.
Claude-Session: https://claude.ai/code/session_01R8hSsKTB3W3e61wD6kvzur
Co-authored-by: Claude <noreply@anthropic.com>
Newer ffmpeg releases refuse to encode JPEG from limited-range video
frames, so poster frames failed on CI's ffmpeg while older versions
accepted the same command. The poster is converted to full-range
yuvj420p before encoding, which every supported ffmpeg version takes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
The artifact pane from main loads and previews files through the streamed
artifact route like the file card, so its hook takes only the artifact id.
attach_file keeps media types and gains present, which marks image and
file blocks for the pane.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
The live screen opens wide beside the chat while the bot works on it or asks
for a takeover, a file the bot presents as its result opens next to the
conversation, and a question that scrolled away gets a "Needs you" pill back
to it. Shared core rules derive the focus from signals clients already have
(pointer events, takeover requests, a new `present` flag on attach_file,
pending asks), move the pane only on focus edges, never replace a pane the
user opened, and learn per bot whether the screen opens on its own. Pane
widths persist per device; mobile shows a pill that opens the computer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7VSRHHzyt2KwoCkUkZ2Gc
spaces.create created the space first and only then checked the folder,
so a folder that was missing or not a folder returned 400 but left the
new space behind. The New project dialog shows that error, so fixing the
path and submitting again created a second project with the same name.
The folder is now resolved and checked right after the deployment-owner
check, before the space exists, and the space stores that resolved path.
wireProjectWorkspace takes the resolved folder instead of resolving it
again after the space was created. The onboarding scan, its other
caller, resolves and checks each repository folder the same way before
creating the project, and skips a folder that cannot be linked.
The authorization test has the deployment owner submit a missing folder
and a file (both 400) and then the real folder under the same name, and
checks that exactly one project with that name exists.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012HG4pFTUBDpuCS2Y44TV8x
Bots now announce every click and typing step before it runs, and the
web app draws a cursor in the bot's colour that glides to that point
over the live computer screen, ripples on clicks and shows typing.
- contracts: ComputerPointerPayload on the existing thread.computer event
- executor: computer_act and browser_act announce pointer steps
(best effort); the page-browser helper reports element screen boxes,
which are stripped before results reach the model
- web: agentPointerFeed + AgentCursor over the side-panel preview and
the full-window screen, mapped through the noVNC letterbox
- in-app browser: native WebContentsView on desktop (own session, http(s)
only, clamped bounds, back/forward, reload, DevTools, load errors) and
a sandboxed iframe on the web; replaces the dead panel, the fake
50%/40% cursors and the unimplemented devServer bridge
Claude-Session: https://claude.ai/code/session_01WiGooXtsKrZQmdszepRrNi
Co-authored-by: Claude <noreply@anthropic.com>
spaces.create linked any workspacePath to the new project, and
computer.files and computer.readFile then listed and read files under
that folder for anyone in the space. On a deployment with signups, any
member could link / or the API's own checkout and read whatever the API
process can read, such as its .env file. Linking also ran git in the
chosen folder and stored its remote URL.
A linked folder lives on the host, and the host is the deployment
owner's machine, the rule the onboarding scan already follows:
- spaces.create refuses a folder from anyone but the deployment owner,
before the space is created or the folder is touched.
- Project files are listed and read only for the deployment owner, so a
space a member linked before this change stays closed too. Space
navigation no longer shows a linked folder's path and git remote to
other members.
- A linked folder is stored as its real path, and listing and reading
follow symlinks only while they stay inside it. Symlinks that leave
the folder or lead nowhere are left out of a listing instead of
failing it.
- The web New project dialog offers the folder field only to the
deployment owner.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EyQw3AEaHYXegvgvTPAsi1
The scan's project phase called prisma.space with fields the Space model
does not have (title, path). Both calls threw, a per-repository
try/catch swallowed the errors, and no project was ever created. With
the field names fixed, the space would still have had no organization
or membership, so nobody could have seen it.
The phase now goes through createSpaceForMember and
wireProjectWorkspace, the helpers behind spaces.create: each repository
becomes a project of the deployment owner in their current
organization, linked to its folder with its git remote, branch and
head. It still runs only in the owner's scan with ONBOARDING_SCAN_HOME
set. A repository whose real folder one of the owner's projects already
links is skipped, so scanning again creates nothing; the phase stops at
the per-member space limit; and a second start while a scan runs (a
StrictMode remount, a retry) joins it instead of linking everything
twice. Discovery no longer descends into dot folders, where shell and
editor plugin checkouts would otherwise become projects. The Prisma
client is typed, so a field the schema lacks is a compile error again
instead of a swallowed one.
readGitCheckout loaded child_process with require(), which does not
exist in the ES module runtime the API runs in, so linking any folder
with a .git directory threw "require is not defined", in the scan and
in the New project dialog alike. It now imports spawn statically; the
module is only reachable through its server-only subpath, so browser
bundles still never load it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ebcq9seT9eCoHtqxuWEUzg
AGENTS.md asks for top-level `import type` instead of inline `type`
specifiers; this applies it to the import statements the media work added
or changed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
The main conversation lives in the home space, but the user can have another
space selected while it is on screen, and the card's actions sent that space:
media.jobs.start resolved the conversation in the selected space and failed,
and media.jobs.get/cancel only found jobs of the selected space.
The server now takes the space from the card itself. start finds the card's
message in one of the actor's own threads (the conversation named by botId or
groupId), requires the actor to still be a member of that thread's space, and
resolves the conversation and the source artifacts there. get and cancel find
the actor's job by id and require membership of the job's space.
media.capabilities takes an optional job id and answers for that job's space;
the card asks through its job and shares the answer per conversation. Every
other case is a 404, as before.
A Postgres test runs a card in the home-space main conversation while another
space is selected (start, get, capabilities, cancel) and checks that another
user and a user who left the card's space get 404 on every procedure.
This also completes the media settings section committed earlier as work in
progress (01eea83); its code was final, only that commit message said
otherwise.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
The model saw edit_video and export_video with artifact_id next to
generate_image, generate_video, edit_image and transcribe with artifactId,
sourceArtifactId and negativePrompt. Mixed conventions in one tool list invite
wrong calls, so all media inputs now use snake_case like continue_after and the
other builtin tools: artifact_id, source_artifact_id, negative_prompt,
duration_seconds. Handlers, artifact checks, the media card's transcribe
action and the tests follow. Nothing has shipped, so stored job inputs need no
migration.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
The shell contrast test added on main measured the sidebar trading status,
which this branch removes; it now measures the rows that remain.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
Node's default 5-minute requestTimeout cut off a 500 MiB video upload on
any uplink slower than about 14 Mbit/s, so the video cap was unusable over
ordinary connections.
- The API server's requestTimeout is 30 minutes, enough for the largest
upload at about 2.5 Mbit/s. headersTimeout keeps its 60-second default.
- Each streamed upload aborts when the client sends no bytes for 60
seconds: the route answers 408, the partial file is deleted and no row
is written, so a slow-drip client cannot hold a connection for the full
request timeout. Time spent writing to disk does not count.
- The store and the upload guard no longer wait for a body to confirm its
cancellation, which a stalled or hostile client may never do.
- A guard test checks that every file the executor attaches records the
thread's group, which the render_plot chart fallback once dropped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
Dictation falls back to the owner's transcription server when the voice
provider cannot transcribe, so that server is a voice recipient too: it is
listed for voice use (as "Transcription (<origin>)") whenever it would take
the recording, and not when the voice provider transcribes itself.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
A card that only says "Transcript" does not tell two transcriptions apart,
and it is English whatever the user's language.
- transcribe takes an optional title like the video operations, so a bot
names the card in the user's language; "Transcript" stays the default.
- Transcribe on a media card passes the source file name as the title.
- The E2E seeds the card through bootstrap as well as threads/get, since the
bot thread's first snapshot arrives with bootstrap.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
The image E2E now runs in the main conversation, which is a group thread,
so the browser test covers group uploads and reads from the artifact route
as well as bot threads. Bot file attachments keep their run's operation id
for tracing, and the non-streaming attach cap is the inline cap directly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
Generated clips (nothing binary committed) go through extractAudio: a video's
audio reaches the server as 16 kHz mono WAV, a 12-second recording is
uploaded in two ranges under a small limit, and a video without sound is
refused before anything is uploaded. Skipped where ffmpeg is missing, which
may include CI runners.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
Edit cards showed English words such as "crop · flip · extend" to every user.
edit_image now takes an optional title written by the bot in the user's
language, used for the card and the file name like edit_video. Without one,
the card falls back to the repaint prompt or to language-neutral tokens only:
sizes, ratios, degrees, arrows and format names.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
Brings in ffmpeg (probing, extractAudio) and the video operations. The
transcribe registration now gets the ffmpeg audio tools, so video is
transcribed through extractAudio with the demuxer forced from the MIME type,
and long recordings are converted and uploaded in ranges.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
- A handler that ignores its abort is left behind after the grace period;
a late rejection from it (or from the grace timer after the job settled)
was unobserved, and the worker exits on unhandled rejections. Both sides of
the race are now observed and the grace timer is cleared with the job.
- Ending a job removes its temp directory when no runner in this process
holds it (leftovers of a runner that died here), and reconciliation sweeps
directories of jobs that no longer run.
- An executor test runs a media tool end to end through applyTool: shown
only for operations the space can run, started with the run's scope and
effect key, continue_after split off, refused when not exposed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
A Postgres suite starts edit_video and export_video through the real
runtime and local artifact store: outputs become linked versions with
posters on the card, exports store one version per preset, appended clips
and subtitle files of another user are refused at start, and an
out-of-range cut fails the job with a readable error. It skips without
Postgres or ffmpeg.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
- The GPU guard waited for free VRAM at the operation's floor even on a card
whose total memory is below it (an 8 GB card and a 12 GB video floor), so
such jobs deferred until their budget ran out. The requirement is now capped
at 90% of the card's total; a card with less than half the floor fails at
once with "The GPU does not have enough memory for this job."
- Tests pin that media tools reach a run only through the space's
capabilities and within the bot's allowlist now that the image tool
definitions exist.
- Type-only imports are top-level `import type` in the media runtime, tools,
RPCs and tests (AGENTS.md).
- The ComfyUI settings test gives saveMediaSettings a store with nothing saved,
since the save reads the previous settings before validating.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
The card's follow-ups now reach the media job RPCs, and it only offers what
the space can run.
- media.capabilities answers which operations the actor's space can run
(booleans only). The card asks once per minute per space, and only when it
shows finished video or audio, so other cards cost nothing.
- Export and Transcribe call media.jobs.start with the card's message, so the
same bot posts the new card; Cancel calls media.jobs.cancel. A refused
start shows the server's message under the card.
- New files use top-level import type.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
Picks up ComfyUI generation, media tool dispatch and the streaming runner.
The video tools now share the image tools' schema helper, and both sets of
operations join the runtime's operation list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
Uploads with a text/plain, text/csv or text/markdown body are CORS "simple"
requests that a browser sends cross-site without a preflight. The session
cookie's SameSite default already keeps it off such requests, but a
deployment that relaxes it would accept uploads from any page. The route now
applies the same origin rule as CORS before reading the session.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
Brings in the media job RPCs, generic media tool dispatch and the image
operations; transcribe joins the operation list and the tool catalog next to
them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
Agents only received the documents of a turn on their computer and could
attach workspace files up to 10 MiB, so they could neither cut a clip a user
sent nor post the video they rendered.
- Current-turn images, video and audio are copied into the bot's workspace
(Team Computers under bots/<botId>/) like documents. Remote computers move
files as base64 JSON, so anything above their 12 MiB transfer cap stays in
storage and the agent is told it by artifact id instead of failing the
turn; local computers copy up to the kind cap.
- attach_file accepts video and audio by extension and attaches up to the
kind cap when the store streams (still bounded by the transfer cap of a
remote computer), stored through the shared artifact helper with a real
SHA-256.
- The render_plot fallback that attaches a chart PNG now records the group,
so the chart opens in group conversations.
- Agent history names image artifacts by id, like video and audio, so a
later turn can refer to them.
- artifacts.get refuses files above 25 MiB: base64 reads hold the whole
file in memory, and larger media is served by the artifact route.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
A transcript is whatever was said in the recording, so the summary the bot
is woken with quotes it and says it is not instructions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u