106 Commits

Author SHA1 Message Date
Claude
2796965256
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
2026-10-03 13:47:44 +00:00
Claude
dab9398c25
Merge memory append, profile editing and the cross-space inbox
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-03 13:43:12 +00:00
Claude
d54d8bff88
Merge the media job runtime into the media integration branch
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-03 13:32:20 +00:00
Claude
1f33b48362
Merge claude/phase3-media (artifact streaming) into the media jobs runtime
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-03 13:32:17 +00:00
Claude
c60c693d13
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
2026-10-03 13:31:53 +00:00
Claude
62e9bc67f1
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
2026-10-03 13:31:39 +00:00
Claude
3db0f0cfbd
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
2026-10-03 13:28:59 +00:00
Claude
1a3829031f
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
2026-10-03 13:27:50 +00:00
Claude
e13dbe5ffa
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
2026-10-03 13:27:39 +00:00
Claude
a094c23b47
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
2026-10-03 13:19:12 +00:00
Claude
2c88d09eea
Merge branch 'claude/phase2-coach' into p2/a1-onboarding
Brings in the coach and notification interfaces the onboarding goal, rhythm, quiet-hours
and notification steps write to.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-03 13:17:20 +00:00
Claude
f4e02eb248
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
2026-10-03 13:17:06 +00:00
Claude
a163447461
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
2026-10-03 13:13:22 +00:00
Claude
4875753d9c
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
2026-10-03 13:10:25 +00:00
Claude
105a3094b1
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
2026-10-03 13:08:58 +00:00
cynarAI
a35e431d1c
feat: main agent with a main conversation, user model and delegation (#101)
* feat(db): main agent, main conversation, user model and delegated task foundation

The product needs one main agent that leads a main conversation, knows the
user across spaces, and hands work to visible specialists. Today none of
that exists in the data model: "Chief" is a client-side naming convention,
the "Hauptthread" is a group the web client creates by name, and nothing
stores what the agents know about the user.

This adds the shared foundation the Phase 1 features build on:

- Bot.kind ("main" | "specialist", one main bot per user) plus capability
  fields (tags, builtin tool allowlist, model tier).
- ChatGroup.kind ("group" | "main", one main conversation per user) and
  ChatGroup.leadBotId.
- UserProfile and UserFact with category, confidence, source, consent
  status and per-space visibility; embeddings are stored as float arrays
  because none of the Postgres images ship pgvector.
- DelegatedTask for work the main bot hands to a specialist.
- SpaceModelTier to map a model tier to a connected model per space.

The migration backfills each user's onboarding bot in the home space as the
main bot and turns an existing "Hauptthread" group containing it into the
main conversation. CHECK and foreign key constraints on existing tables are
added NOT VALID and validated in a separate migration, as elsewhere in this
repo.

New db helpers ensure the main bot and the main conversation (idempotent and
race-safe, repairing a lost lead), add same-space specialists to the main
conversation and announce it with a group.updated event, and load the user
context a run may see. The main conversation is exempt from the two-member
floor and cannot have its members replaced through a group update. The
contracts expose the new fields and the user model and delegated task
schemas.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* feat(adapters): every run knows who it is and who the user is, in the user's time zone

Runs dropped the bot's name, title and description whenever custom
instructions existed, always stated the time in UTC, and knew the user only
through free-form durable memory. Groups with a lead, like the main
conversation, still got the peer-group lines that forbid handing work back.
And every group follow-up run failed during setup: the inbound handoff lookup
selected a `bot` relation that Message does not have, which Prisma rejects.

- Instructions now open with the bot's identity, then its own instructions,
  a main-agent role paragraph for the main bot, the current time in the
  user's time zone with its offset (UTC when unknown), and a `<user>` block
  built from loadUserContext: profile fields and the active facts visible in
  the run's space, escaped, clipped and framed as data rather than
  instructions. Runs that reply in a shared messaging channel get only the
  name, zone, locale, language and style, matching how those runs already
  leave out durable memory.
- run_subagent helpers receive the identity, time and user block through a
  new optional AgentRunRequest.subagentContext instead of nothing.
- Groups with an active lead get lead and specialist lines and matching
  inbound handoff instructions. Members may hand a stage back to the lead and
  the lead may delegate the next stage; the bounce rules still apply between
  members. Ordinary groups keep today's lines.
- The teammate directory is rendered only when the run has message_bot, which
  group runs never have, and tool guidance names a tool only when the run has
  it.
- The auto-review judge, history compaction and the team chat judge read the
  time in the user's zone, and schedule_create defaults to it.
- The inbound handoff sender is read from the handoff block and looked up
  among the user's bots; a Postgres test covers the follow-up run, the prompt
  it receives, and handing the result back to the lead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* feat(memory): learn the user profile from conversations; add built-in semantic memory

Why: the user profile only knew what onboarding wrote, so people had to repeat themselves,
and semantic recall needed a hosted or separately run memory service. The profile now grows
from everyday conversations, and recall can run on the user's own server with a local
embeddings endpoint (for example llama.cpp). Both stay optional and are tested offline.

What:
- adapter-kit: EmbeddingProvider contract (describe, embed) and the profile.consolidate job
  (payload schema and a replace-keyed job factory, one pending job per user).
- adapters: OpenAI-compatible embeddings (POST {baseUrl}/embeddings through the guarded
  OpenAI-compatible fetch, batching, bounded responses, clear errors), a deterministic
  emulator, an in-process /v1/embeddings server emulator, and a shared conformance suite.
- adapters: "builtin" semantic memory stored in Postgres (semantic_memory_items with Float[]
  embeddings, cosine ranking in process, bot/shared/history scopes matching the existing
  providers), registered in the memory provider factory and configured through
  memory.connectProvider with an embeddings URL, model and optional API key. Private
  endpoints require the deployment owner, like the other local memory providers.
- adapters: profile consolidation reads the user's settled app conversations since
  UserProfile.consolidatedAt (never messaging channels), asks the main bot's model for strict
  JSON facts and an updated summary, drops restatements (embedding cosine when built-in
  memory is configured, normalized text otherwise), stores sensitive categories as pending,
  and advances the cursor with a compare-and-set. A reconciler hook enqueues it at most daily
  per active user; it skips under the scripted runtime and without a usable model.
- db: additive migration 20261002210400_ws4_semantic_memory.
- web: a "Built-in" option in Memory settings; AI data sharing names the embeddings server.

Tests: new embedding conformance, OpenAI-compatible wire, built-in provider and consolidation
unit tests; new Postgres suites for built-in memory (including a real loopback embeddings
server through the memory provider resolver) and for consolidation and its scheduling query,
both added to the integration harness. The settings E2E spec now opens the new form.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* feat(models): map model tiers per space and let bots and helpers ask for one

A space can now map the tiers fast, standard and code to connected models,
including several models served by one OpenAI-compatible router endpoint
(one credential, many model ids). A bot can ask for a tier, and a single
run_subagent call can ask for one. A tier with no model mapped falls back
to the usual model choice, so nothing breaks before tiers are set up.

- db: SpaceModelTier repo (list, find, set or clear); tier mappings are
  copied into a new space like model and voice preferences; createBot
  carries a tier, so duplicates keep it and spawned bots inherit it.
- contracts/api: models.tiers.list and models.tiers.set;
  models.probeOpenAiCompatible can list a connected server's models by
  credential id with its stored address and key; bots.update accepts
  modelTier and keeps a pinned model and a tier mutually exclusive, so a
  model picked on a surface that knows no tiers (mobile) is never silently
  overridden. Thinking-level validation follows the tier's model. AI
  consent status discloses tier models; desktop local settings allow the
  tier procedures.
- adapters: selectConfiguredModel runs a mapped tier ahead of the pinned
  model and the space default; the executor resolves bot tiers for runs
  and model lookups, and helper tiers through resolveModelTier.
  run_subagent takes model_tier; an explicit model wins over it.
- web: a collapsed Tiers section in model settings (shown once a model is
  connected) and mapped tiers as choices in the bot Model select.
- docs: tiers in self-host model setup.
- tests: resolution order and fallbacks (selection, executor, Pi helper),
  repo, API and contract units, authorization and offline Pi Postgres
  suites, and a model-tiers web E2E spec.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* fix(adapters): state the consolidation time in the user's zone; name the memory migration after its content

Profile consolidation now passes the profile's time zone to the shared time
instruction, which takes the zone since every run states the time in the
user's zone. The semantic memory migration is renamed after what it creates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* fix(adapters): keep the user's summary and facts out of replies to other owners' agents

An agent connection wakes a bot with another owner's message, and the reply
goes back to that owner. Such runs now get only the basics of the user block
(name, time zone, locale, language, style), like runs in shared messaging
channels, so confirmed facts such as health never reach another person's
agent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* feat(profile): readable, writable and forgettable user model

The main agent is meant to know the user, but nothing could read or change
the user model the foundation added. Onboarding pasted the user's answers
into the first bot's instructions as a "User Context" block, and the user
had no way to see or remove what their bots knew about them.

This makes the user model a user-controlled resource:

- db: a profile repository scoped by user. One atomic update writes the
  account name, profile fields, new facts (deduplicated by normalized
  text, capped per user), fact edits including per-space visibility, and
  confirmations. Health and values facts wait for confirmation unless the
  user wrote them. Forgetting hard-deletes facts by id, category or all,
  clears the summary that may restate them and marks earlier chats as
  consolidated so they cannot teach them again; forgetting everything also
  clears the profile fields. Writes take a per-user advisory lock.
- api/contracts: profile.get, profile.update and profile.forget. Unknown
  fact ids are a 404 on update and ignored on forget; spaces outside the
  user's memberships are rejected.
- adapters: an update_user_profile tool for the main bot only, hidden from
  specialists and shared messaging channels and refused if called anyway.
  Facts carry source "conversation" and the bot, thread and message they
  came from. The scripted runtime records "learn about me: [category:]
  statement" for tests.
- web: onboarding writes the name, time zone (browser default, now
  prefilled and editable), locale, chosen language, communication style
  and the hobby and work-style answers to the profile instead of
  overwriting the bot's instructions, and the harvested memory section no
  longer repeats them. A new "About you" settings section lists profile
  fields and facts by category with their origin, confirms pending facts,
  and forgets one fact or everything.

Tests cover the contracts, the repository (unit and PostgreSQL), tool
selection and refusal, the scripted trigger, the onboarding mapping and
view helpers, an API suite that runs the tool through the executor with a
real main bot, authorization for the new procedures, and an E2E spec.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* feat(delegation): main bot hands work to specialists as tracked tasks

The main agent had no way to give a specialist a piece of work and keep
track of it. message_bot is a fire-and-forget peer message whose work stays
hidden from the user, and handoff_to_bot ends the main bot's turn and only
works between members of one group. Delegation now runs the specialist's
work where the user follows the main agent and brings the result back so
the main agent can close the task.

- delegate_task({bot, title, brief, deadline?}) is offered to the main bot
  only and refused unless the caller leads the main conversation. It
  resolves the specialist by id or name across the user's spaces, creates a
  DelegatedTask and posts a task card in the main conversation.
- A specialist from the main conversation's space joins it and works there
  in a run with the new trigger "delegation", which the peer filters do not
  hide, so its progress, asks and approvals appear in the main conversation
  with the specialist as sender. A specialist from another space works in
  its own chat; its written result is posted into the main conversation as
  its message.
- The executor mirrors the run on its task (running, waiting, done, failed,
  cancelled) after the run starts and when each attempt ends, and updates
  the card in place through thread.message.updated. Done and failed tasks
  wake the main bot in the main conversation with the result. The job
  reconciler settles tasks whose run ended without the executor (stop,
  clear, archive, deletion, crash).
- tasks.list({threadId?, status?}) returns the caller's tasks for the
  current space.
- The task card shows who, what and status with Lingui labels; the brief
  opens on demand. The message_bot card no longer shows the recipient's
  unrelated overall status or hard-coded German copy.
- The scripted runtime understands "delegate to the bot named X: ..." and
  the task wake prompt; activity rows name delegation runs by task title.

Tests: unit tests for the prompts, status mapping, card, reducer, tool
selection and scripted runtime; a Postgres suite for same-space, group,
other-space, ask and answer, failure, stop and replay; tasks.list in the
authorization suite; and an end-to-end spec for the whole flow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* fix(adapters): show the main agent the specialists it can delegate to

The main conversation is a group run, and group runs no longer list the
teammate directory, so the main agent could not see the names of the
specialists delegate_task expects. Runs that have delegate_task now list
the user's specialists across the spaces they belong to, those next to the
conversation first and others marked with their space, and that list
replaces the teammate directory so no bot appears twice. It also says when
to prefer delegate_task over handoff_to_bot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* feat(inbox): show what specialists need from the user in the main conversation

Specialists ask questions, request approvals and the computer screen in
their own threads, and the user only finds out by opening each thread. With
the main conversation as the single entry point, those requests have to
reach the user there, with the specialist as the visible sender.

- inbox.list returns, for the current space and the user's own bots, pending
  asks and approvals (bot messages with a pending ask block whose run waits
  for input), takeover requests, and unread specialist threads. Asks already
  in the main conversation are left out; takeovers stay because their
  controls live in the bot's own thread. Each item carries the bot, the
  thread target, run and message ids, and the ask block for asks and
  approvals.
- The main conversation shows the items above the composer. Asks and
  approvals are answered in place through threads.answer with the source
  thread target; takeovers and unread threads open the bot's thread. The
  list refreshes when the bots' status, unread state or newest message
  changes, every 15 seconds, and after an answer.
- The inbox shrinks and scrolls so it never pushes the composer out of view.
  The transcript now clips its scroller: with no room left in the column,
  the scroller's padding used to spill over whatever sat below it.
- The ask block schema is extracted from MessageBlock so the inbox contract
  reuses it, and AskCard accepts a className.

Tests: unit tests for the item builder and the component, a Postgres suite
for the query (added to the harness list), inbox cases in the authorization
suite, and an E2E spec in which a specialist asks in its own thread and the
user answers from the main conversation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* fix(adapters): write learned facts under the profile's lock and limit

Profile consolidation wrote facts beside the user profile repository: without
its per-user lock, its duplicate check at write time, or its limit of 500
facts. A long-running profile could grow past the limit and then refuse the
user's own additions. Consolidation now takes the same lock, drops facts the
profile gained since the run read it, and keeps only what still fits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* fix(adapters): keep profile writes out of replies to other owners' agents

A run woken over an agent connection hears another owner's bot, not the
user, so update_user_profile is withheld there: nothing such a conversation
says can become a fact about the user. Detection moves ahead of tool
selection; the reduced user block uses the same flag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* feat(capabilities): least-privilege builtin tools per bot and schedules on behalf

Every bot saw every builtin tool, and nothing outside the runtime's own
declaration kept a run to the tools it was given: the executor ran any tool a
runtime asked for, and the Pi runtime exposed all builtins whenever a caller
passed an empty tool list, which the safety checker, the history summarizer
and the team chat judge do on purpose. A specialist could not be kept to the
tools of its lane, bots had no capability tags, and the main bot could only
schedule routines for itself.

- A bot can carry a builtin tool allowlist. Its runs see only the allowlisted
  builtins, and an empty allowlist means none. MAIN_ONLY_BUILTIN_TOOLS (empty
  until the user profile and delegation tools land) never reach other bots.
  applyTool refuses any tool the run did not expose, and an empty tool list
  now means no tools for the Pi runtime and its subagents.
- A bot spawned by a restricted bot inherits the restriction and a duplicate
  keeps it, so neither path widens a bot's tools.
- bots.create and bots.update accept tags, restrictBuiltinTools and
  allowedBuiltinTools; the router rejects names that are not builtin tools.
  bots.builtinTools lists the names a bot's allowlist can use.
- Bot settings get a Tags field and a switch with a tool checklist under
  Advanced.
- The main bot's schedule_create takes an optional bot: one of the user's
  bots in any space the user still belongs to. The routine belongs to that
  bot in its space and wakes in the main conversation when the bot is a
  member there, otherwise in its 1:1 thread. Routines record the bot that
  scheduled them (additive migration), so schedule_list and schedule_cancel
  also cover exactly the routines a bot scheduled for others. Cancelling by
  a name that matches several routines is refused.

Tested with unit tests for the per-bot filter, the applyTool guard, the empty
tool list, and on-behalf resolution, creation, listing and cancelling; with
Postgres journeys for on-behalf routines waking across spaces and in the main
conversation and for allowlist enforcement through the scripted runtime; and
with a web E2E for the settings section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* test(adapters): give the consolidation unit tests the profile lock

The write transaction now takes the per-user profile lock, so the mocked
client answers the lock query, and the first case checks the lock is taken.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* feat(adapters): run profile consolidation on the fast tier when one is mapped

Fact extraction is short structured work, so consolidation asks the model
resolver for the fast tier. The resolver takes a per-call tier and, when the
space has not mapped it, keeps the bot's own tier or model as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* feat: land in the main conversation, led by the main bot

After onboarding the user ended up in a 1:1 chat with "Chief", and the
"Hauptthread" was a group the web client created and found by name. The
main bot had no conversation it actually led, and addressing another bot
there did nothing unless the user had added it as a member first.

Now the main conversation is a real place:

- bots.ensureMain creates or returns the main bot. Bootstrap and space
  navigation return `main` ({ botId, groupId, spaceId }) once the main
  bot exists; reading navigation never creates a bot.
- The main bot leads the main conversation and answers when nobody is
  addressed: message routing, follow-ups, reactions and clearing the
  thread fall back to the group's lead instead of its first member.
- Addressing a same-space bot that is not a member yet, by @Name or a
  mention chip, makes it join before the message is routed, so it
  answers in the conversation. Specialists from other spaces do not join.
- The main bot cannot be archived or deleted, the main conversation
  cannot be archived or deleted or have its members edited, and child
  bots never dissolve it. Renaming the main bot renames the conversation.
- Web: onboarding uses bots.ensureMain and finishes in the main
  conversation. The main conversation is the first sidebar entry in every
  space, shown with the main bot's avatar and name; from another space it
  switches back to the home space. The main bot is not listed again among
  the bots, its 1:1 thread stays reachable by URL. The client no longer
  creates or detects the "Hauptthread" by name, refetches a group when it
  changes, and shows the speaker of any of the user's bots in a group.
- Onboarding no longer resets the step when its bootstrap resolves after
  the user has already moved on, and new spaces open in the app instead
  of walking onboarding again.

Tests: group mention routing with a default responder, lead follow-ups,
child-bot guards, a journey for the main conversation (lead answers,
addressed bot joins, guards, rename), authorization for bots.ensureMain,
and an E2E spec that lands in the main conversation, gets an answer from
the lead, sees an addressed bot join, and reaches the conversation from
another space. Existing E2E specs start in the main bot's thread through
a helper or use the main conversation where that is what they test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

---------

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 06:44:20 +02:00
cynarAI
4c619b25b9
fix(api): keep the onboarding scan off the host unless the owner opts in (#98)
* fix(api): keep the onboarding scan off the host unless the owner opts in

The first-run scan walked the home directory of the API process user for
every account that signed up, with no consent, and its status endpoint
accepted any scan id. Any account could read the repository paths and
remotes of the server owner.

The scan now stays simulated unless the deployment configures
ONBOARDING_SCAN_HOME and the requesting account is the deployment owner;
the previous HOME fallback is gone. Scan records remember who started
them and the status endpoint answers only that account.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* ci: give each runner on a shared host its own E2E ports

Two Linux runners live on the same host. Every Web E2E job first frees
"stale" listeners on the fixed ports 3110, 3111, 5180 and 4321 with
fuser -k, so a job starting on the second runner killed the Vite server
of the job already running on the first one. The victim died with
SIGKILL (exit 137), and its remaining tests failed on reloads that could
no longer reach a server. This is what made the Web E2E run on this PR
red, and it explains the recurring reload failures on main whenever two
runs overlapped.

The workflow now derives API_PORT, WEB_PORT and WWW_PORT from the runner
name (suffix -N means an offset of N times 100), and the port-freeing
script follows those settings instead of a hard-coded list. The harness,
Vite, the API and the marketing site already read these variables.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
(cherry picked from commit 2f05ab6a817e5198e2a740706976108b7b1edde1)

* test(testkit): cover the env-driven E2E ports instead of a fixed list

The ports test parsed a hard-coded array that no longer exists. It now
runs the script on a fake host whose fuser finds nothing, and checks the
default ports, the per-runner override, the rejection of a non-numeric
setting, and that the workflow picks the ports before freeing them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
(cherry picked from commit cd0aedafcdec1b24dea924d8924b3b29af29d598)

* ci: keep every runner off the default E2E ports

Branches that still free the fixed ports 3110, 3111, 5180 and 4321 keep
running on the shared host (main, the nightly run, other open PRs) until
they carry the per-runner ports. A job of ours on the runner without a
numeric suffix used exactly those defaults, so such a neighbour could
still kill it, and its own port freeing could kill the neighbour. The
unsuffixed runner now starts at offset 100, so no job from this workflow
touches the defaults in either direction.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
(cherry picked from commit ddf5febdb3072425442ef375ca0be63fbe09e7f1)

* fix(web): recover lazy routes and overlays from a failed module fetch

When the browser aborts a module request while a page loads, the lazy
import rejects and the route throws, leaving a blank page. Chromium does
this whenever a network interface appears or disappears mid-request
(net::ERR_NETWORK_CHANGED), which happens on a laptop that changes
networks and on the CI host every time a container starts or stops. A
Web E2E run hit exactly this: two modules of the onboarding page failed
to load and the suite waited on an empty document.

Lazy routes and overlays now load through a helper that retries the
import once and, when the browser keeps rejecting it from its module
map, reloads the page a single time before surfacing the error.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
(cherry picked from commit d5dacb12f14380071ef14ba8845d18c9d39e3fac)

* fix(web): reload once when the module graph is aborted while booting

The lazy-route recovery cannot help when the browser aborts a module of
the static import graph: nothing of the app runs, the root stays empty,
and the only trace is a console error. A Web E2E run hit this right
after a page reload, when four modules of the shell failed with
net::ERR_NETWORK_CHANGED and the shell never rendered.

An inline script in the HTML entry now listens for the error event of
the module script and reloads the page once, guarded by a session flag
that the entry module clears after a successful boot. The existing
preload recovery stays in place; it only covers Vite's chunk preloads of
production builds.

A new E2E spec aborts one module request during boot and one lazy route
request, and expects the sign-in page to render in both cases.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
(cherry picked from commit e5b7e036e1a60e7e270592c4b9382ee23ed45ccb)

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
2026-10-02 21:48:46 +02:00
cynarAI
5426bb73c5 fix(e2e): wait for shell-ready after RPC bot create (#87)
* fix(e2e): wait for shell-ready after RPC bot create

createNamedBot now waits for data-ready and the Message composer after
navigating to a freshly created bot, instead of asserting immediately on
a cold page load where bootstrap had not finished.

Also fix onboarding scan advancing to bot creation (onComplete was a no-op)
and use a fast simulated scan when AGENT_RUNTIME=scripted so CI E2E does
not walk the real filesystem during onboarding.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* style: apply biome format to touched onboarding files

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* style: clear biome lint in onboarding scan files touched by the fix

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* style: format onboarding-scan after rebase onto main

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* style: fix biome lint on main files blocking Product CI

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(onboarding): allow scan before first bot exists

The scan RPC required a non-empty botId, but onboarding runs scan before
Chief is created and the UI sent an empty string. That rejected the RPC,
left the flow on /onboarding, and every completeOnboarding helper timed out.

Make botId optional, omit it from the client when unknown, advance to bot
creation when scan completes, and keep scripted/fake runs on the fast scan path.

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>
2026-09-24 03:38:40 +02:00
c18a04ce3f feat(api): sub-agent roles + DB project creation in onboarding scan
- Auto-generate specialist roles from detected interests/skills:
  Code Engineer, UI/UX Designer, Data Analyst, DevOps, Music, Writer
- Always include General Assistant role
- Embed role descriptions + instructions in agent briefing
- Create real Prisma Space entries for discovered git repos
- Deduplicate: skip spaces that already exist
- Pass prisma client through scan pipeline chain

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-24 00:17:29 +00:00
85a0f3860a feat(api): real onboarding scan — filesystem, Git repos, profile compilation
- Replace simulated scan with real filesystem scan of home directory
- Discover Git repositories via .git directory scanning
- Analyze repos: remote URLs, branches, last commits, language stats
- Compile user profile from file types, languages, and repo data
- Generate agent briefing with tech stack, interests, and skills
- Create project folders from discovered Git repositories
- 1500+ line implementation with comprehensive file classification
2026-09-23 23:43:51 +00:00
20afcb0d0b feat: security headers + onboarding scan & profile creation flow
Security:
- Add CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy,
  Permissions-Policy as Hono middleware (after CORS)
- CORS already hardened via isTrustedOrigin (localhost, private, Tailscale)

Onboarding scan flow:
- New 'scan' step between 'integrations' and 'bot' in onboarding
- OnboardingScanProgress.tsx: progress UI with 4 phases (scanning, connecting,
  building, briefing) with real-time polling
- onboarding-scan.ts: backend scan pipeline (in-memory registry, async phases)
- RPC routes: onboarding.scan + onboarding.scanStatus
- Contracts updated with new procedure definitions

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-23 23:31:12 +00:00
cynarAI
63f96b6edd fix(ci): restore the web client and the failing product checks (#83)
* fix(ci): restore browser preview module and onboarding e2e

The web bundle imported a browser-preview store that was never added, so production builds and every Playwright signup failed before the shell rendered. Mobile check failed because Expo packages were a patch behind the SDK.

Restore the store, type the optional desktop bridge, and walk whichever onboarding step scripted CI actually opens.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(ci): stop the blank web client and the send 500

The core barrel pulled git checkout into the browser, so node:child_process crashed the page before sign-up. Serve that module from a node export instead.

Add the missing run tool-call budget columns that threads/send writes. Guard an indexed shell flag for typecheck, and expect the files key the desktop preload already exposes.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(ci): isolate pnpm setup and the takeover membership

The Team Computer takeover journey inserted a space member the
default-space trigger had already created, so shared Postgres rejected
the unique pair. Self-hosted jobs now keep a per-job pnpm home and
store, and artifact uploads no longer fail a job that already passed.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(ci): clear typecheck, lint, and unit failures

Inbound channel rosters can mark a participant list as authoritative.
Answered channel asks stay off the group, and clearing a thread waits
until delegated returns land. Lint, unit, and web e2e contracts follow
the current shell, composer, and settings.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(web): align e2e with the shipped shell and onboarding

Web E2E still expected older focus buttons, nested project rows, and
settings that never opened MCP. Persist the signed-in space, list
non-default spaces under Projekte, and keep the quote control on screen.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(web): open group reply from a revealed hover rail

The first user row covered its Reply control until the rail accepted
pointer events. The group transcript now reveals that rail, then replies.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(web): stop the shell render loop and settle computer and mention UI

A working bot rewrote preview state on every render, the computer menu never stayed still, and the mention picker did not expose its expanded state or option name.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(ci): stop idle probes hanging and accept a follow-up send

Background probes walked every process fd and timed out on the shared runner. Linux now tracks the marker with flock and /proc/locks. A new composer draft can send while an earlier send is still refreshing, and group members stay mentionable.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(adapters): detect background work when flock ids are padded

The idle probe compared /proc/locks with an unpadded device key, so majors
or minors under 16 never matched. It deleted the live marker and reported
idle while the command still held the lock. Probe by taking the lock, and
print the cancel key the way the kernel does.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(adapters): stabilize background-work probe tests on self-hosted CI

Clear stale marker files before each probe scenario and allow longer
polling so launch can acquire flock on busy persistent runners.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(web): do not block onboarding on spaces list

Render the first onboarding step from me and integration setup alone.
Resolve the optional skip-to-app redirect after that so Playwright
signups are not stuck on the loading shell when spaces.list is slow.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(ci): restore green gates after second main rebase

Re-apply biome formatting on Shell/SortableGroup and strict TypeScript
assertions in computer-idle tests dropped when skipping the duplicate
conflict-resolution commit.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(web): wait for onboarding bootstrap before E2E proceeds

Expose data-onboarding-step on the onboarding shell and have Playwright
wait until loading finishes. Ignore ad-hoc screenshot scripts on
self-hosted runners so they do not fail lint.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(web): fall back when onboarding bootstrap stalls

Race me/integration setup against a timeout and promote loading to the
emoji step after 20s so Playwright is not blocked on a hung RPC.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(web): render onboarding immediately for E2E signups

Start on the emoji step instead of a loading shell that waits on RPC.
Bootstrap can still redirect once me and integration setup resolve.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(ci): stabilize web e2e on self-hosted runners

Per-job pnpm stores broke node_modules symlinks when setup-node restored
cached installs, so Vite could not resolve react during dependency
optimization. Keep a runner-scoped store, clear stale Vite caches, and pin
Playwright to one worker in CI to reduce onboarding RPC contention.

Add Vite resolve aliases for react/react-dom, wait for an interactive
onboarding step in E2E helpers, and include the React hook smoke spec from
main.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(web): resolve React aliases through pnpm package layout

Root node_modules/react does not exist under pnpm, so Vite aliases pointed
at missing paths and broke production builds and the Playwright webServer.
Resolve react and react-dom from the web package and map jsx-runtime to the
actual entry files.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(core): type the lazy child_process spawn cache

Main's lazy require left spawn typed as null after assignment, which broke
Typecheck on the rebased branch.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(db): mock git-checkout export path in unit tests

Vitest resolves vi.mock against package exports; the .js suffix is not
declared for @rakazo/core/git-checkout.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(web): stabilize E2E after main sidebar and onboarding changes

Use first() for duplicate transcript lines, wait for interactive onboarding
before model-required assertions, and allow project sidebar to settle after
create. Extend the Playwright job timeout so the single-worker suite can finish.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(web): restore Projekte sidebar and integrations MCP entry

Use the default space for Unterhaltungen/Projekte separation instead of the
active space header, persist the bootstrapped space in localStorage, wire
SettingsOverlay to MCP management, and compare hover tones on inactive rows.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(web): satisfy biome format on homeSpaceId line

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(web): align sidebar E2E with nested Projekte shell layout

Scope Unterhaltungen assertions to conversation rows only, target project-space
Chief actions under .sidebar-projekte, and stabilize hover-tone polling.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix: satisfy lint after main merge (format and unused var)

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(web): show project-space 1:1 bots in Projekte sidebar

Include lone bots in projectSidebarGroups, render bot rows in Projekte,
and stabilize E2E for nested sidebar layout and duplicate group entries.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(web): disambiguate duplicate group sidebar buttons in E2E

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(web): align spaces and group-chats E2E with Projekte sidebar IA

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(web): dedupe Projekte sidebar rows and stabilize E2E specs

- Deduplicate project space and group-chat entries in projectSidebarGroups
- Scope group-chats/spaces E2E clicks to first Projekte match after IA split
- Wait for shell ready before mention-picker keyboard test; restore main locators
- Fix PrivacySettingsOverlay lint and align biome schema with CLI 2.5.13

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(web): restrict project drag handle and stabilize space E2E

- Move SortableGroup drag listeners to ProjectDragGrip only so sidebar clicks work
- Scope space delete actions to project-space rows; wait for menu in E2E
- Switch group settings E2E via direct navigation after unsaved draft check
- Merge main (DataTrustPanel) and fix Shell import order for CI lint

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(web): stabilize message hover Escape assertion in E2E

Assert the More menu closes and the hover rail stays visible instead of
requiring focus restoration, which is flaky across Chromium builds.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* chore: format apps/api/src/app.ts after main merge

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(web): stabilize team-computer E2E send after bot switch

Wait for shell ready and use the Send button instead of Enter after
switching bots and closing the computer overlay. The prior run timed
out on threads/send with only an apps/api format commit in between.

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>
2026-09-23 22:09:49 +02:00
89a932cdd5 feat(api): add /api/data/export endpoint for user data portability
feat(web): add DataTrustPanel to Shell sidebar showing local data status
  - 3 trust indicators: verschlüsselt, lokal gespeichert, kein Tracking
  - Responsive: hidden on small screens, visible on xl+
  - Green checkmark with detail text for each trust point
2026-09-23 17:42:36 +00:00
cynarAI
de2a64f070 fix(web): read sidebar trading mode from the bot status API (#80)
The Live Trading control stored its mode in localStorage, so the shell could
claim live money while the bot was in dry-run. Show the Tradingbot status
when TRADING_BOT_URL is set, and an unknown state when it is not.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
2026-09-22 22:10:18 +02:00
cce793151e fix(ui): remove Integrations button from sidebar, add E2E tests
- Remove 'Integrationen' button from sidebar (Shell.tsx)
- Fix auth heading text: 'Create your Rakazo' → 'Create your EvOo'
- Fix completeOnboarding helper: handle setup progress, dismiss overlays
- Fix vite proxy CORS: use proxyRes handler instead of headers option
- Add local provider fallback in modelSetup
- Add 4 new E2E tests (all passing):
  - sidebar-integrations-removed: Integrations button not in sidebar
  - sidebar-nav: expected nav items present, no Integrations
  - message-send: composer interactive, message appears
  - onboarding-complete: full onboarding flow works
- Add playwright config: PLAYWRIGHT_NO_SERVER support
2026-09-22 15:46:05 +00:00
cynarAI
cf6ac7270e feat: rich project creation with folder and git wiring (#73)
Extend the existing NewSpaceDialog project flow so an optional host
folder becomes the space workspace root. When .git metadata is present,
remote URL, default branch, and head commit are stored on the space.

Team Computer file APIs expose the linked folder under project/ without
a second VCS stack. Desktop adds a native folder picker; web falls back
to an absolute path field.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
2026-09-22 12:29:25 +02:00
cynarAI
c37afbd297 fix(teamchat): retry delegated replies when external origin is missing (#69)
When deliverDelegatedReplies could not resolve the Slack/Teams origin thread,
it still set teamChatMirroredAt and permanently dropped the delegated reply.
Leave the mirror timestamp unset when sourceMessageId exists so reconciliation
retries once the wake chain can be resolved again.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
2026-09-22 12:00:21 +02:00
cynarAI
b4a0dbc74d fix(messaging): preserve group inbound media URLs in fan-out (#66)
Group channel fan-out used event.content only, dropping mediaUrl that
DM handling already merges into message text. Photos with empty captions
in SendBlue groups reached member bots with blank prompts and blocks.

Also skip fan-out when the merged text is empty, matching DM tapback
behavior.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
2026-09-22 12:00:07 +02:00
06943e209f feat(api): start GraphileJobWorkerHost for graphile-based job processing
The GraphileJobWorkerHost was never started when using graphile as the
wakeup driver. Added else-if branch to start the worker host with the
job pool, enabling actual job consumption (previously jobs were queued
but never processed).
2026-09-22 09:45:01 +00:00
cynarAI
04528a93e3 fix(api): restore getMemoryProviderConfig export (#68)
Commit 928d9ad refactored requireSpaceOwner into isSpaceOwner but
accidentally dropped getMemoryProviderConfig, breaking API boot while
router.ts still imported it. Restore the owner-gated read helper.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
2026-09-22 09:04:43 +00:00
Cursor Agent
bede7d0d73 fix(api): block takeover while teaching on sibling bot
When a user records a taught skill on one Team Computer bot, manual
takeover on another bot in the same space cleared the teaching lease
without stopping the session. Teaching input then failed silently and
skills/stop could not release the replacement takeover lease.

Reject computer/takeover while another bot on the same computer is
recording, matching the existing Stop teaching first messaging guard.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
2026-09-22 08:59:42 +00:00
Cursor Agent
ff93b2c481 fix(api): scope Team Computer read/list to member bot homes in shared Spaces
Team mode stores one disk per Space. computer/readFile and computer/files
only verified the caller owns the entry bot; an explicit bots/<otherBotId>/…
path let one member read another member's workspace. Wire existing path guards
into the API router and executor file/shell tools, and add a shared-Space
authorization regression test.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
2026-09-22 08:59:09 +00:00
cynarAI
0a3ce24797 fix(api): pass actor identity when purging memory after thread clear (#62)
threads/clear called memoryProviders.resolve with a bare spaceId string.
The resolver destructures { spaceId, userId }, so both were undefined and
purgeHistory was silently skipped after every clear.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
2026-09-22 10:57:40 +02:00
cynarAI
db39fe7752 fix(api): scope routine remove to acting user in shared Spaces (#61)
* fix(api): scope routine remove to acting user in shared Spaces

routines/remove only filtered by spaceId, so any member of a shared Space
could delete another member's routine by ID. Match update/testRun by also
requiring userId in the lookup.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* test: drop unused ownerActor in routine remove authz case

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>
2026-09-22 10:57:35 +02:00
cynarAI
492e0f0965 fix(messaging): deliver delegated tool-only replies before mirroring (#60)
deliverDelegatedReplies used allowSilence and marked teamChatMirroredAt
even when no Slack/Teams message was sent, leaving tool-only delegated
runs silently dropped with no retry path.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
2026-09-22 10:57:30 +02:00
ec597a5c68 fix(api): scope Team Computer export to exporting bot workspace
- Add teamBotWorkspaceDirectory import to router.ts
- Add exportOptions to scope team exports to one bot workspace
- Add authorization test: member export only sees their workspace files
- Add LocalAgentHomeStore and computerHomeKey imports to testkit
2026-09-22 07:15:22 +00:00
Cursor Agent
c8ea27c239 chore(api): drop unused db imports after follow-up extract
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
2026-09-22 07:10:48 +00:00
Cursor Agent
f5265924ba fix(api): route group follow-ups to the waiting member bot
Group threads/followUp always steered to members[0], so a follow-up
while a later member had waiting_input went to the wrong bot or started
a fresh run on the first member instead of continuing the ask.

Resolve the target bot like group sends: prefer waiting ask/takeover on
any member, then the newest steerable active run, then @mentions, then
the default first member. Extract followUpGroupThreadMessage and add
regression tests.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
2026-09-22 07:10:48 +00:00
Cursor Agent
44d1bc7277 fix(api): redact memory provider details in aiConsent/status for members
Non-owner shared-space members could read the space memory provider id
and internal endpoint origin via aiConsent/status, bypassing the owner-only
policy that draft PR #46 applies to memory/providerConfig.

Owners still see full provider disclosure; members get a generic Space
memory recipient when a provider is configured, without loading settings.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
2026-09-22 07:10:42 +00:00
2f8368a9ce fix(api): gate memory providerConfig reads to Space owners
- Replace findSpaceMemoryConfig inline call with getMemoryProviderConfig(deps, actor)
- Add owner gate: space members get 403, owners get config
- Add authorization tests for providerConfig/connectProvider/setDefaultScope/disconnectProvider
- Add wiring test to ensure router.ts uses the owner-gated handler
2026-09-22 07:10:07 +00:00
Cursor Agent
101c908cdc style: format introPostedAt test expectation
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
2026-09-22 07:06:21 +00:00
Cursor Agent
2ee72af28b fix(messaging): set introPostedAt only after intro delivery succeeds
Group intros were stamped as posted before the outbox drain ran. When
delivery enqueue failed or the intro row exhausted retries, the channel
stayed marked and no retry path existed — unlinked participants never saw
the onboarding message.

Stamp introPostedAt in the drain path after a successful provider send,
matching the messagingMirroredAt pattern from #42.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
2026-09-22 07:06:21 +00:00
31a6438145 fix(api): block cross-member Team Computer takeover hijack in shared Spaces 2026-09-22 07:05:29 +00:00
928d9ad950 fix(api): redact memory provider details in aiConsent/status for members 2026-09-22 07:05:26 +00:00
Cursor Agent
ca292d2cc0 chore(api): drop unused db imports after follow-up extract
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
2026-09-22 07:04:42 +00:00
Cursor Agent
57f3ee4958 fix(api): route group follow-ups to the waiting member bot
Group threads/followUp always steered to members[0], so a follow-up
while a later member had waiting_input went to the wrong bot or started
a fresh run on the first member instead of continuing the ask.

Resolve the target bot like group sends: prefer waiting ask/takeover on
any member, then the newest steerable active run, then @mentions, then
the default first member. Extract followUpGroupThreadMessage and add
regression tests.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
2026-09-22 07:04:42 +00:00
Cursor Agent
33ce657201 fix: sort adapter imports for biome
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
2026-09-22 07:04:29 +00:00
Cursor Agent
0b5cd4ed43 fix(api): scope Team Computer export to exporting bot workspace
Team mode shares one disk per Space. export/bot walked the entire
team home, so a member exporting their own bot received every other
member's workspace files too.

Scope exportHome with the exporting bot's bots/<id>/ prefix and add
regression coverage for the shared-Space case.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
2026-09-22 07:04:29 +00:00
9c87398f53 fix(api): scope routines/remove to owner in shared Spaces 2026-09-22 06:21:53 +00:00
Cursor Agent
18934e03c9 fix(api): skip group membership sweep on partial participant rosters
Group inbound webhooks often report only the sender in participants,
not the full roster. Sweeping on any non-empty list incorrectly marked
approved members as left, silently dropping them from channel fan-out.

Gate the sweep on participantsAuthoritative so adapters opt in only when
the provider supplies a complete roster.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
2026-09-22 06:20:56 +00:00