127 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a36b45b791
|
Let computer git reach GitHub through the broker with working credentials (#210)
* Let computer git reach GitHub through the broker with working credentials The broker URL handed to computers pointed at the supervisor's loopback address, which a container cannot reach, and host git ignored the integration token for HTTPS remotes. Computers now get the host through host.docker.internal, host git receives the token via a one-off credential helper, and clone paths and the working directory map from the computer onto the host workspace. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ * Serve the GitHub broker to computers on a Linux host-run supervisor A loopback-bound supervisor is unreachable from containers on Linux, so it also serves the broker routes alone on the default bridge gateway, which only containers on the same host can reach. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
ff7e95a081
|
Find connected-app tools from queries written in words (#209)
* Find connected-app tools from queries written in words Catalog search matched the query only as one substring, so "search repositories" or "get file" found nothing for search_repositories and get_file_contents and the agent concluded GitHub had no tools. Underscores, hyphens and spaces now compare equal, a query matches when every word starts a word of the tool, and a miss returns the source's tool names. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ * Bump Expo SDK 57 packages to the patch versions expo install expects expo install --check now expects @expo/ui 57.0.25, expo 57.0.30 and expo-router 57.0.28, which turns the mobile typecheck red on every branch. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
0b3ce11df8
|
feat: recruit library agents into project conversations and scan project folders (#206)
* media: check ffmpeg filter support before subtitle operations * sandbox: handle synchronous spawn errors safely in desktop sandbox * Voice-first streaming, non-blocking follow-ups, quiet routines and chat UX Why: questions blocked the chat, follow-up messages had no acknowledgement, message actions sat beside the bubble, and voice turn-taking added latency. What: waiting runs accept steering messages and the bot marks each claimed follow-up as seen; unclaimed follow-ups show as queued. Reaction, copy and reply controls sit under each message. Streaming voice (VAD endpointing, barge-in that stops the run, clause-based TTS), an optimistic input queue, procedural earcons and delta-based quiet routines were added. Onboarding can be skipped straight to the main conversation. Dual-surface layout and project pickers are included. Status colors use semantic tokens. Tested: unit suites for core, adapters, api and web pass, typecheck and Biome are clean. Browser end-to-end tests were not run locally. * Update translation catalogs Re-extracted catalogs for the new and moved strings; the catalog sync test passes. * Add project plan and test readiness notes Notes from the milestone work: feature inventory, test infrastructure and test readiness. No code changes. * fix(ci): align scale guards, space deletion, computer toggle, and mobile expo deps * fix(ci): restore space deletion guard, browser toggle, and run-steps contracts * fix(ci): restore model resolution errors, group stage, and onboarding flow * fix(ci): restore in-app-browser close button, disable stop button while sending, and sync lingui catalogs * fix(ci): address web e2e failures for bot-crud, choice cards, message quote, and voice onboarding * test(web): wait for avatar and rig visibility in oo-avatar motion spec * test(web): wait for persisted message IDs in message-hover-actions pagination spec * feat: recruit library agents into project conversations and scan project folders * test(web): expect repo description in GithubRepoPicker onChange * Onboarding: always drop the local voice when the voice step is skipped Skipping waited only for the latest voice load and disconnected only when the client saw the connect succeed. An earlier or client-failed connect could still persist the local credential, so a user who skipped voice was left with one. Wait for every load and always disconnect. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wy1md9QE1ayJbDCwJ4vAQs * Shell: hide the optimistic copy once the transcript shows the send A message sent while the bot works streamed into the transcript before its send request resolved, so the same text showed twice until the optimistic bubble was removed. Drop an optimistic message as soon as a persisted user message with the same text appears. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wy1md9QE1ayJbDCwJ4vAQs --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
ca869561ac
|
Voice-first streaming, non-blocking follow-ups and chat UX (#205)
* media: check ffmpeg filter support before subtitle operations * sandbox: handle synchronous spawn errors safely in desktop sandbox * Voice-first streaming, non-blocking follow-ups, quiet routines and chat UX Why: questions blocked the chat, follow-up messages had no acknowledgement, message actions sat beside the bubble, and voice turn-taking added latency. What: waiting runs accept steering messages and the bot marks each claimed follow-up as seen; unclaimed follow-ups show as queued. Reaction, copy and reply controls sit under each message. Streaming voice (VAD endpointing, barge-in that stops the run, clause-based TTS), an optimistic input queue, procedural earcons and delta-based quiet routines were added. Onboarding can be skipped straight to the main conversation. Dual-surface layout and project pickers are included. Status colors use semantic tokens. Tested: unit suites for core, adapters, api and web pass, typecheck and Biome are clean. Browser end-to-end tests were not run locally. * Update translation catalogs Re-extracted catalogs for the new and moved strings; the catalog sync test passes. * Add project plan and test readiness notes Notes from the milestone work: feature inventory, test infrastructure and test readiness. No code changes. * fix(ci): align scale guards, space deletion, computer toggle, and mobile expo deps * fix(ci): restore space deletion guard, browser toggle, and run-steps contracts * fix(ci): restore model resolution errors, group stage, and onboarding flow * fix(ci): restore in-app-browser close button, disable stop button while sending, and sync lingui catalogs * fix(ci): address web e2e failures for bot-crud, choice cards, message quote, and voice onboarding * test(web): wait for avatar and rig visibility in oo-avatar motion spec * test(web): wait for persisted message IDs in message-hover-actions pagination spec * Onboarding: always drop the local voice when the voice step is skipped Skipping waited only for the latest voice load and disconnected only when the client saw the connect succeed. An earlier or client-failed connect could still persist the local credential, so a user who skipped voice was left with one. Wait for every load and always disconnect. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wy1md9QE1ayJbDCwJ4vAQs * Shell: hide the optimistic copy once the transcript shows the send A message sent while the bot works streamed into the transcript before its send request resolved, so the same text showed twice until the optimistic bubble was removed. Drop an optimistic message as soon as a persisted user message with the same text appears. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wy1md9QE1ayJbDCwJ4vAQs --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
f14b112cdd
|
Broker GitHub Integrations token for computer git/gh (#203)
* Broker GitHub integration token for computer git/gh Use the Integrations GitHub MCP secret for supervisor broker sessions so home and project computers can clone private repos without PAT prompts. Add rakazo git/gh shims, push policy, deploy image rebuild, and tests. Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * ci: retrigger product checks after runner timeout Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * ci: run product change detection on GitHub-hosted runners Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * style: format deployment-github-token Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * ci: self-hosted change detection with timeout and fail-open gate Keep Detect product changes on evoo-ci with a 10-minute cap, continue-on-error filtering, and a fail-open product flag when detection fails. Downstream jobs and Product CI treat a failed or timed-out changes job as full product impact. Add job timeouts on remaining self-hosted ci jobs. Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * fix: continueRun tests and broker home path guard - Skip broker workspace resolution when computer homeKey is absent - Add shared continue-run Prisma/home/secretStore test stubs - Update computer PATH expectation and evo-deploy command sequence - Remove unused executor imports flagged by Biome Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * style: Biome format and import order for broker CI Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * fix: compose YAML, typecheck for broker stubs and GitHub hint Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * ci: retrigger Product CI after compose and typecheck fixes Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * ci: retry Product CI queue Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * ci: give self-hosted Web E2E a 45m job timeout Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * ci: extend Web E2E job timeout to 60m on self-hosted runners Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * fix: keep github-broker-token off the core browser barrel Export broker HMAC helpers only from @rakazo/core/node/github-broker-token so Vite does not pull node:crypto into the web client and break Playwright signup. Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> |
||
|
|
22fa2e1606
|
Accept more argument shapes for connected-app tools and name what is missing (#202)
Catalog execute calls now merge parameters flattened next to an empty arguments object and unwrap parameters nested under keys like parameters or args, unless the tool itself takes that key. A failed validation lists the required and received keys so the agent can correct its call, and the prompt shows the expected call shape and says connected apps need no token. Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
eb777d63ee
|
Let the agent use GitHub tools and git on its computer (#201)
Catalog execute calls now accept arguments sent as a JSON string or flattened next to the id, the computer image ships git and curl, and the evo deploy rebuilds that image when its sources change. Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
d6835fe0b0
|
Keep schedule_create for later or repeated work (#200)
A live check showed the agent answering a business-idea question by creating a schedule that would run the analysis later. The schedule_create description now says never to use it for work the user wants now, however long. |
||
|
|
42eb420545
|
Agent replies without thinking aloud or tool names; chart-links E2E names its failure (#199)
A run of the real prompt against the local model showed reasoning written as chat text, internal tool names in a capabilities answer, and only a data fetch when asked to build a tool. The working principles now say that everything written is shown, to describe abilities in plain words, and that a request for a tool means building one. The chart-links spec now waits for the chart or its error and reports browser errors, so the recurring 90-second timeout names its cause. The ZIP upload spec matches the file chip exactly, since the agent reply can quote the name. |
||
|
|
f3b47cb7cd
|
Tell the agent what it can reach and how to work reliably (#198)
Agents denied a connected GitHub and did not know they could install skills: the prompt said "No plugins are connected yet." whenever only installed tool sources existed, and the skill catalog never mentioned skill_install. Connections and skills were also bound to the space they were set up in, so a project never saw them. - Name every connected app from the tools the run actually received, including lazy catalogs and how to reach them. - Tool sources and skills follow the user into every space; a skill in the current space wins a name clash. - Add shared working principles (do the work, verify before claiming, keep it simple, build a missing tool as a tested script plus skill). - Rewrite the skill catalog in plain words, mention skill_install, clip long descriptions and count skills past the limit. - Routine runs learn that nobody is waiting and history is not shown; schedule prompts must be self-contained. Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
4cd9468b6f
|
Install skills from a pasted GitHub link (#197)
A new skill_install agent tool reads every SKILL.md behind a public github.com repository, folder or file link and saves each as one of the user's own, editable skills. Only GitHub's API and raw hosts are contacted; invalid files and name clashes are skipped and reported. Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
4585d3b0ec
|
Add Second opinion, Ship and Founder builtin skills (#196)
* Add Second opinion and Founder builtin skills Second opinion has a fresh reviewer approve the plan and inspect the build, so whoever writes never approves their own work. Founder tests a business idea with simulated roles and a crowd of 100 simulated buyers and ends in a business plan card with a verdict. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ * Add Ship builtin skill for phased software work Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
3469165c92
|
Connect GitHub from a token alone (#195)
* Connect GitHub from a token alone, without a vendor account Integrations only reached GitHub through Composio or Pipedream, which a self-hosted instance does not have. A built-in GitHub row now opens GitHub's prefilled token form, and the pasted token connects GitHub's own MCP server through the existing installed-connector path. Web and mobile. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ * Sync web catalogs for the GitHub connect strings Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
797906dde2
|
Allow repository ZIP uploads up to 5 GiB (#194)
* Allow ZIP uploads up to 5 GiB and stream them onto Docker computers Repository archives could not be picked at all: application/zip was not an allowed attachment type. ZIPs are now allowed with their own 5 GiB cap, upload as a stream, and reach the bot's Docker computer through a new streamed supervisor write that commits only after the body ended cleanly. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ * Use a disallowed type for the invalid-attachment check now that ZIP is allowed Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
a746783c48
|
ci: keep every job on internal runners without GitHub Actions storage (#192)
Runs used GitHub's artifact storage and cache service, which ran out of the monthly included quota and blocked publishing. Caches and job handoffs now stay on the self-hosted runner host, the desktop release hands builds over through its draft release, and Windows waits for a self-hosted runner instead of a GitHub-hosted one. A test enforces it. Claude-Session: https://claude.ai/code/session_01R8L4XgqjAemFR2remB5hVY Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
6ace14c3c8
|
Projects: one sidebar row with icon and color, coordinator overview (#190)
Each project is now a single sidebar row with its own icon and color that opens the project's coordinator conversation. The whole row drags to reorder. The row menu gains Customize for name, icon and color. Inside the coordinator conversation, an overview lists the project's agent threads that need the user, are running, or recently finished. Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
1c8d13a837
|
Open an existing project instead of creating a same-named twin (#189)
Creating a project whose name a member already uses (from the dialog, the agent's create_space tool or the onboarding scan) now returns that project, so the sidebar no longer lists two projects with one name. The conversation picker lists the main agent first. Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
5cf9d679b4
|
Fold a run's step messages behind its latest message (#188)
* Fold a run's step messages behind its latest message Long tool runs posted every step as its own bubble, so the answer drowned in narration and a summary written before saving a file appeared twice. Web and mobile now fold a run's text-only step messages behind one "N steps" line above the run's latest message. The thread snapshot also stops replaying live narration that was already posted, which showed steps again after a refresh. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ * Keep a run's last step visible above a card A card such as the peer-message receipt has no words of its own, so folding the narration into it hid everything the bot said. Steps now fold only into a written answer or the live row; before a card the last step stays visible. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
83d2106633
|
Quieter chat chrome: no repeated names, no English working line, no tool names (#186)
* Name a run's chat once when its group carries the bot's name A project's main conversation is named after its lead bot, so Today showed "Chief · Chief". Web and mobile now share one title helper that drops the repeated name. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ * Drop the "1:1 with" line under a conversation's title The stage header repeated what the chat already shows, such as "1:1 with Chief" under a project's name. The title and the phase pill remain. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ * Let the stage's translated thinking lines speak while a bot works A running bot raised a "working" awareness event whose English line, such as "Chief is working on your request.", replaced the translated thinking lines and added an "In progress" strip next to the phase pill. The strip now only shows collaboration. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ * Ask bots to describe their steps without naming tools Long runs showed lines such as "I'm using browser_navigate" in the chat. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ * Announce the stage phase as status and keep following after a tap The working strip used to be the only status region while a bot worked; the stage's phase pill now carries that role. A tap at the latest message no longer stops the transcript from following new messages. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
f720683496
|
Unstick chats waiting on the user and show them where the user looks (#185)
* Resume a waiting run after takeover even when its execution lease is gone When resuming a run failed while it waited for the user, the run stayed waiting_takeover but lost its execution lease. Taking over then bound no run, so handing control back resumed nothing and the chat hung at the computer. Takeover now binds the newest leaseless waiting run. The stage also counted waiting runs as work and showed "working" while the bot waited on the user; it now listens instead. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ * Tell every bot it is an agent in evOo, not the model behind it Local models introduced themselves as the assistant product they were trained as and even asked for that product's login. The identity block now names evOo, and the fallback prompts say evOo instead of Rakazo. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ * Show runs waiting on the user in Today from every space Today and its badge listed only runs of the selected space, while the sidebar shows chats from all spaces. A chat waiting on the user in the home space therefore showed "needs you" in the sidebar and "All quiet" in Today. The activity list now spans every space the user belongs to, carries each run's space, and opening a run switches to that space on web and mobile. The runs-list Postgres suite was not part of the integration run; it is now, with a case for a waiting run in another space. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ * Keep a project's main conversation to its own asks Every main conversation showed the inbox of all the user's spaces, so a takeover waiting in the home chat sat above the composer of every project. Only the home space's main conversation collects other spaces now; a project shows its own items. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ * Also resume a waiting run that sits behind an older run's inactive lease Inactive execution leases stay as fencing tombstones, so a run that lost its lease can wait behind an expired lease of an earlier run. Takeover now looks for the bot's waiting run whenever the lease is neither busy nor bound to a waiting run. Journey 4f covers both the released and the stale lease. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ * Translate the voice awareness labels and the computer card's state The voice strip showed hardcoded English labels such as "In progress", and the computer card showed its stored "Needs you" state untranslated in German chats. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
8ae3764b05
|
Speak German through a local Piper server, since Kokoro has no German voices (#182)
Kokoro-FastAPI ships English, Spanish, French, Hindi, Italian, Japanese, Portuguese and Chinese voices only. The German presets pointed at voice ids no Kokoro server has, so German users either got English voices or picked a voice that failed to speak. The local voice now also lists the German voices whose Piper model is installed (PIPER_BASE_URL) and speaks them through Piper's /synthesize as WAV. Thorsten, the clearest model, is the German default. Mobile plays WAV as well as MP3, and the E2E voice stub stands in for Piper. Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
f07d17e927
|
Oo computer browser: live Chromium, cowork, AgentCursor (#179)
* feat(web): Oo computer browser panel with cowork and local preview Wire the in-app browser panel to the active Oo's live Chromium via the computer screen stream, page-browser navigation, and interactive cowork input with AgentCursor. User browser actions become system meta lines in thread context; localhost preview remains as Lokale Vorschau. Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * fix(i18n): sync Lingui catalogs for Oo browser panel Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * Fix the Oo browser's lint and E2E failures The shared-browser overlay is a focusable input surface, so it gets the application role and a translated label. The local preview focuses the address field again when no page is open, which the split out of InAppBrowser had dropped. The Oo browser spec opens the panel by its exact name, because the signed-up user's name also contains "Browser", and reads the user action from the RPC body's json envelope. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wy1md9QE1ayJbDCwJ4vAQs --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
97787a0272
|
Production readiness phase 1: journey audit, fixes 1–4, E2E (#175)
* Production readiness phase 1: journeys doc, onboarding persistence, teardown, chat UX - Add docs/PRODUCTION-READINESS.md with journey matrix and defect backlog - Persist onboarding draft in session storage across reload - Localize onboarding discovery main-bot role; German Chief of Staff string - Destroy team sandboxes and home folders when deleting an account - Keep composer/transcript visible during queued runs; extend send watchdog - Add Playwright specs: reload persistence, DE intake, queued progress, console guard Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * Fix CI: sync Lingui catalogs, onboarding name draft, E2E stability - Localize Chief of Staff in discovery shell; sync all locale PO files - Propagate Oo name to onboarding draft while typing - Adjust DE intake, queued-run, and console-error E2E specs Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * Format chat-queued-progress E2E spec Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * fix(api): scope account-delete sandbox teardown to owned spaces Only destroy team sandboxes and homes for spaces the deleting user owns with no remaining members or bots. Guard home removal under dataDir/homes. Add unit and postgres journey coverage. Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * chore: apply biome formatting Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> |
||
|
|
ba89574c83
|
Align Oo avatar eye spacing with evoo-logo (#173)
* Align Oo avatar eye spacing with evoo-logo overlap ratio Derive small-eye placement from evoo-logo.svg center spacing (~0.94 of r_big + r_small) for the shared rig, EvooMascot brand lock, and face crops. Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * Format brand-lock spacing test for Biome Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * Align mobile Expo packages for install --check in CI Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * ci: retrigger after flaky chart-links E2E Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> |
||
|
|
f8b4ce4b6d
|
Größere Header- und Sidebar-Icons (Web + iPhone) (#172)
* Increase header and sidebar icon scale on web and mobile Raise shared --icon-sm/md/lg tokens (~25%) for web shell chrome, widen chat header action controls, and align Expo home/thread header glyphs with 44pt tap targets. Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * ci: retrigger checks after flaky evo-deploy test Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> |
||
|
|
a55ed44394
|
One product: evOo updates and name, phone actions, German on mobile, working account deletion, consistent settings (#168)
* Pull updates from evOo instead of the upstream Rakazo repository The in-app server updater, the image-based compose files and the desktop auto-updater still pointed at elie222/rakazo, so updating an evOo install would have pulled Rakazo. They now follow cynarAI/evOo-local, where the publish workflow already pushes the images. The disabled Rakazo production deploy job is gone from CI; evo deploys itself. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ * Call the product evOo everywhere people see its name The desktop setup window and menu, account emails, the iOS app name and permission prompts, sign-in headings, messages bots post into connected chat apps, the web fetch user agent, issue templates and the marketing site still said Rakazo, or EvOo. They now say evOo. The marketing site no longer calls evOo an alternative to itself. Internal identifiers (package scopes, env vars, bundle ids, the desktop productName that keys its data folder and keychain entry) stay as they are. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ * Open conversation actions by holding a row on a phone Pin, move to folder, mark unread, duplicate, archive and delete only opened on right-click, which iOS Safari never fires. Holding a conversation or a folder header now dispatches the same contextmenu event, so the existing menu opens under the finger; Android's own long-press event is not doubled and the lift does not also open the conversation. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ * Give small chrome icons a 44px touch area on phones The browser and computer toggles, the activity bell, the create button and the section plus buttons were 18 to 36 px targets. On touch screens each now gets an invisible 44px hit area without changing how it looks; the create button uses the same plus icon as the rest of the sidebar instead of a text glyph, and the computer toggle gets an accessible name. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ * Add German to the mobile app German speakers saw the phone app in English because the mobile UI only knew English, Simplified Chinese, and Russian. The app now ships a German catalog covering every mobile chrome string, follows a German device language (de, de-DE, de-AT, de-CH, ...), and offers Deutsch in the Account language picker. Strings that also exist in the web German catalog reuse its wording so web and phone say the same thing; the rest follow the same terms and the informal "du". Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ * Use the evOo sign-in strings in the German phone catalog Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ * Make account deletion work and give settings one section style Deleting an account posted to the auth endpoint without a JSON content type, so the server refused it and the button did nothing. The privacy page now deletes through the auth client and returns to sign-in. It also drops the claims it could not verify, the export button that only showed an alert, and its own padding, and shows where the data and the computer live. Notifications and media settings use the same SettingsSection. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ * Use status tokens, the shared switch and one German glossary Voice stage dots, the onboarding progress bar and the Voice First button used raw emerald, violet, cyan and rose; they now use foreground, success and destructive like the rest of the app. The routine editor's hand-built switch is the shared Switch. German says Unterhaltung for threads and Space consistently on web and phone. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ * Expect the evOo setup copy in the desktop e2e specs Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ * Translate the voice preview label in the German phone catalog Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ * Wait for the main thread URL without waiting for every resource openMainBotThread already navigated with domcontentloaded; its URL check then waited for the full load event, which can exceed 20 s on a busy runner. The composer check that follows covers readiness. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
b78eff1f7b
|
Run on the deployment's local models without any model choice, preselect Claude Opus 5.5 otherwise, explain why dictation is unavailable (#151)
* Recommend Claude Opus 5.5 by bumping the Pi model catalog The pinned pi-ai 0.85.1 had no Claude Opus 5.5, so the connect flow fell back to Opus 5. pi-ai and pi-agent-core 0.87.1 add it, and the keyless recommendation now preselects it. The new catalog names router aliases "Qwen Max Latest (Qwen3.8 Max)"; those now read "(auto-updates)" like the other alias labels, so no picker entry claims to be "latest". Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD * Name HTTPS as the cause when dictation is unavailable, and translate it Over plain HTTP on a LAN address, Safari hides the microphone and speech recognition, so voice mode said the browser had no dictation, in English inside a German UI. Dictation now checks for a secure context first and says to open the address with https://. Its other errors go through Lingui too, and Safari's blocked recognizer gets a clear message instead of "Dictation failed: not-allowed". Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD * Serve a fake Kokoro voice server in the E2E harness Onboarding's voice step connects to the local Kokoro server on its default port, so the shell E2E failed on any machine where nothing (or a misbehaving server) listened there: the failed request shows up as a browser console error. The harness now starts an offline stand-in and points KOKORO_BASE_URL at it, so the suite no longer depends on the runner host's own voice server. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD * Show only the recommended sign-in until a model is connected With no model connected, "Connect model" opened the full model settings: provider list, tiers and a model picker, even though the recommendation was already preselected. Until something is connected, the Models panel now shows just the preselected model with its sign-in and API key field; "Other providers" reveals the full settings. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD * Give the second chart-links test the same cold-load allowance as the first Each Playwright test runs in a fresh browser context, so the second test's first navigation re-fetches the lazily imported Plot chunk just like the first test's warm-up does. On a loaded CI runner that exceeded the default 20 s expect timeout. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD * Use the recommended model as the Anthropic deployment default A deployment that sets only ANTHROPIC_API_KEY and PI_DEFAULT_PROVIDER=anthropic now runs Claude Opus 5.5, the same model the connect flow preselects, instead of an older default. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD * Run every space on the deployment's local models without a connect step A deployment with PI_DEFAULT_PROVIDER=local and RAKAZO_LOCAL_MODELS set still asked every user to connect a model, because only OpenRouter and Anthropic counted as a deployment model. Configured local models now do, keylessly. RAKAZO_LOCAL_TIERS assigns local models to the tiers the agent delegates to, for spaces that have not mapped their own, and a RAKAZO_LOCAL_MODELS entry can name its own server (id=url) when the models run behind different endpoints. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD * Hide model choices when the deployment routes models itself When the server provides its own models, the system picks and routes them per task, so Settings no longer offers Models and bot settings no longer offer Model and Thinking, on web and mobile. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD * Wait for the workspace pane's width transition before measuring it The pane animates its width over 300 ms, so a single boundingBox read right after it opens can land mid-transition on a slow runner. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD * Allow chart re-renders inside the chart-links loops to take longer Each navigation in the loops renders Observable Plot again. On a loaded CI runner that exceeded the default expect timeout even after the warm-up, so the per-iteration visibility checks get 30 s. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
9062717eee
|
fix(evo-deploy): validate restart config before mutating checkout (#167)
Move restart-mode prerequisites (systemd unit default, tmux, compose file, script hook, EVO_SYSTEMD_SCOPE) ahead of git fetch/install/build/migrate. Explicit systemd with no EVO_SYSTEMD_UNIT falls back to EVO_SYSTEMD_AUTO_UNIT. die() after checkout triggers rollback; rollback marks in-progress to avoid loops. Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> |
||
|
|
eb9c8847a4
|
Brand videos with a logo overlay and an intro before the cut (#161)
* Export videos for named platforms and check each file against its limits Exports only knew aspect ratios, so a cut could still be rejected or degraded on upload: too long for the platform, over its size limit, or far off its loudness. export_video now takes platforms (TikTok, Instagram Reels and Feed, YouTube Shorts and YouTube, Facebook Reels, LinkedIn, X, Pinterest, Snapchat Spotlight), up to six targets with the presets. Each platform is rendered to its frame with the export's fit, its frame rate cap, a bitrate cap that also keeps the file under the size limit, and its loudness target in one loudnorm pass. Every render is probed and measured afterwards. A file over the size limit is encoded once more at a bitrate scaled from what it averaged, and audio more than 2 LU off the target gets one linear loudness pass. The result is stored on the output as a pass, warn or fail check with language-neutral issue codes. A platform the video is too long or too short for is not rendered: the card lists it with the reason, the other targets still render, and the bot is told to trim first. The limits come from the earlier profile research, with its sources, review date and unverified flag; content, disclosure and caption rules are left out because they need inference or a review flow. Preset definitions move to their own module so the platform schemas can use their aspect ratios without an import cycle. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * Add remove_silence step to edit_video Talking-head clips are mostly cut by hand to drop pauses, and the bot had no way to find them: it could only keep segments it guessed. The new remove_silence step finds every pause quieter than threshold_db (default -35 dB) and at least min_silence long (default 0.6 s), keeps padding (default 0.12 s) on each side so word onsets survive, and cuts the rest through the existing keep-segments graph, so picture and sound stay in sync. Steps run in order, so silence is detected on the timeline the steps before it leave: the planner gets an analysis mode that sends that audio through silencedetect into a null output, and ametadata prints the marks to a file in the job directory. The detected speech is handed back to the planner per step. Nothing silent makes the step a no-op; nothing left fails with a clear error. At most 200 pieces are kept, bridging the shortest pauses first, so the filter graph stays a sensible size. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * Offer platforms in the media card's export menu and show their checks The card's Export menu now lists the platforms first, by brand name and untranslated, then the aspect ratios after a separator; the platform an output was made for is left out, like its own shape. A platform export shows the platform's name under the player, or, when its check did not pass, one line for the first issue instead ("Too long for TikTok (max 10:00)"), in the warning or destructive color. Skipped platforms get the same kind of line, and they stand in for the error of a job that skipped every platform, so the card does not say the same thing twice in two languages. The E2E spec seeds a platform export with a failed check and a skipped platform, and captures the card and the open menu. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * Add analyze_video to find cut points for the bot To cut a social video well the bot has to know where the cuts fit, and it could not look at a video at all. analyze_video is a read-only media job: one ffmpeg pass prints scene changes (scene score above 0.3, one per cut), pauses (silencedetect with the remove_silence defaults) and short-term loudness (ebur128) to files in the job directory, and a transcript made from the video by transcribe (an SRT whose parent is the video) adds when each line is spoken. The job stores nothing; its card just says done. The result reaches the bot through the existing media job path: the job always wakes the bot that started it (the tool has no continue_after) and the summary carries a short legend plus compact JSON. The runtime's summary limit grows to 12,000 characters and the summary is fitted into it: at most 200 entries per list, then every list shrinks evenly, keeping the strongest cuts, the longest pauses and the start of the transcript, with omitted counts. The wake prompt no longer claims the card shows the result when a job made no files. Jobs read derived artifacts through a new fetchDerivedArtifact, scoped to the job's user like fetchArtifact. A migration admits the operation in the media_jobs check and adds the tool to existing Studio specialists that still edit video. The web card gets an icon; no new copy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * fix(media): keep one skipped entry per platform on the card A job that recovers after a crash runs again and reports the same skipped platforms; the newest reason per platform now replaces the earlier one instead of listing it twice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * chore(db): add the widened media job operation check as NOT VALID and validate it separately Follows the repository's migration convention: the constraint commits without scanning media_jobs, and a second migration validates it with a lock that lets reads and writes continue. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * Brand videos with a logo overlay and an intro before the cut edit_video gains an overlay step that lays a PNG, JPEG or WebP image over the video at a corner or the center, sized to a share of the frame width, with optional opacity and time range; tall frames keep the 9:16 safe areas clear. concat takes at: "start" so an intro can go before the video. The image is authorized like other inputs, read with its own demuxer and refused above 8192 pixels on a side. New Studio specialists are told to brand with the user's logo and intro. 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> |
||
|
|
459d7c99e4
|
Pick the voice from a carousel in the app language, local voice only (#166)
* Pick the voice from a carousel in the app language, local voice only Voice settings now list only the voices that speak the app language (German shows German voices, English shows English ones) as a horizontal carousel: one card per voice, a play button next to the name, tap to make it the active voice. Changing the app language moves the saved voice to that language. The hosted voice providers (ElevenLabs, OpenAI, Cartesia, Fish Audio) are removed; Kokoro on the server is the only voice. E2E runs against a stub Kokoro server so the carousel is covered. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KxoSHAyzMXvdtwcDJZcn57 * test(e2e): measure the workspace panel width after its transition settles Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KxoSHAyzMXvdtwcDJZcn57 --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
3b2c46dd36
|
feat(media): remove pauses and find cut points in videos (#159)
* Export videos for named platforms and check each file against its limits Exports only knew aspect ratios, so a cut could still be rejected or degraded on upload: too long for the platform, over its size limit, or far off its loudness. export_video now takes platforms (TikTok, Instagram Reels and Feed, YouTube Shorts and YouTube, Facebook Reels, LinkedIn, X, Pinterest, Snapchat Spotlight), up to six targets with the presets. Each platform is rendered to its frame with the export's fit, its frame rate cap, a bitrate cap that also keeps the file under the size limit, and its loudness target in one loudnorm pass. Every render is probed and measured afterwards. A file over the size limit is encoded once more at a bitrate scaled from what it averaged, and audio more than 2 LU off the target gets one linear loudness pass. The result is stored on the output as a pass, warn or fail check with language-neutral issue codes. A platform the video is too long or too short for is not rendered: the card lists it with the reason, the other targets still render, and the bot is told to trim first. The limits come from the earlier profile research, with its sources, review date and unverified flag; content, disclosure and caption rules are left out because they need inference or a review flow. Preset definitions move to their own module so the platform schemas can use their aspect ratios without an import cycle. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * Add remove_silence step to edit_video Talking-head clips are mostly cut by hand to drop pauses, and the bot had no way to find them: it could only keep segments it guessed. The new remove_silence step finds every pause quieter than threshold_db (default -35 dB) and at least min_silence long (default 0.6 s), keeps padding (default 0.12 s) on each side so word onsets survive, and cuts the rest through the existing keep-segments graph, so picture and sound stay in sync. Steps run in order, so silence is detected on the timeline the steps before it leave: the planner gets an analysis mode that sends that audio through silencedetect into a null output, and ametadata prints the marks to a file in the job directory. The detected speech is handed back to the planner per step. Nothing silent makes the step a no-op; nothing left fails with a clear error. At most 200 pieces are kept, bridging the shortest pauses first, so the filter graph stays a sensible size. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * Offer platforms in the media card's export menu and show their checks The card's Export menu now lists the platforms first, by brand name and untranslated, then the aspect ratios after a separator; the platform an output was made for is left out, like its own shape. A platform export shows the platform's name under the player, or, when its check did not pass, one line for the first issue instead ("Too long for TikTok (max 10:00)"), in the warning or destructive color. Skipped platforms get the same kind of line, and they stand in for the error of a job that skipped every platform, so the card does not say the same thing twice in two languages. The E2E spec seeds a platform export with a failed check and a skipped platform, and captures the card and the open menu. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * Add analyze_video to find cut points for the bot To cut a social video well the bot has to know where the cuts fit, and it could not look at a video at all. analyze_video is a read-only media job: one ffmpeg pass prints scene changes (scene score above 0.3, one per cut), pauses (silencedetect with the remove_silence defaults) and short-term loudness (ebur128) to files in the job directory, and a transcript made from the video by transcribe (an SRT whose parent is the video) adds when each line is spoken. The job stores nothing; its card just says done. The result reaches the bot through the existing media job path: the job always wakes the bot that started it (the tool has no continue_after) and the summary carries a short legend plus compact JSON. The runtime's summary limit grows to 12,000 characters and the summary is fitted into it: at most 200 entries per list, then every list shrinks evenly, keeping the strongest cuts, the longest pauses and the start of the transcript, with omitted counts. The wake prompt no longer claims the card shows the result when a job made no files. Jobs read derived artifacts through a new fetchDerivedArtifact, scoped to the job's user like fetchArtifact. A migration admits the operation in the media_jobs check and adds the tool to existing Studio specialists that still edit video. The web card gets an icon; no new copy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * fix(media): keep one skipped entry per platform on the card A job that recovers after a crash runs again and reports the same skipped platforms; the newest reason per platform now replaces the earlier one instead of listing it twice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * chore(db): add the widened media job operation check as NOT VALID and validate it separately Follows the repository's migration convention: the constraint commits without scanning media_jobs, and a second migration validates it with a lock that lets reads and writes continue. 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> |
||
|
|
23d70971f8
|
feat(media): export videos for named platforms and check each file against its limits (#158)
* Export videos for named platforms and check each file against its limits Exports only knew aspect ratios, so a cut could still be rejected or degraded on upload: too long for the platform, over its size limit, or far off its loudness. export_video now takes platforms (TikTok, Instagram Reels and Feed, YouTube Shorts and YouTube, Facebook Reels, LinkedIn, X, Pinterest, Snapchat Spotlight), up to six targets with the presets. Each platform is rendered to its frame with the export's fit, its frame rate cap, a bitrate cap that also keeps the file under the size limit, and its loudness target in one loudnorm pass. Every render is probed and measured afterwards. A file over the size limit is encoded once more at a bitrate scaled from what it averaged, and audio more than 2 LU off the target gets one linear loudness pass. The result is stored on the output as a pass, warn or fail check with language-neutral issue codes. A platform the video is too long or too short for is not rendered: the card lists it with the reason, the other targets still render, and the bot is told to trim first. The limits come from the earlier profile research, with its sources, review date and unverified flag; content, disclosure and caption rules are left out because they need inference or a review flow. Preset definitions move to their own module so the platform schemas can use their aspect ratios without an import cycle. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * Offer platforms in the media card's export menu and show their checks The card's Export menu now lists the platforms first, by brand name and untranslated, then the aspect ratios after a separator; the platform an output was made for is left out, like its own shape. A platform export shows the platform's name under the player, or, when its check did not pass, one line for the first issue instead ("Too long for TikTok (max 10:00)"), in the warning or destructive color. Skipped platforms get the same kind of line, and they stand in for the error of a job that skipped every platform, so the card does not say the same thing twice in two languages. The E2E spec seeds a platform export with a failed check and a skipped platform, and captures the card and the open menu. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * fix(media): keep one skipped entry per platform on the card A job that recovers after a crash runs again and reports the same skipped platforms; the newest reason per platform now replaces the earlier one instead of listing it twice. 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> |
||
|
|
4ab0d25224
|
Merge pull request #156 from cynarAI/claude/project-thread-vljeb4
Media library: owner folders that bots can search and import from |
||
|
|
738811dbe7
|
Read the web app at 16px base size on phones (#155)
Text sizes now follow a --font-scale variable that is 1 on wider screens and 8/7 below 768px, so the 14px base text reads at 16px on phones (Safari stops zooming into fields at that size) and every other size grows by the same ratio. One-off pixel sizes in the shell move onto the shared text tokens so they scale too. The stage subtitle now truncates inside the screen instead of spilling past both edges. Claude-Session: https://claude.ai/code/session_01PFEDgbyJm12ExL3Kn4Dk8B Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
044669edfc
|
Add a media library: owner folders that bots can search and import from
The deployment owner adds folders of photos and videos in the media settings. A per-folder background job indexes them (relative path, kind, size, capture date, dimensions, duration), re-indexes every six hours, and drops files that are gone. The owner's own runs get two tools: search_media (words in folder and file names, kind, capture date range, newest first) and import_media (copies one file into the conversation as an artifact). Files are never changed; hidden entries and symlinks are skipped, and an import must resolve inside its folder. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wy1md9QE1ayJbDCwJ4vAQs |
||
|
|
b3f20967db
|
Preselect the recommended model while none is connected (#150)
Without a model key the settings opened on the keyless deployment default (OpenRouter). The connect flow now opens on Claude Opus 5, so a new user only adds a key or signs in. A connected credential, an owner-chosen default, or a deployment key still decides as before. Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
54658ee689
|
feat: autopilot progress, composer start and morning summary (#146)
The autopilot's start line counts its steps and folds open to the latest checkpoints, and its result reads as a card. The composer's plus menu starts an autopilot with the draft as the order and a choice of until done, tomorrow morning or a time. Today lists the autopilots that run, wait or ended within a day, and their step runs no longer show one by one. Autopilot runs send no completion or failure push; only a pause notifies. Claude-Session: https://claude.ai/code/session_01UmSCnoCgXqBp2fU3vwz4o1 Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
c69f00e891
|
feat(avatars): bots offer a new Oo look from chat (#145)
update_bot takes a look (oo1: value), validated and normalized in core. A suggested look renders as a "New look" card with the avatar preview on web and mobile; accepting applies it to the bot. Claude-Session: https://claude.ai/code/session_01EKuME5CgL1ddGGN7LGvtBj Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
460eede226
|
feat: autopilot that keeps working on an order until it is done (#144)
An autopilot per thread continues the user's order run after run, with no time limit. Each run reports a checkpoint; after three runs without progress or three failed runs it pauses with Continue and Stop. Start, pause, stop and finish show as quiet lines in the conversation. Approvals during an autopilot run are parked as suggestions, and the temporary approval rules end with the autopilot. Claude-Session: https://claude.ai/code/session_01UmSCnoCgXqBp2fU3vwz4o1 Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
63578bb645
|
feat(avatar): dress up any Oo in the Atelier on web and mobile (#143)
The Atelier replaces Avatar Studio: pick a species, colour, eyes, glasses, hats, neckwear, arms and temperament, with every option drawn as your own Oo wearing it. Onboarding's "Design your Oo" now offers species looks per vibe and opens the Atelier for the rest; mobile bot settings carry the same editor instead of a bare colour row, so picking a colour there no longer throws away the rest of the look. Claude-Session: https://claude.ai/code/session_01EKuME5CgL1ddGGN7LGvtBj Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> |
||
|
|
6fd136e944
|
feat: suggestions the user accepts or dismisses with one tap (#142)
Bots can offer an exact tool call as a card. Nothing runs until the user taps Accept; the call then runs as its own approved run of the bot it was meant for, and the card shows the outcome. Open suggestions also wait in the inbox and expire after a week. Claude-Session: https://claude.ai/code/session_01UmSCnoCgXqBp2fU3vwz4o1 Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
55df4a6439
|
feat(avatar): let Oos react to what their bots do (#141)
A bot's Oo now cheers for a moment when its run finishes, the stage Oo follows the pointer and squishes when tapped, and chat headers show the speaking bot's own Oo instead of the generic mascot. Small avatars in lists only animate while working or waiting so long lists stay calm. Claude-Session: https://claude.ai/code/session_01EKuME5CgL1ddGGN7LGvtBj Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
5f3aa8f3fe
|
feat: recipe cards that scale servings and cook step by step (#140)
Claude-Session: https://claude.ai/code/session_01UmSCnoCgXqBp2fU3vwz4o1 Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
7db3a56fe8
|
feat(avatar): give mobile bots the same Oo face as web (#138)
Native avatars now draw the shared Oo rig with react-native-svg instead of a visor with two equal eyes, so the Oo eyes look the same on every surface. The robot/organic avatar setting is gone from web and mobile because every bot is now an Oo; uploaded pictures show on native too. Claude-Session: https://claude.ai/code/session_01EKuME5CgL1ddGGN7LGvtBj Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
47988602e2
|
feat: let bots answer with cards the user taps (#137)
Claude-Session: https://claude.ai/code/session_01UmSCnoCgXqBp2fU3vwz4o1 Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
9f4991abe0
|
feat(avatar): draw every bot as a living Oo on web (#136)
Bots now render as an Oo rig built once in core: nine species, styled Oo eyes, eyewear, headwear, neckwear, arms and moment props, with a level of detail per size. A shared animation frame loop moves only visible avatars and holds still under reduced motion. Claude-Session: https://claude.ai/code/session_01EKuME5CgL1ddGGN7LGvtBj Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
dbf37f86a8
|
feat: show routines the bot sets up from chat as one quiet line (#133)
* feat: show routines the bot sets up from chat as one quiet line
When someone mentions something recurring in passing ("every Sunday I plan the
week"), the bot now creates the routine right away and the conversation shows a
single line with the schedule, Edit and Undo, on web and mobile.
- New `event` message block for routine lines, kept in history so later turns
know what was set up or removed.
- schedule_create reuses an active repeating routine of the same name instead
of adding a duplicate, caps a bot at 50 active schedules, and falls back to
default times when none was given.
- Removing a routine (Undo, the editor, or schedule_cancel) marks its lines
removed via the new routines.noticeMessageId column.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UmSCnoCgXqBp2fU3vwz4o1
* test: give the routine notice test a complete job publisher
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UmSCnoCgXqBp2fU3vwz4o1
---------
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
d6dd2bef5e
|
feat(avatar): add the oo1 avatar value for customizable Oo looks (#135)
A bot's look is stored in its `color` string, which today only holds a colour, an optional classic shape index or an uploaded picture. The customizable avatars (species, eye styling, accessories, temperament) need a richer value that still travels everywhere `color` already goes: bot rows, group members, mentions and the inbox. - `@rakazo/core` gains `oo-avatar`: an `oo1:` key=value codec that writes only non-default fields, reads every legacy value unchanged, rejects malformed values, and exposes `ooAvatarColor` for plain CSS consumers. - The rig's eye anchors keep the Oo trademark (big eye = small eye × φ), and lenses scale with their eye so glasses stay asymmetric; the shared asymmetry ratio now comes from core. - The API rejects malformed `oo1:` values on bot create and update. - `parseBotAvatar` reads through the codec, so an `oo1:` value already renders with its colour and shape before the new renderer lands. - The agent cursor and the mobile avatar used the raw value as a CSS colour and broke on `#hex::shape_N`; they now use the decoded colour. Claude-Session: https://claude.ai/code/session_01EKuME5CgL1ddGGN7LGvtBj Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
e928600c98
|
fix(evo-deploy): prefer systemd over compose in auto restart mode (#130)
In auto restart mode, prefer an active evoo-local user unit over compose so a missing POSTGRES_PASSWORD does not roll the deploy back. |
||
|
|
dda03bff9e
|
feat: self-hosted media — streaming, ComfyUI generation, video cutting, transcription (#119)
* 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 * feat(media): add media contracts, schema and provider interfaces Groundwork for image and video generation, video editing and transcription. Nothing calls it yet; the media runner, providers, tools and UI build on it. - Contracts: video, audio and subtitle MIME types, per-kind size caps for streamed and generated artifacts, video/audio message blocks and the live media card block, media operations, job statuses and output presets (9:16, 16:9, 1:1, 4:5, 4:3, 21:9). - Core: attachment kinds include video and audio, artifacts become video or audio blocks, and agent history keeps media artifact ids so later turns can refer to them. - Schema: artifacts record width, height, duration and the artifact they were made from; media_jobs tracks generations, edits and transcriptions. - Adapter kit: provider-neutral image generation, video generation and transcription interfaces, the media job context operations run in, and the MediaRuntime that tools use to start jobs. 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 * 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 * feat(memory): remember appends dated notes instead of replacing memory The remember tool rewrote the bot's whole MEMORY.md with each call, so every new fact erased the earlier ones unless the model re-sent the full document. - MemoryCommitRequest gains mode "append" (the store joins the note onto the current content inside the same serializable transaction and still writes a revision row) and every commit is capped at 16 KB per document. - remember appends a dated bullet ("- YYYY-MM-DD: ...", the user's local date) by default and rewrites only with mode "replace"; a full document returns an error that tells the bot to condense it. Retries of the same tool call stay single appends through the existing effect record. - Scripted runtime memory entries can append too; the scripted "remember" script now appends instead of overwriting the bot's memory. - When memory does not fit the run's 32 KB budget, the prompt keeps a document's newest lines instead of its oldest. - memory.update maps an oversized save to BAD_REQUEST. - docs/PRODUCT.md records that UserFact is the cross-space user model and the space MEMORY.md stays space notes (no migration). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(coach): goals, habits, check-ins, daily briefs and check-in rhythm The main agent should coach the user, but nothing stored what the user wants to achieve or keep doing, and no routine ever checked in. This adds the data model and the interfaces the onboarding intake and the coach tools build on. - Schema: UserGoal, Habit, CheckIn (one per habit, goal or day and date) and DailyBrief, all keyed to the user's local calendar date, plus a coachKey on Routine that marks the main bot's coach check-ins. - Contracts (coach.ts): CoachRhythm, Goal, Habit, CheckIn, DailyBrief and CoachToday DTOs and the RPC inputs. - Repository (@rakazo/db coach.ts): goal, habit and check-in CRUD with per-user limits, upsertDailyBrief and loadCoachToday with streaks in the user's time zone. - coach-rhythm.ts: setCoachRhythm/getCoachRhythm keep up to three main bot routines (morning brief, evening check-in, weekly review) in the main conversation in step with the user's chosen times. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(notifications): add push subscriptions, notification settings and web push interfaces Push could only reach one mobile device per user (a token file), quiet hours did not exist, and the web app had no service worker, so a closed tab never heard about finished work. This lands the shared interfaces first so onboarding can build on them: - push_subscriptions (web push endpoints and Expo tokens, one row per device) and user_notification_settings (quiet hours as minutes in the profile time zone, nudges, a per-day nudge counter), with CHECK constraints. - NotificationSettingsSchema and web push contracts, notifications.settings.get/update and notifications.webPush.config/subscribe/unsubscribe (owner-scoped, https endpoints only). - getNotificationSettings/updateNotificationSettings plus the policy and nudge-cap helpers. - VAPID keys from VAPID_PUBLIC_KEY/VAPID_PRIVATE_KEY or generated once and stored encrypted, and an SSRF-guarded web push sender built on the web-push package. - The web app links its manifest, registers a push-only service worker where supported (secure context, not the desktop app) and exposes webPushSupported/webPushEnabled/enableWebPush/ disableWebPush. 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(coach): repository, streak and rhythm suites Covers what the coach interfaces promise before anything builds on them: goal and habit ownership and limits, one check-in per habit, goal and day dated in the user's time zone, streaks across weekends and weeks, and a rhythm that keeps exactly one main-bot routine per check-in and is idempotent when set again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * refactor(contracts): share one clock-time schema The coach rhythm and the quiet hours both parse "HH:MM" wall-clock times and each defined its own ClockTimeSchema, which the contracts index cannot export twice. Both now import it from one module. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(onboarding): ask the onboarding questions in the main conversation The wizard asked for the account name again and a "Tell us about yourself" form, and the focus answer in the main conversation was never saved. The main bot now gets to know the user in the main conversation instead, one card after each answer. - UserOnboarding keeps the pending step, the focus and when onboarding finished. - onboarding-intake.ts declares the steps (focus, goal, rhythm, quiet hours, notifications) as cards plus what their answers change; onboarding.ts commits an answer, the focus app suggestions and the next card or the closing line in one transaction. - The choice block gains a step, an optional text input and the typed answer; choose takes the step, the text and whether the device can still turn push on. - The focus answer becomes a goals fact; the goal, rhythm and quiet-hours effects and the push seam are placeholders until the coach and notification modules are merged. - The wizard drops the preferences step and fills time zone, locale and language from the browser; the main bot's prompt notes open onboarding cards; agent history shows a card's question and answer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(media): transcribe speech on any OpenAI-compatible server and write subtitles Transcription has to run on the user's own hardware, and the only speech-to-text path so far was the OpenAI voice key. Self-hosted whisper servers (whisper.cpp, faster-whisper based servers, vLLM) all speak the OpenAI transcriptions protocol, so one provider covers them. - createOpenAiCompatibleTranscriptionProvider posts a multipart upload to {base}/audio/transcriptions asking for timed segments, falls back to plain JSON for servers that reject verbose_json, reads text/plain bodies, and reports failures in words. It reuses the address-checked fetch and URL guards of the other OpenAI-compatible adapters, caps uploads at 25 MiB and responses at 8 MiB, and builds the multipart body by hand because the undici fetch behind the address checks does not serialize the global FormData class. - An in-process server emulator (OpenAI/whisper.cpp, plain-JSON-only and text-only flavours) and an in-process provider back an offline conformance suite, plus one loopback test through the real fetch. - toSrt/toVtt in core turn segments into SubRip and WebVTT, wrapping long segments into cues of at most two 42-character lines that share the segment's time, and escaping WebVTT cue text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(notifications): fan push out to every device, honour quiet hours, add notify_user Run notifications reached at most one phone and nothing could hold them back at night. - PushNotificationProvider sends each notification to all of the user's registered devices (web push and Expo), skips everything during quiet hours in the profile time zone, drops devices the push service reports gone (404/410, DeviceNotRegistered), records lastUsedAt and links each push to its conversation. - Expo tokens move from DATA_DIR/push-tokens files into push_subscriptions: the API imports and deletes the legacy files at start; notifications.registerPush/unregisterPush keep their contract for the mobile app (tokens must look like Expo tokens). - NotificationMessage gains the nudge kind, and the main bot gets notify_user: a short push to the main conversation that respects the nudge switch, quiet hours and a cap of three per day. It is withheld from shared messaging channels and from runs answering another owner's agent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(coach): main-bot coach tools and goals in its prompt The main agent had no way to record what the user wants to achieve or how their day went, and its prompt never showed their goals or habits. - update_goal, update_habit, log_check_in and write_daily_brief, main-bot only and, like update_user_profile, withheld from shared channels and from runs that answer another owner's agent. Goals and habits can be named by id or exact title. - The main bot's own runs list active goals and habits (with ids, done state and streak) in the <user> block; the basics-only block and every other bot never see them. The main agent role adds a short coaching stance and names each coach tool only when the run has it, so the check-in routine prompts stay tool-agnostic. - The scripted runtime understands a few coach commands and the morning check-in, so tests can drive the tools end to end. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(media): add the Studio specialist the main bot hands media work to Media work (cutting for a format, subtitles, exports) needs editing judgement and a focused tool set, so the main bot should be able to delegate it to a visible specialist instead of doing it itself. - ensureStudioBot creates "Studio" once per user, next to the main bot so it can join the main conversation and read its media, tagged media, with editor instructions (hook first, tight pacing, 9:16 safe areas, subtitles for muted playback) and a restricted builtin allowlist: every media operation plus files, questions, memory and web lookup. It has a thread, so the main bot's specialist directory lists it. The spawn key carries the user id so members of a shared space each get their own. - A Studio the user archived or deleted stays gone. Deleted bots now record their spawn key in bot_deletions so idempotent creation can tell. - Postgres suite for concurrency, shared spaces, archive and delete. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * fix(web): use English Lingui sources for German UI text Several web surfaces hard-coded German (the project card, roster and activity status labels, the sidebar "Heute" label) or used German text as the Lingui source message (sidebar sections, project timeline, bot picker, voice entry, group stage, copy and read-aloud buttons). English users saw German, and because production builds keep only message ids, none of these strings could be translated per locale. Convert them to English source messages, keeping the meaning: - ProjectCreatedCard: Lingui Trans/Plural instead of literals. - agent-activity: labels go through i18n._ with literal ids so the module stays importable from plain unit tests; the extractor still collects them. - Shell, ProjectSessionTimeline, bot-picker, StageVoiceEntry, OoStage: English sources, reusing existing messages where one already fit ("just now", "{n}m ago", "Search", "No bots match", "Set up voice", "Group conversation", "Transcript"). - The conversation picker matches its create entry against the translated label instead of a fixed German string. The German wording returns through the de catalog in the next commit. Unit and E2E tests now assert the English labels. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(web): keep Lingui catalogs complete and in sync The committed catalogs were last extracted weeks ago: about 330 source messages were missing from every locale, and production builds keep only message ids, so those strings rendered as hash ids (sidebar sections, privacy settings, profile, timeline, ...). German also had 226 empty entries, and every extraction rewrote the files because references carried line numbers. - lingui.config.ts: po references without line numbers, and a stable sort that breaks Lingui's ordering ties (explicit ids, equal text) by id, so the output no longer depends on file-system order. - Re-extract all catalogs with --clean (drops 25-44 obsolete entries per locale; no existing translation changed). - Translate every German entry (du form, consistent with the existing catalog). Other locales keep empty entries and fall back to English. - i18n-catalog-sync.test.ts extracts into a copy of the catalogs and fails when the committed files differ or a German entry is empty, so stale catalogs fail the unit test job (offline, about 5 s). Adds @lingui/format-po (already installed through @lingui/cli) as a direct dev dependency to configure the formatter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(media): add the ComfyUI provider, default workflows and emulator Image and video generation run on the user's own GPU server. ComfyUI is the common self-hosted backend, so this adds a provider for its HTTP API that the generation operations can call. - Core: API-format workflow templates with a small placeholder format ({{prompt}}, {{width}}, {{seed}}, ...) that is validated and filled without evaluating anything. Defaults: text-to-image and image-to-image on core nodes for any SD-family checkpoint, and text/image-to-video on ComfyUI's core LTX-Video nodes; a README lists the placeholders and model files. - Adapters: createComfyUiProvider submits a workflow, polls its history, uploads input images and downloads outputs through the address-checked fetch used for other self-hosted model servers, with timeouts, capped reads and readable errors. A custom workflow from settings replaces the default it matches. Cancelling dequeues or interrupts only its own prompt. GPU status counts memory ComfyUI can free for itself as available. - Contracts: input schemas for generate_image, generate_video and edit_image. - Tests: an in-process ComfyUI emulator, a provider-neutral generation conformance suite and provider tests, all offline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * refactor(memory): derive the run's memory budget from the document limit The prompt budget was a separate 32 KB constant; it is now twice the per-document limit, so a full bot document and a full space document keep fitting if either number changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(profile): edit facts in place in About you About you could only confirm or delete what the bots know about the user, so a fact that was slightly wrong or filed under the wrong category had to be deleted and taught again, and a fact could not be limited to some spaces from the app even though the profile supports it. - Each fact gets an Edit button that turns its row into an inline editor: wording, category, and (when the user has more than one space, or the fact is already limited) which spaces it shows in. Escape or Cancel leaves it unchanged; only changed fields are sent through profile.update editFacts. - A limited fact names its spaces next to its source. - The repository validates edits like new facts: a wording whose normalized form is empty or equals another fact of the user is refused, all edits of one update apply atomically, and the stored embedding is only dropped when the wording really changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(inbox): list and answer items from all of the user's spaces The inbox in the main conversation only showed asks, approvals, takeovers and unread threads of the current space. The main conversation lives in the home space, so anything a bot needed in a project space never reached it. - inbox.list now covers every space the user belongs to (except spaces being deleted), still only the user's own bots, runs and threads. Items from another space carry that space's id and name. - The web inbox shows the space name next to the bot for those items, answers them with that space's header so the existing thread procedures check membership and ownership for that space, and opens their thread in it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(coach): coach RPCs for goals, habits, check-ins, today and rhythm The web app needs to show and change the user's coach data, and the check-in times need a way to change after onboarding. - coach.today, coach.goals.*, coach.habits.*, coach.checkIns.* and coach.rhythm.get/set, owner-only and not space-scoped; another user's ids read as not found, invalid input as a bad request. - coach-rhythm reuses the shared clock-time helpers. - Tests: an API suite covering ownership and validation for every procedure, the coach tools driven through the main bot (and refused for a specialist), and a morning check-in routine that fires in the main conversation and writes today's brief; the unauthenticated authorization sweep lists the new procedures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(onboarding): save goal, check-in rhythm, quiet hours and push from the cards With the coach and notification modules merged, the onboarding answers now take effect: - the goal card creates a goal (source onboarding) through createGoal; - the rhythm card sets the main bot's check-in routines through setCoachRhythm, and the closing line names the first check-in from the routines that were saved; - the quiet-hours card updates the notification settings; - the choice card tells the server whether this browser can still turn push on, so the notifications card is offered only there, and "Turn on" subscribes before answering. The thread lock becomes NO KEY UPDATE so a rhythm routine, which references the thread, can be written while the answer holds the lock. The goal answer is capped at the coach title length. A Postgres suite walks the questions end to end: answers, skips, a dismissed card, a stale and a doubled answer, another bot's focus card, and another user's access. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(media): stream artifacts over HTTP with Range support Videos and audio can be hundreds of megabytes. Artifacts only travelled as base64 inside RPC calls, capped at 10 MiB and held whole in memory, so media could neither be uploaded, played with seeking nor produced by media jobs. - ArtifactStore gains stat, createReadStream (inclusive byte ranges), putStream (size cap enforced while writing, real SHA-256) and an optional localPath so ffmpeg can read stored files in place. LocalArtifactStore implements them with the same safety as before (O_EXCL, O_NOFOLLOW, 0600) and now validates space ids and keys before building paths. - storeArtifactStream / storeArtifactFromFile store a stream or a job's output file as an artifact row, with width, height, duration and parent, and remove the blob again if the row cannot be written. - GET /api/artifacts/:id serves an artifact with Range/206/416, ETag, inline disposition for media and attachment otherwise (safe RFC 6266 filenames), nosniff and same-origin CORP. Authorization matches artifacts.get: the owner's artifact of an active bot thread or group, with membership of the artifact's space (media elements cannot send the space header; an explicit header must name the same space). - POST /api/artifacts takes a raw streamed body with the target and name in the query, authorized like artifacts.create, with the MIME allowlist and the per-kind caps checked from Content-Length and again while writing. - The API CSP allows media from the app origin and blob URLs. - Web: artifactUrl(artifactId) and uploadArtifactFile for the UI. Tests cover the streaming store, range parsing, filename sanitizing, the file helper, and a Postgres suite for both routes: ranges, caching, other users, shared-space members, other spaces, archived threads, signed-out requests and upload limits including chunked bodies. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(media): media job runtime, media queue and media settings Media operations (generation, edits, transcription) need a runner that works outside the agent turn, keeps a card in the conversation current and never lets two processes put work on one GPU. Operations land in later commits; this is the runtime they register with. - Background job `media.run { jobId }` on a named `media` queue. Jobs carry an optional queue: the Graphile publisher passes it as queueName and the in-memory driver runs each queue one job at a time, in order. - `createMediaRuntime` implements MediaRuntime (capabilities, start, get, cancel) over a list of `MediaOperationRegistration`s and runs jobs: start validates the input, creates the job and posts the bot's media card in one transaction (idempotent per tool call through an operation key), then enqueues it. The runner claims a fenced lease (an advisory lock keeps one live lease at a time, so the GPU stays single even when the queue is bypassed), renews it while polling for cancellation, writes progress and outputs into the card, defers with backoff when the GPU is short of memory or the operation asks to wait, bounds deferrals, crash restarts and run time, always cleans the job's temp directory and always ends the card in a terminal state. With continue_after the starting bot is woken in the same transaction that ends the job (new run trigger `media`). - The reconciler re-enqueues jobs whose enqueue was lost, whose runner died or whose wait is over; a job stuck behind a dead worker's queue lock runs outside the queue. - Media backends are deployment-wide settings stored encrypted as the `media` IntegrationProviderConfig row: `loadMediaSettings` and `saveMediaSettings` with the same address policy as OpenAI-compatible servers (no metadata hosts, public hosts only when allowed, HTTPS when a key is sent), a saved key never follows the settings to another server, and the view never returns keys. Contract in contracts/media-settings.ts. - `createMediaOperations` is the one list both the API and the worker build their runtime from (empty until the operations land), and media-tools.ts has the shared `continue_after` schema helper for the tool definitions. - Migration: lease, deferral, operation key and continueAfter columns. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * test(notifications): cover quiet hours, delivery, nudges and the notification RPCs The quiet-hours CHECK accepted a row with only one bound set, because a NULL comparison made the whole condition NULL; it now requires both bounds explicitly. The migration has not shipped yet, so it is corrected in place. - Unit: quiet-hours math across midnight, time zones and daylight saving; VAPID key handling (configured, generated once and shared, invalid); the web push sender (encrypted VAPID-signed request, gone on 404/410, no private or non-https endpoints); the fan-out with fake senders. - PostgreSQL: settings and subscription repositories (constraints, per-user removal, endpoint hand-over, device cap, concurrent nudge cap, cascade on account deletion), delivery and notify_user against a real main conversation, and every notification RPC through the API including cross-user isolation and endpoint validation. - .env.example documents the optional VAPID keys and VAPID_SUBJECT. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(web): notification settings, and sign-out stops this browser's push Users had no way to turn on push for a browser, set quiet hours or opt out of nudges. Settings gains a compact Notifications section: a switch for push on this device (only where web push works: a secure context outside the desktop app), quiet hours with from/to times (22:00-07:00 when first turned on) and a switch for nudges. Changes save in order as they are made. A browser that blocked notifications says so instead of offering a dead switch. Logging out unregisters the browser first, so a shared computer stops receiving the account's pushes. The E2E stubs the Push API (headless Chromium has no push service), walks all three controls, checks they survive a reload and captures the notification-settings screenshot. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(media): transcription input, chunked transcripts and the Studio trigger Groundwork for the transcribe operation and for creating Studio when it is first needed. - Contracts: TranscribeInputSchema (artifact id, optional language code). - transcribeChunks uploads audio chunks in order and stitches them into one transcript on the original timeline; transcriptionChunkSeconds sizes 16 kHz mono WAV chunks below the server's upload limit; transcriptSummary hands the bot the transcript, cut at a word. - ensureStudioWhenMediaConfigured creates Studio once a media server (generation or transcription) is configured. Local edits alone do not count, because ffmpeg and image tools are present on most installs and a specialist nobody asked for would appear for everyone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(web): show media jobs as a live card in the conversation Media jobs report progress and results through the media block, which the web app did not render. - MediaCard renders the block: operation icon, title, status (reusing the task card's labels, plus "Waiting for GPU" for jobs queued behind the GPU), a progress bar while active, the error when failed, and outputs: images as thumbnails with the existing lightbox, video and audio in the media player, subtitle and text files as file cards. Finished video and audio get a download link, "Export" with presets labelled by aspect ratio (the video's own shape left out) and "Transcribe", offered only when the space can run them; running jobs get "Cancel". - MessageView renders it with one branch. - An E2E seeds the card on a thread snapshot with real uploaded artifacts. - artifact-url.ts and MediaPlayer.tsx are minimal stand-ins for the pipeline workstream's files and give way to them on merge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(media): edit and export video with ffmpeg Bots can now cut, reframe and export videos for every common format without a hosted service: ffmpeg and ffprobe from PATH do the work on the server. - ffmpeg wrapper: PATH detection, ffprobe JSON probing (rotation, pixel aspect, frame rate, audio, packet scan when the container has no duration), runs with -progress parsing, abort and stall kills, an output size cap, partial-output cleanup and short errors without temp paths. Inputs go through the file protocol with the demuxer forced from the MIME type, so a crafted upload cannot be probed as a playlist that reads other files or URLs. extractAudio (16 kHz mono WAV) and renderPoster for transcription and media cards. - A pure planner turns edit steps (trim, keep, concat, reframe, speed, volume, mute, subtitles, extract_audio, poster) into one filter_complex pass: leading cuts seek the input, appended clips are normalized to the frame, fps and 48 kHz stereo (silence where a clip has none), reframes crop, pad or blur to the presets, speed chains atempo, subtitles burn in from generated file names with a readable style that stays clear of the controls on 9:16 frames. H.264/AAC MP4 with yuv420p and faststart; location metadata is stripped. - edit_video and export_video operations and tool definitions; the inputs are shared contracts so the media card can start exports. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(media): stream job inputs and outputs, cover the job lifecycle on Postgres Media files can be hundreds of megabytes, so the runner no longer buffers them: inputs are cloned from the local artifact file when the store has one and streamed otherwise, outputs go through storeArtifactFromFile (per-kind size cap and MIME check while streaming; an oversized result fails the job with a short card error). A Postgres suite runs the runtime with fake operations: success with outputs, posters and parent versions; input, availability, artifact and thread checks before a job exists; tool-call replay; failure with temp-dir cleanup; outputs outside the temp dir and sources of another user; deferral with backoff and its budget; the GPU memory guard; cancel of a queued job and of a job running in another process; one job per lease and one job on the GPU at a time; crash recovery and its restart limit; reconciliation inside and outside the queue; the continue_after wake; a cleared conversation; the run time limit; and capabilities. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * fix(profile): keep the fact editor's Save label visible Small buttons in ui-web merge their text-ui-label size over the default variant's text color, so the small Save button rendered ink on ink. The editor now uses default-size controls, with the category select at the same height. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(media): generate images and video on ComfyUI and edit images Bots can now make images and short clips on the user's own GPU server and rework images locally, through the media job runtime. - generate_image: prompt, aspect preset or exact size, up to 4 images, optional source image for image-to-image. Sizes target what the default models are tuned for, and sources are fitted to that size before upload, so large photos cannot exhaust GPU memory. - generate_video: prompt, aspect, duration up to 10 s, optional start frame. - edit_image: crop, rotate, flip, resize to a size or format (crop, pad or blurred background), extend, convert, in that order, with sharp; an optional prompt repaints the result on ComfyUI. Edits keep the source as parent. - Registrations declare the input artifacts so jobs refuse ones the user cannot read, and generation declares a fixed GPU memory floor (6 GB images, 12 GB video) that a custom workflow cannot lower. - Tool definitions reuse the operation input schemas plus continue_after. - Saving media settings rejects custom workflows the provider could not fill. - Tests: sharp edits on generated fixtures, handlers against the ComfyUI emulator, and a PostgreSQL suite running the operations through the media runtime (card outputs, artifact versions, foreign artifacts, GPU wait). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * refactor(media): share the ComfyUI JSON request helpers Queue, interrupt and prompt requests now go through one JSON helper, so the cancel path reads like the rest of the client. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(media): register video operations with the media runtime edit_video and export_video now run as media jobs: both registrations join the shared operation list the API and the worker build their runtime from, name every artifact they read (source, appended clips, subtitle files) so starting a job refuses artifacts the user cannot read, and their tool schemas use the runtime's continue_after helper. Container deployments get ffmpeg, ffprobe and the subtitle font in the API/worker image. Tests cover the handlers against a fake job context: cuts, blurred 9:16 reframes with subtitles, appended silent clips, audio extraction, single frames, batch exports with posters and progress, rotated phone video, clear input errors and cancellation. They skip where ffmpeg is not installed; the planner and parser tests always run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * fix(media): describe media tools by what the model can rely on The image server is the deployment's, not necessarily the user's, and repainting an edit only works when generation is available. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(media): media tools, media RPCs and owner-only media settings API Bots and users need to start and follow media jobs, and the deployment owner needs to point the deployment at their GPU and transcription servers. - Media tools are named like their operations. A run sees one only when the space can run that operation (MediaRuntime.capabilities); the per-bot allowlist still applies. applyTool hands every media tool to one dispatcher (media-tools.ts) that splits off continue_after, starts the job under the tool call's effect key (a replay returns the same job) and returns at once; refusals (invalid input, unknown artifact, unavailable) go back to the model as tool errors. - media.jobs.start starts an export, transcription or image edit from a card: the conversation is resolved like every thread procedure, the card's bot (or the group's lead) posts the new card, and the runtime refuses source artifacts outside the actor's space. media.jobs.get and media.jobs.cancel only see the actor's own jobs. - media.settings.get/update are deployment-owner only, never return keys and answer invalid URLs with a 400 that says what is wrong. - createApp accepts media operations as a test seam. - Tests: a Postgres suite over the RPCs (owner-only settings, encryption, key retention per server, URL policy, start/get/cancel isolation between users, card jobs end to end) and unit tests for tool exposure and dispatch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(media): transcribe audio and video into subtitles as a media job With the media runtime in place, transcription becomes an operation like the others: bots call the transcribe tool, the job runs in the background and the card shows the subtitle files. - createTranscriptionOperations registers transcribe: available when a transcription server is configured, refuses artifacts the user cannot read before the job exists, and writes <name>.srt and <name>.vtt next to the source (a .txt when the server returns no timings). With ffmpeg the audio is converted to 16 kHz mono WAV and long media is transcribed in upload-sized ranges on one timeline; without it audio is sent as stored and video is refused with a clear message. A server that is down or busy (unreachable, 429, 502, 503) defers the job instead of failing it, since nothing has been stored yet. The bot gets the transcript, cut at a word. - The transcribe tool definition uses the shared media schema helper and continue_after; dispatch stays generic. - The main bot's run creates Studio once a media server is configured, right before the specialist directory is built. - Dictation: when the user's voice provider cannot transcribe (or there is none), /api/voice/transcribe uses the self-hosted transcription server, and the voice status reports server dictation so the web app uses it instead of the browser's speech recognition. A voice provider that transcribes keeps being used as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * refactor(notifications): keep the push sender contract next to the fan-out The payload, target and outcome types are shared by the Expo and web push senders, so they now live with PushSender in push-notifications.ts instead of in the web push adapter. The settings E2E also stubs the Notification permission (headless Chromium reports it as denied even when granted), and the quiet-hours inputs are wide enough for 12-hour times. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(web): play video and audio inline and stream uploads and downloads Media sent in a conversation had nowhere to go: the composer capped every file at 10 MiB and pushed it through a base64 RPC, video and audio blocks did not render, and downloads decoded whole files in the page. - Video and audio blocks play inline through MediaPlayer, which loads nothing until the player is near the viewport, sizes the frame from the block's dimensions (vertical clips stay within the column) and uses the poster when one exists. Files the browser cannot play fall back to their name with a download action. - Images load straight from the artifact route; the lightbox gains a download action. - File cards, the Markdown preview and downloads read from the artifact route instead of decoding base64. - The composer accepts each kind up to its cap and names the computed limit when it skips a file; video, audio and anything above the inline cap upload as a raw stream, small files keep the inline RPC. - MediaPlayer and artifactUrl are what the media card builds on. Tests: MediaPlayer laziness, sizing and fallback, the upload helper, and an E2E that uploads a tiny MP4 fixture (VP9, since Playwright's Chromium has no H.264 decoder), plays it from the artifact route and captures media-video-block, plus the image lightbox download. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * test(media): run transcribe through the media runtime on Postgres Covers what the unit tests fake: the job starts from validated input with the "Transcript" card, runs against the transcription emulator, stores the .srt and .vtt as artifacts next to the source and lists them on the card, and an artifact of another user is refused before a job exists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(media): disclose media servers in AI data sharing Media tools and card actions send prompts, images, video and audio to the deployment owner's ComfyUI and transcription servers. Like the embeddings server behind built-in memory, those self-hosted endpoints are recipients in the AI data-sharing disclosure: they appear with the model uses ("ComfyUI (<origin>)", "Transcription (<origin>)", keyed by the full endpoint so a new server asks again), and starting a card job asks for model consent on clients that run the foreground check. Also adds unit tests for the media settings store: URL normalization and refusals, encryption, the key-free view, one decryption per saved version, key retention per server and removing empty settings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * refactor(media): trim unused ffmpeg wrapper options Binary paths, the ffmpeg version, the probe's container name and a configurable stall timeout had no caller; detection is cached per process, so every helper looks the binaries up itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * fix(media): hand the transcript to the bot as quoted speech 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 * feat(web): media settings section for the deployment owner (wip) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(media): give agents attached media and larger bot files 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 * fix(mobile): show video, audio and media cards as plain rows Mobile has no media UI yet, so video, audio and media-card blocks rendered as empty bubbles or fell out of previews. - Thread text and reply previews describe video and audio files and the media card's title and status. - The bubble lists video, audio and a finished card's outputs as tappable rows that open the file like other attachments. - Opening video or audio downloads it from the artifact route to the cache instead of a base64 RPC, so long recordings open without the server holding them in memory. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * fix(web): download media outputs with the forced-download artifact URL The media card's download link uses artifactUrl's download option so a video or audio output is saved rather than opened inline, and the E2E seeds the thread snapshot with fresh response headers so the patched body is read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * fix(api): refuse artifact uploads from untrusted origins 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 * fix(core): load child_process normally in the git checkout reader readGitCheckout loaded node:child_process through require(), which does not exist in the ESM API runtime. Linking a project space to a folder that is a git checkout therefore failed after the space was created. The lazy load is no longer needed: the reader is only reachable through the server-only "@rakazo/core/git-checkout" subpath, never through the package entry the web app bundles. Import spawn at the top instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(onboarding): ask before discovering projects and let the user choose The onboarding scan started on its own, discarded what it found, claimed to read email and calendars, simulated progress for everyone who could not scan, and its project creation always failed (wrong Space fields and a space lookup by title across tenants). Discovery now asks first and creates nothing without a choice: - The scan step appears only for the deployment owner when ONBOARDING_SCAN_HOME is set; everyone else skips it. The simulated scan is gone. - It shows the folder and asks. Only directory listings are read: git projects up to four levels deep, their languages and a few tools (Docker, GitHub Actions, Terraform). Symlinks are never followed and each chosen folder is resolved again before use, so nothing outside the folder is reached. Results stay with the account that scanned. - Found projects are a checklist (none preselected); found languages and tools are facts (preselected). "Add" creates one project space per chosen project with createSpaceForMember and wireProjectWorkspace in the user's own organization, reusing the user's own space already linked to that folder, and saves the chosen facts as onboarding facts under the profile lock and fact limit. - When a connection becomes connected (direct connect, authorization round trip, or catalog reconciliation), "Uses <App>" is remembered as a tools fact from the connector. Revoking keeps it. The E2E harness now points ONBOARDING_SCAN_HOME at a fixture folder and starts without a deployment owner, so owner-only onboarding steps appear only in specs that take the seat through /__e2e/deployment-owner. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(web): start exports, transcriptions and cancels from the media card 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 * fix(media): GPU guard for smaller cards, top-level type imports - 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 * test(media): run video edits and exports through the media runtime 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 * fix(media): abandoned handlers cannot crash the worker, sweep stale temp dirs - 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 * fix(web): offer media card follow-ups on any settled job Failed or cancelled jobs can still carry finished outputs, so the card asks for the space's capabilities whenever a settled job shows video or audio. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(web): coach on the Heute page Heute only listed runs and linked to a hard-coded local Mission Control address, so the coach's work had nowhere to show. - Today's brief at the top, habits with a check-off and their streak, and active goals, each only when there is something to show; then the existing needs-you, doing and done lists. - One icon button opens the check-in times (morning, evening, weekly), loaded when opened and saved on every change. - The page reads coach.today, refreshes it when a run finishes or the tab becomes visible, and checks habits off optimistically; the date header uses the user's time zone from the profile. - Removes the Mission Control link and its constant. - Tests: helper unit tests and a web E2E that writes the brief through the main bot, checks a habit off across a reload and sets check-in times (screenshots heute-coach and heute-rhythm). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * test(onboarding): walk the onboarding questions end to end; mobile reads cards as text - onboarding-intake.spec walks focus, goal (after typing into the composer, which still works), check-ins, quiet hours and the notifications card when the browser offers it, with the onboarding-intake-goal, -rhythm and -done screenshots. - completeOnboarding and the wizard specs drop the preferences step; the scan step is optional (skipped when it asks, otherwise it advances by itself). - The user-profile spec now checks the browser-filled profile and the focus fact; the conversation spec checks the shorter app suggestion and the following goal question. - The hover spec measures a reply that fills its row (a bot row's rail sits at the row's end, so the old long onboarding line was what made it pass) and scopes Send to the composer, since an open goal card has its own. - Mobile shows a choice card as its question and answer instead of an empty bubble. - The focus options drop their unused summary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * test(web): stub the Notification permission without a static-only class Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * fix(media): let bots title image edits in the user's language 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 * test(media): transcribe real clips through ffmpeg 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 * test(web): cover group uploads in the main conversation 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 * feat(media): title transcription cards with what they transcribe 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 * refactor(web): match the media player stand-in to the pipeline's props The pipeline workstream's MediaPlayer takes artifactId, kind, name, poster and size, without a MIME type; the card and the stand-in now use exactly that, so its file replaces the stand-in without other changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * test(web): media settings E2E with the media-settings screenshot The Media settings section (added in the previous web commit) lets the deployment owner point media at their own servers: ComfyUI URL and optional key, with the image model and custom image/video workflows folded under Advanced, and a transcription URL with optional model and key. Saved keys are never shown again; the key field says one is saved and offers to remove it. Members who do not own the deployment do not see the section. The spec fills both servers, captures `media-settings`, saves and checks the exact update the server receives, then removes the saved key and the transcription server; a second spec checks a member has no Media section. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * fix(web): size uploaded videos from their own frame Uploaded videos carry no dimensions, so a vertical clip sat letterboxed in a 16:9 frame. The player now takes the frame size from the file's metadata when the block has none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * fix(media): disclose the transcription server before dictation uses it 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 * fix(web): render media card outputs with the URL-based previews The media pipeline moved image and file previews to the artifact route, so they no longer take a bot or group target. The media card still passed one, which no longer type-checks; only its follow-up actions need the target. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * fix(api): let large uploads finish and abort stalled ones 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 * fix(media): use snake_case keys in every media tool input 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 * fix(media): act on media cards in their own space, not the selected one 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 * test(web): find the sidebar Today button by its English label The shell contrast test added on main looks up the sidebar button by the old German source text, which this branch replaces with "Today". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * style: use top-level type imports in the media changes 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 * chore(web): extract catalogs and translate the media strings to German Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * feat(onboarding): show the onboarding cards and check-in names in the user's language The onboarding cards, the closing line and the coach routine names were English server text for every user. The server keeps the state and an English fallback; the web now words them through Lingui, so there is one translation source. - Choice cards: the web says the question, the option labels and the placeholder from the step and option ids, and keeps the server text for anything it does not know (quiet-hour ranges are already written in the user's locale). Another bot's focus card is recognized by its option ids. Mobile and agent history keep the server text. - The "Got it. … are a good place to start." line is gone: the app cards speak for themselves, and with no cards nothing is said. - The closing line becomes an intake_note block (note "done", nextCheckInAt, English text). The web words it with the first check-in in the user's clock; mobile blockText and blocksToAgentHistoryText read its text through their generic fallback. - Routines carry coachKey. The routine list and editor show a check-in under its label in the UI language until the user renames it; saving an untouched label keeps the stored name. The English names move to contracts (COACH_ROUTINE_NAMES); prompts stay English. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * style: use top-level type imports in the coach changes AGENTS.md asks for top-level `import type` instead of inline `type` specifiers; this applies it to the import statements the coach work added or changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * chore(web): extract catalogs and translate the coach strings to German Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * test(web): look for the bot reply in the transcript only The main conversation's sidebar row now previews the latest reply, so the unscoped text lookup matched the sidebar too and failed in strict mode. 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 * test(onboarding): link the foreign folder while its tenant owns the deployment Since #112 only the deployment owner can link a host folder, so the discovery test creates the other tenant's linked space while that tenant holds the deployment, then hands the deployment to the scanning owner. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * chore(web): extract catalogs and translate the in-app browser strings Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * fix(media): write poster frames as full-range JPEG 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 * test: look up the home main conversation in the scheduling journey 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 * test: look up the home main conversation in the scheduling journey 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 * test: expect the home main conversation's ask in another space's inbox Every project now has a main conversation of its own, so an ask waiting in the home main conversation is not visible from a project and its inbox lists it, answered in the home space. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * fix(coach): move check-ins when the user's time zone changes The coach check-ins are main-bot routines scheduled in the profile time zone, but a later zone change (About you, onboarding, the main bot's update_user_profile tool, or forgetting the whole profile) left them in the old zone until the rhythm was saved again, so they fired at the wrong local time. syncCoachTimeZone, next to the rhythm code, moves the main bot's coach routines that are not in the current profile zone into it, recomputes the next run of armed ones and re-arms their wakeup. Paused check-ins move but stay paused, routines the user made are never touched, users without check-ins get none, and a save that leaves the zone as it is changes nothing. The profile update and forget RPCs and the profile tool call it whenever they write the zone. Quiet hours, the daily nudge cap and coach dates are evaluated in the profile zone when they are used, so they need no migration. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * fix(web): word the main conversation preview in the user's language during onboarding While onboarding wrote the main conversation, its sidebar row, project rows and Heute showed the server preview: nothing for a pending card (a card has no text of its own, so the row fell back to "Ready") and the English fallback of the closing note, even for a German user. Groups now carry `previewBlock` next to `preview`: the latest message when it is an onboarding card or note, validated with the same block schemas the messages use (now named exports). The web words it with the helpers that render the card and the note, so no copy is duplicated: the question while a card waits, the answer once given, and the closing note in the user's language and clock. A dismissed card keeps the plain preview, as the conversation no longer shows it. The field is left out for every other message, and mobile keeps using the plain `preview`. The chosen-answer lookup moves from the card into onboarding-copy so the card and the preview share it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * fix(media): stream large media between the store and local computers 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 * feat(media): live ComfyUI progress over its websocket A video generation runs a single ComfyUI prompt for minutes, and the media card only advanced after each prompt finished, so it sat still until the clip was done. The ComfyUI provider now follows ComfyUI's standard /ws stream while its prompts run and maps step progress to the job's overall fraction across all prompts: never backwards, and short of each prompt's share until its outputs are downloaded. The history poll stays the source of truth for completion and errors; a socket that cannot connect, drops, or stays silent leaves generation exactly as before, and it is closed on finish, failure, cancel and abort. Each generation uses its own client id, so concurrent jobs do not take over each other's messages. The socket goes through a new createOpenAiCompatibleWebSocket next to the guarded fetch: same URL and key checks, same resolved-address lookup, ws/wss derived from http/https, the same Authorization header, a message size cap, and a hard drop when a server never finishes the closing handshake. It uses the undici package's WebSocket, so no dependency was added. A provider given a replaced fetch without a matching socket opens none, so tests and emulators never reach the network. The ComfyUI emulator now serves the websocket and reports progress for running prompts; provider, guard and conformance tests cover ordering, multi-prompt mapping, unavailable and failing sockets, stray messages, closing on cancel, and the address checks against a loopback server. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u * test(web): look for the focus question in the conversation, not the sidebar The main conversation's sidebar row now shows the pending intake question, so a page-wide text match finds it twice (strict mode) or never reaches zero. The specs mean the card in the open conversation, so they look there. 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> |