diff --git a/.env.example b/.env.example index 6272ca7..c266d85 100644 --- a/.env.example +++ b/.env.example @@ -6,12 +6,19 @@ # quote any value containing spaces, <, >, or other shell metacharacters, and never # use a variable name with a hyphen. A malformed line kills every tick. # -# Every YAAS_* knob below is OPTIONAL and shown with its built-in default, so an -# empty .env (plus your Slack app values) is a working install. Uncomment only what -# you want to change. +# Every YAAS_* knob below is OPTIONAL and shown with its built-in default. Uncomment +# only what you want to change. # ── Slack app (create your own — PKCE OAuth, no client secret) ───────────── -# REQUIRED. These four have no default: an empty .env is NOT a working install. +# The free Python Slack checkers and reaction sweep are enabled by default and require +# these four values plus the Keychain token installed by setup.sh. +# +# If you have no Slack app and intend to access Slack only from scheduled paid workers +# through Claude/Codex/Cursor MCP, disable the local adapter. Existing slack_* entries +# remain stored with unchanged watermarks; the Python loop quietly ignores them. +# Scheduled and non-Slack watches continue normally. +# +# YAAS_SLACK_CHECKERS_ENABLED=0 # # YAAS talks to Slack via a user-token OAuth app in YOUR workspace, which you # have to create before these values can exist. Get the ready-to-paste manifest @@ -157,11 +164,11 @@ YAAS_CODEX_PERMISSION_MODE=workspace-write # YAAS_WORKER_STALL_SECONDS=900 # Model overrides. Leave unset to use each CLI's own default. -# Recommended default: Opus 5 at low thinking. Triage work is bounded and well-scoped (the +# Recommended default: Opus 4.8 at medium thinking. Triage work is bounded and well-scoped (the # quest's context.md carries the decision rules), so low effort is fast and cheap while still # using the most capable model. Raise the effort per-install if your quests need deeper reasoning. -YAAS_CLAUDE_MODEL=claude-opus-5 -YAAS_CLAUDE_EFFORT=low +YAAS_CLAUDE_MODEL=claude-sonnet-5 +YAAS_CLAUDE_EFFORT=medium # YAAS_CODEX_MODEL= # YAAS_CURSOR_MODEL= diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..41ba88b --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,7 @@ +# Default owner for every file in this repository. +# +# A GitHub handle only resolves inside its own tenant, so this line cannot name both +# mirrors at once: listing the other tenant's handle makes the whole entry an "Unknown +# owner" error there. The handle below is correct for this repository; the publish +# script rewrites it when it delivers a snapshot to the other tenant. +* @guangmian-circle diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml index 1c34f74..1c17b79 100644 --- a/.github/workflows/scan.yml +++ b/.github/workflows/scan.yml @@ -1,9 +1,9 @@ name: Scan on: pull_request: - branches: [main] + branches: [master] push: - branches: [main] + branches: [master] jobs: scan: if: github.event_name == 'pull_request' diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 5184f53..25cad02 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -66,6 +66,13 @@ OAuth uses PKCE, stores the resulting user token in the operating-system Keychai Python checkers a supported credential they can use directly. The app is the workspace-approved identity; the token is the local key that lets the no-LLM polling plane act as that identity. +An installation without that app can set `YAAS_SLACK_CHECKERS_ENABLED=0`. This disables the +entire local Slack adapter: `slack_*` entries are not executed, their watermarks do not move, and +the global reaction sweep does not run. Schedule and non-Slack checkers are unaffected. Agent +workers keep their own connector configuration, so a scheduled paid sweep may still use Slack +through Claude, Codex, or Cursor MCP. The switch lives at the adapter seam because all local Slack +watch types share the same credential and failure mode. + The alternative is one published Sidequestor Marketplace app shared by every workspace. That would reduce setup to an install-and-approve flow, but it would make every installation depend on a centrally owned app identity, its publication status, and its ongoing support. A hosted OAuth @@ -313,6 +320,18 @@ state/quests/active// Lifecycle: `active/` → `completed/` or `archived/`. A quest is a folder. No database, no schema migration. +Workspace-level briefings are separate from quests: + +``` +state/briefs/ +└── *.md free-form Markdown briefing names +``` + +`state/briefs/` is the canonical briefing store. The dashboard reads and renders these files; +sorts them newest-first by filesystem creation time; and treats cadence words in filenames only as +optional display hints. Slack posts are delivery copies. A briefing does not belong in a quest +folder because it may summarize several quests and other workspace activity. + --- ## 10. The file map diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..fdefa19 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,84 @@ +# Changelog + +All notable changes to Sidequestor (YaaS). The current version also lives in `VERSION`. + +Versions are dated by the day the snapshot was published. + +## Unreleased + +### Changed +- Every Markdown file in `state/briefs/` is now displayed. Briefing names are free-form, ordering + comes from filesystem creation time, and cadence words in names are optional display hints. + +## 2.5.1 - 2026-08-17 + +A same-day patch release: eight defects found by auditing the 2.5 snapshot, plus one reported from +using the dashboard. No new features, no config changes required. + +### Fixed +- Watermark claims are truncated to Slack's 6 decimals, never rounded. `checkers/result.py` + formatted `advance_to` with `:.6f`, which is round-half-even and could move a claim FORWARD of the + point actually proven covered; a message sitting exactly on the rounded microsecond was then read + as already-seen forever. `slack_dm` and `slack_mention` were also pre-rounding at the call site, + which made the emit-side fix a no-op for the two checkers that reach it. +- `result.emit()` no longer raises on a non-finite claim, as its docstring promises. +- `classify()` no longer erases a numeric `0` watermark claim through falsiness. An erased claim is + not a hold: the commit layer falls back to `now - lag` and jumps the watermark to NOW. +- The dashboard no longer rebuilds DOM it has not changed. Every 2s poll rewrote whole subtrees even + when the payload was identical, which made the prompt box flicker and reset a long draft's scroll + in the review interface. Writes now compare first, and a textarea's value is left alone when it + matches. +- Briefings are no longer rebuilt on every poll and discarded (~114ms and 75KB of JSON per poll on a + 150-file archive). They are served on demand from `/api/briefs`. +- Briefings have one canonical timestamp, `at`, derived from the filename with an explicit UTC + offset. Dates were previously read from the filename as a bare local wall clock, or from the file's + mtime, which are different things. +- `build_briefs()` checks the `__` filename prefix, so a stray `.md` in + `state/briefs/` can no longer sort above every dated file and be served as the newest briefing. A + trailing segment is kept in the type rather than silently trimmed. +- The markdown renderer's link placeholder can no longer be forged from prose, and a `$&` in a URL + or label is inserted literally instead of being expanded as a substitution pattern. + +### Changed +- A fractional value for a whole-number knob is now refused at startup instead of being floored to 0 + and silently disabling the cap it was meant to set. Knobs whose reader honours a fraction + (`YAAS_STALE_REPLY_HOURS`, `YAAS_MAX_SPEND_*`) still accept one. + +### Added +- `yaas-triage/tests/unit/dashboard-render.test.sh`: the dashboard's renderer, date helpers and + write guards are tested by behaviour, running the shipped implementations rather than asserting + that the file contains certain strings. Skips cleanly where `node` is absent, naming what it did + not cover. + +## 2.5 - 2026-08-17 + +The first versioned release. Everything below landed after the initial public import. + +### Added +- Dashboard v2: a manual-review surface with its own overlay, worker state, clearer metrics, a + revise-and-resubmit path, and the Field Guide theme. +- A quick start (`QUICKSTART.md`) written for someone who has never run the loop. +- Unit coverage for watermark precision, the re-armed approval watch, and the edit route. +- A `doctor.sh` Python version check, so an unsupported interpreter fails with a readable message + instead of a `TypeError` mid-dispatch. + +### Changed +- Watermarks are stored at Slack's 6-decimal precision, so a message can no longer be re-read or + skipped because of a rounded timestamp. +- Timeline events are stamped by the logging helper rather than the worker, which has no clock. +- README rewritten around the agent-driven install and the Slack-first workflow; `ARCHITECTURE.md` + and the shipped skills realigned with the runtime that actually runs. +- The approval watch re-arms on every non-terminal transition, so a reviewed item cannot stall. +- Quests are documented as full local agent missions, including how their watches adapt. +- Python 3.9 is supported again; duplicated helpers consolidated. + +### Fixed +- Security hardening: path traversal, the approval gate, the parser, a dispatch loop, and token + exposure. +- Uninitialised quests are distinguished from empty ones, and the create modal no longer + overpromises. +- The dashboard logo is served from a file with `no-store` instead of an inlined blob. + +## Earlier + +Pre-2.5 history is in the git log; the initial public import is the root of this repository. diff --git a/QUICKSTART.md b/QUICKSTART.md index aa0658c..48da4b9 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -83,6 +83,11 @@ Behind the curtain, a tiny local Python loop checks for changes about once a minute. When nothing changed, no AI runs. When something did, it wakes one agent for one target, records the result, and goes back to sleep. +Free Slack checking requires a workspace Slack app. If that is unavailable, set +`YAAS_SLACK_CHECKERS_ENABLED=0` in `.env`. Slack watch entries stay recorded but dormant, +the reaction sweep is disabled, and schedule, email, Jira, and GitHub watches continue. +Scheduled agent runs can still access Slack through the agent's own MCP connector. + That is the whole rhythm: **react for now, quest for later, review before send.** Want the technical tour? See the full [Sidequestor README](README.md). diff --git a/README.md b/README.md index 7842b08..5d0d466 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ > *Safely expose your local interactive agent setup as a service.* +**Version 2.5.1** (see [`CHANGELOG.md`](CHANGELOG.md); the current version is also in `VERSION`). + **Sidequestor (YaaS, short for Yourself-as-a-Service)** keeps your local agent tending the work you can't sit and watch: it turns the loose ends scattered across your channels into things your own agent chases down while you're doing something else. @@ -99,11 +101,12 @@ Attach https://github.com/circlefin/sidequestor.git to this repository. Follow i existing-repository contract exactly, preserve all existing work, and stop on any unsafe collision. ``` -Both prompts stop and ask you before Slack authorization and before background jobs are installed. +Both prompts stop and ask you before Slack authorization, when local Slack checking is enabled, +and before background jobs are installed. Prefer the fresh folder unless Sidequestor has to live inside an existing repository. Manual route and prerequisites: [Pack the kit](#pack-the-kit). -> **macOS only.** It runs on `launchd` and keeps its Slack token in the Keychain, so it will not +> **macOS only.** It runs on `launchd` and, when local Slack checking is enabled, keeps its Slack token in the Keychain, so it will not > run as-is on Linux or Windows. Early project, no support commitment. Sidequestor is built for solutions engineers, architects, PMs, support and ops leads, researchers, @@ -181,15 +184,16 @@ is a confusing `TypeError` mid-dispatch, so `doctor.sh` checks the version expli ### What the install prompt does You paste the prompt; the agent does the work. It clones, creates `.env` and your agent-rules -file, prints the Slack manifest, runs setup, installs the background jobs, and verifies the whole +file, prints the Slack manifest when local Slack checking is wanted, runs setup, installs the background jobs, and verifies the whole thing with the doctor, the health monitor, and the test suite. It stops and asks before Slack authorization, before installing background jobs, and before any commit. Four things it cannot do for you. They are the whole of your install: -**1. Create the Slack app.** Sidequestor ships no shared Marketplace app on purpose: the polling +**1. Choose how Slack is checked.** Sidequestor ships no shared Marketplace app on purpose: the polling loop needs a credential it can use without waking a model, and that credential should belong to -you rather than to a vendor. So the app has to exist in **your** workspace first. +you rather than to a vendor. For free per-minute Slack checking, the app has to exist in **your** +workspace first. Your agent prints a manifest with every scope and the redirect URL already filled in. Take it to → **Create New App** → **From an app manifest**, pick your workspace, @@ -199,12 +203,17 @@ Budget more than two minutes if your workspace restricts app installs. `reaction `reactions:write` are the scopes most often held for admin review; without them the reaction triggers do nothing. Grant them later and ask your agent to re-authorize. -**2. Approve the OAuth.** A browser window, once. PKCE, no client secret. The token lands in your -Keychain, never in the repo. +If you are not able to set up an organisation level Slack app, set `YAAS_SLACK_CHECKERS_ENABLED=0` in `.env`. Setup then skips +Slack OAuth. The Python loop leaves every `slack_*` watch and its watermark untouched, skips the +reaction sweep, and continues checking schedules and all non-Slack sources. A scheduled paid +worker may still read those Slack entries and access Slack through its own MCP connector. + +**2. Approve the OAuth, if local Slack checking is enabled.** A browser window, once. PKCE, no +client secret. The token lands in your Keychain, never in the repo. -**3. Say what goes in `.env` beyond Slack.** Your sender identity for email replies, reaction emoji -overrides, and the spend ceilings. Everything except the four `SLACK_*` values has a default, and -your agent will walk you through the ones worth changing. Worth a glance at the ceilings. +**3. Say what goes in `.env`.** Choose whether local Slack checking is enabled, then configure +the adapters you use, your sender identity for email replies, reaction emoji overrides, and the +spend ceilings. Your agent will walk you through the values worth changing. **4. Put your voice in the agent-rules file.** `CLAUDE.md` for Claude, `AGENTS.md` for Codex, or the addendum merged into your harness's own project-rules file. Who your agent is, how it writes, @@ -390,11 +399,17 @@ Once installed, there is nothing to do. It ticks every 60 seconds. The pinned dashboard at `localhost:8877` is the whole interface. That's where drafts wait for you and every quest's messages are one click from Slack. A pulsing pill appears when a worker is running: click it to watch the agent think in real time. Stuck (misconfigured) or throttled (rate limited) quests sort to the top with a badge. **Control** is -the live map, **Audit** keeps prior approvals and dispatch runs, and **Field guide** in the header -explains quests and Slack reaction spells. +the live map, **Briefings** reads the canonical Markdown archive, **Audit** keeps prior approvals +and dispatch runs, and **Field guide** in the header explains quests and Slack reaction spells. The dashboard is the map. The files remain the truth, and your agent is the one that reads them. +Briefings have one canonical home: `state/briefs/`. Every Markdown file in that directory is a +briefing; filenames are free-form labels rather than a schema. The dashboard sorts files by their +filesystem creation time and recognizes `morning`, `evening`, `weekly`, or `monthly` in a name only +as an optional display hint. Anything posted to Slack is a delivery copy rather than the source of +truth. Briefings stay outside quest folders because they can summarize work across several quests. + For anything the dashboard doesn't show, ask. Your agent knows the ops surface, so questions work better than commands: diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..73462a5 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +2.5.1 diff --git a/dashboard.html b/dashboard.html index aaa94dd..3fc54bb 100644 --- a/dashboard.html +++ b/dashboard.html @@ -11,7 +11,7 @@ .hero{display:grid;grid-template-columns:minmax(0,1.6fr) repeat(3,minmax(145px,.55fr));gap:1px;overflow:hidden;border:1px solid var(--line);border-radius:14px;background:var(--line);box-shadow:var(--shadow)}.hero>*{min-height:86px;padding:15px 18px;background:var(--surface)}.headline{display:flex;flex-direction:column;justify-content:center;gap:4px}.headline strong{font-size:20px;letter-spacing:-.04em}.headline span{color:var(--muted);font-size:12px}.metric{border:0;text-align:left}.metric:hover{background:#fbfdff}.metric b{display:block;font:700 24px/1 var(--mono);letter-spacing:-.06em}.metric span{display:block;margin-top:8px;color:var(--muted);font-size:10px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.metric.warn b{color:var(--amber)}.metric.live b{color:var(--teal)}.field-hint{margin:6px 0 0;font-size:10px;line-height:1.5}.quest.initialising{opacity:.55}.quest.initialising .quest-detail{font-style:italic}#quest-prompt{min-height:176px;resize:vertical}.worker-state{display:block;font:normal 26px/1 "Apple Color Emoji","Segoe UI Emoji","Noto Color Emoji",sans-serif}.worker-state.asleep{filter:grayscale(1);opacity:.45}.spinner{display:block;width:23px;height:23px;margin:1px 0 2px;border:3px solid var(--teal2);border-top-color:var(--teal);border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.spinner{animation-duration:2.6s}}.livebox{display:none;margin-top:10px;padding:12px 16px;border:1px solid var(--line);border-radius:10px;background:#122338;color:#d7e5f3}.livebox.open{display:block}.livebox-head{display:flex;justify-content:space-between;gap:12px}.livebox pre{max-height:240px;margin:9px 0 0;overflow:auto;font:11px/1.55 var(--mono);white-space:pre-wrap} .headline-copy{width:fit-content;max-width:100%}.motion-stream{position:relative;width:100%;height:7px;margin-top:2px;overflow:hidden;opacity:0;transition:opacity .2s;-webkit-mask-image:linear-gradient(90deg,transparent,#000 10%,#000 90%,transparent);mask-image:linear-gradient(90deg,transparent,#000 10%,#000 90%,transparent)}.motion-stream::after{content:'';position:absolute;top:3px;right:0;left:0;height:1px;background:#d8e6f1}.motion-stream.active{opacity:1}.motion-belt{position:relative;z-index:1;display:flex;width:max-content;animation:conveyor 7s linear infinite;will-change:transform}.motion-pack{display:flex;align-items:center;gap:10px;padding-right:10px}.motion-puck{position:relative;width:19px;height:4px;border:1px solid #b8d4f4;border-radius:999px;background:var(--blue2);box-shadow:0 1px 2px rgba(7,95,198,.1)}.motion-puck:nth-child(3n+2){width:9px;border-color:#acdcd4;background:var(--teal2)}.motion-puck::after{content:'';position:absolute;top:1px;right:2px;width:1px;height:1px;border-radius:50%;background:var(--blue)}.motion-puck:nth-child(3n+2)::after{background:var(--teal)}.motion-stream.event .motion-belt{filter:drop-shadow(0 0 3px rgba(0,124,112,.5))}.motion-stream.event .motion-puck{animation:cargo-flash .8s ease-out}.motion-stream:not(.active) .motion-belt,.motion-stream.paused .motion-belt{animation-play-state:paused}@keyframes conveyor{to{transform:translateX(-50%)}}@keyframes cargo-flash{40%{border-color:var(--teal);background:#c9f0e8;transform:scaleY(1.35)}}@media(prefers-reduced-motion:reduce){.motion-belt{animation:none;transform:translateX(-8px)}.motion-stream.event .motion-puck{animation:none}} .workspace{display:grid;grid-template-columns:minmax(0,1.12fr) minmax(360px,.88fr);gap:20px;flex:1;min-height:0;margin-top:19px}.panel{display:grid;grid-template-rows:auto minmax(0,1fr);min-height:0;overflow:hidden;border:1px solid var(--line);border-radius:13px;background:rgba(255,255,255,.94);box-shadow:0 8px 22px rgba(22,35,52,.045)}.panel-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 17px 11px;border-bottom:1px solid var(--line)}.panel-head h2{font-size:14px}.hint,.muted{color:var(--muted);font-size:12px}.scroll{min-height:0;overflow:auto;overscroll-behavior:contain}.section{padding:13px 17px}.section+.section{border-top:1px solid var(--line)}.section-label{display:flex;align-items:center;gap:7px;margin-bottom:8px;color:var(--muted);font-size:10px;font-weight:800;letter-spacing:.1em;text-transform:uppercase}.count,.pill{display:inline-flex;align-items:center;border-radius:5px;padding:2px 7px;font:700 10px var(--mono)}.count,.pill.neutral{color:#506072;background:#edf1f5}.pill.active,.pill.send,.pill.done{color:#006d63;background:var(--teal2)}.pill.normal,.pill.info,.pill.reviewed{color:#0059ab;background:var(--blue2)}.pill.high,.pill.pending_review,.pill.needs_reply,.pill.executing{color:#895500;background:var(--amber2)}.pill.blocked,.pill.cancelled,.pill.stalled{color:#9b3735;background:var(--red2)}.pill.low{color:#596878;background:#edf1f5}.pill.draft{color:#0059ab;background:var(--blue2)} -.quest{width:100%;display:grid;grid-template-columns:9px minmax(0,1fr) auto;gap:10px;align-items:start;padding:10px 0;border:0;border-top:1px solid #e8eef3;text-align:left;background:transparent}.quest:first-of-type{border-top:0}.quest:hover .quest-name{color:var(--blue)}.signal{width:8px;height:8px;margin-top:6px;border-radius:50%;background:#aab7c5}.signal.review{background:var(--amber);box-shadow:0 0 0 3px var(--amber2)}.signal.running{background:var(--teal);box-shadow:0 0 0 3px var(--teal2)}.quest-name{display:block;font-weight:800}.quest-detail{display:block;overflow:hidden;color:var(--muted);font-size:12px;text-overflow:ellipsis;white-space:nowrap}.time{color:var(--faint);font:10px var(--mono);white-space:nowrap}.empty{padding:16px;color:var(--muted);font-size:12px} +.quest{width:100%;display:grid;grid-template-columns:9px minmax(0,1fr) auto;gap:10px;align-items:start;padding:10px 0;border:0;border-top:1px solid #e8eef3;text-align:left;background:transparent}.quest:first-of-type{border-top:0}.quest:hover .quest-name{color:var(--blue)}.signal{width:8px;height:8px;margin-top:6px;border-radius:50%;background:#aab7c5}.signal.review{background:var(--amber);box-shadow:0 0 0 3px var(--amber2)}.signal.running{background:var(--teal);box-shadow:0 0 0 3px var(--teal2)}.quest-title-row{display:flex;min-width:0;align-items:center;gap:6px}.quest-name{display:block;min-width:0;overflow:hidden;font-weight:800;text-overflow:ellipsis;white-space:nowrap}.quest-review-count{flex:none}.quest-detail{display:block;overflow:hidden;color:var(--muted);font-size:12px;text-overflow:ellipsis;white-space:nowrap}.time{color:var(--faint);font:10px var(--mono);white-space:nowrap}.empty{padding:16px;color:var(--muted);font-size:12px} .queued{margin:0 0 8px;border:1px solid #cfe0f3;border-radius:9px;background:#f6faff}.queued summary{display:flex;align-items:center;gap:7px;padding:9px 10px;cursor:pointer;list-style:none}.queued summary::-webkit-details-marker{display:none}.queued-body{padding:0 10px 10px}.queued-message{margin:7px 0;color:#405166;font-size:12px;white-space:pre-wrap}.review-card{margin-bottom:10px;padding:12px;border:1px solid #efd8a1;border-radius:9px;background:#fffaf0}.review-card.stalled{border-color:#e8b8b5;background:#fff7f6}.card-head,.target-row,.card-actions,.state-row{display:flex;align-items:center;flex-wrap:wrap;gap:7px}.card-head{justify-content:space-between}.card-head .state-row{gap:6px}.card-title{font-weight:800}.card-age{color:var(--faint);font:10px var(--mono)}.target-row{margin:8px 0;color:var(--muted);font:11px var(--mono)}.target-row a{color:var(--blue);font-family:var(--sans);font-weight:750;text-decoration:none}.risk{margin:7px 0;padding:7px 8px;border-radius:6px;color:#75520d;background:var(--amber2);font-size:11px}.card-context,.reply{margin:7px 0;color:#536477;font-size:12px;white-space:pre-wrap}.reply{padding:8px;border:1px solid #cfe6e2;border-radius:6px;background:#f1faf8}.review-history{margin:7px 0;padding-left:17px;color:var(--muted);font-size:11px}.review-card textarea{min-height:76px}.review-note{min-height:54px}.card-actions{margin-top:8px}.field-label{display:block;margin:9px 0 3px;color:var(--muted);font-size:10px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.awaiting{margin:8px 0;padding:7px 9px;border:1px solid #cfe0f3;border-radius:7px;color:#0059ab;background:var(--blue2);font-size:11px;font-weight:750}.convo{margin:8px 0;font-size:12px}.convo summary{overflow:hidden;color:var(--muted);cursor:pointer;list-style:none;text-overflow:ellipsis;white-space:nowrap}.convo summary::-webkit-details-marker{display:none}.convo[open] summary{margin-bottom:4px;white-space:normal}.convo-label{display:none;font-weight:800}.convo[open] .convo-preview{display:none}.convo[open] .convo-label{display:inline}.convo p{margin:4px 0;color:#536477}.convo strong{color:var(--ink)}.inline-status{min-height:17px;margin-top:6px;color:var(--muted);font-size:11px}.inline-status.error{color:var(--red)} .focus{display:grid;grid-template-rows:auto minmax(92px,1fr) auto;min-height:0}.focus[hidden],.quest-list[hidden]{display:none}.control.focused{grid-template-rows:minmax(0,1fr)}.control.focused>.panel-head{display:none}.focus-head{padding:12px 17px;border-bottom:1px solid var(--line)}.focus-head h2{margin:8px 0 5px;font-size:19px;letter-spacing:-.04em}.back{border:1px solid #c9ddf8;border-radius:6px;padding:4px 7px;color:var(--blue);background:var(--blue2);font-size:11px;font-weight:800}.focus-body{min-height:0;overflow:auto;padding:12px 17px;overscroll-behavior:contain}.focus-body>.block{margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid var(--line)}.block h3{margin-bottom:7px;font-size:12px}.focus-footer{min-height:0;padding:9px 17px 10px;border-top:1px solid var(--line);background:#fbfdff;box-shadow:0 -7px 16px rgba(22,35,52,.04)}#review-dialog{width:min(780px,calc(100vw - 32px))}#review-dialog .dialog-body{display:flex;flex-direction:column;max-height:calc(100dvh - 48px);padding:0}.review-dialog-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:16px 20px 12px;border-bottom:1px solid var(--line)}.review-dialog-head h2{font-size:17px;letter-spacing:-.03em}.icon-button{border:1px solid var(--line);border-radius:7px;padding:3px 8px;color:var(--muted);background:#fff;font-size:14px;line-height:1.2}.review-dialog-body{min-height:0;overflow:auto;padding:14px 20px 18px;overscroll-behavior:contain}#review-dialog .review-card{margin:0;border:0;padding:0;background:transparent}#review-dialog .review-card textarea{min-height:132px}#review-dialog .review-note{min-height:76px}.attention-row{text-align:left;width:100%;display:grid;grid-template-columns:minmax(0,1fr) auto;gap:9px;align-items:center;padding:9px 10px;margin-bottom:7px;border:1px solid #efd8a1;border-radius:9px;background:#fffaf0}.attention-row .state-row{justify-content:flex-end}.attention-row.settled{border-color:#cfe0f3;background:#f6faff}.attention-row strong{display:block;font-size:12px}.attention-row span{display:block;overflow:hidden;color:var(--muted);font-size:11px;text-overflow:ellipsis;white-space:nowrap}.composer textarea{min-height:44px;max-height:44px;padding:12px 13px;border-color:#c9ddf8;border-style:dashed;color:var(--blue);background:#f4f9ff;cursor:pointer;transition:max-height .12s ease,background .12s ease,box-shadow .12s ease}.composer textarea::placeholder{color:var(--blue);opacity:.9;font-weight:750}.composer:not(.open) textarea:hover{background:var(--blue2);border-color:var(--blue);box-shadow:0 3px 10px rgba(7,95,198,.14)}.composer .composer-row{display:none}.composer.open textarea{min-height:84px;max-height:120px;padding:8px 9px;border-color:var(--line);border-style:solid;color:var(--ink);background:#fff;cursor:auto}.composer.open textarea::placeholder{color:var(--faint);font-weight:400}.composer.open .composer-row{display:flex}.composer strong{display:none;margin-bottom:5px}.composer.open strong{display:block}.focus-footer textarea{min-height:44px;max-height:88px;margin-top:5px}.composer-row{display:flex;align-items:center;justify-content:space-between;gap:8px}.markdown{color:#405166;font-size:12px;line-height:1.6}.markdown h1{margin:0 0 8px;font-size:17px}.markdown h2{margin:14px 0 5px;font-size:14px}.markdown h3{margin:12px 0 5px;color:var(--muted);font-size:11px;text-transform:uppercase}.markdown p{margin:0 0 7px}.markdown ul,.markdown ol{margin:5px 0 8px;padding-left:19px}.markdown code{padding:1px 4px;border-radius:3px;background:#edf2f7;font:11px var(--mono)}.watch,.loop{padding:8px 0;border-top:1px solid #e8eef3}.watch:first-child,.loop:first-child{border-top:0}.watch strong,.loop strong{display:block;font-size:12px}.watch span,.loop span{color:var(--muted);font-size:12px} .trail-tools{display:flex;gap:4px}.filter{border:1px solid transparent;border-radius:6px;padding:4px 7px;color:var(--muted);background:transparent;font-size:11px;font-weight:700}.filter.active,.filter:hover{border-color:#c9ddf8;color:var(--blue);background:var(--blue2)}.event{display:grid;grid-template-columns:58px 9px minmax(0,1fr);gap:8px;padding:11px 16px;border-bottom:1px solid var(--line)}.event-time{padding-top:3px;color:var(--faint);font:10px var(--mono)}.event-dot{width:8px;height:8px;margin-top:5px;border-radius:50%;background:#a6b2c0}.event-dot.agent{background:var(--blue)}.event-dot.external{background:var(--teal)}.event-dot.alert{background:var(--amber)}.event-title{display:flex;align-items:center;flex-wrap:wrap;gap:6px;font-weight:750}.trail-quest{border:0;padding:0;color:var(--blue);background:transparent;font:inherit;font-style:normal;font-weight:inherit}.trail-quest:hover{text-decoration:underline}.event-detail{margin-top:3px;color:var(--muted);font-size:12px;white-space:pre-wrap;word-break:break-word}.event-meta{margin-top:5px;color:var(--faint);font:10px var(--mono)}.event-meta a{color:var(--blue);font-family:var(--sans);font-weight:750;text-decoration:none} @@ -39,7 +39,7 @@ :root{--ink:#183044;--muted:#536978;--faint:#7e9096;--paper:#e8f0e8;--surface:#fffdf4;--line:#bdcbbf;--blue:#08786d;--blue2:#e1f1e8;--teal:#08786d;--teal2:#dff3e8;--amber:#99610c;--amber2:#fff0b8;--red:#bc4938;--red2:#ffe0d9;--shadow:0 5px 0 #aebfb2,0 14px 28px rgba(24,48,68,.08)} body{background-color:#e8f0e8;background-image:linear-gradient(rgba(255,255,255,.34) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.34) 1px,transparent 1px),radial-gradient(circle at 15% 0%,#f5f4d9 0,transparent 32%),radial-gradient(circle at 100% 100%,#d7ebe1 0,transparent 36%);background-size:24px 24px,24px 24px,auto,auto} .shell{padding-top:17px}.top{min-height:47px;margin-bottom:15px}.brand h1{font-size:18px;letter-spacing:-.035em}.brand .eyebrow{color:#537166;font:850 9px var(--mono);letter-spacing:.14em}.mark img{filter:drop-shadow(0 3px 0 rgba(24,48,68,.2))} -.navs{gap:0;overflow:hidden;border:2px solid #24435b;border-radius:9px;background:#dce9dc;box-shadow:0 3px 0 #24435b}.nav{min-width:70px;border-radius:0;padding:7px 12px;color:#526b60;font-size:12px;font-weight:900}.nav+.nav{border-left:2px solid #24435b}.nav.active,.nav:hover{color:#fff;background:#107c70}.nav:active{box-shadow:inset 0 2px 0 rgba(24,48,68,.24)} +.navs{gap:0;overflow:hidden;border:2px solid #24435b;border-radius:9px;background:#dce9dc;box-shadow:0 3px 0 #24435b}.nav{min-width:70px;border-radius:0;padding:7px 12px;color:#526b60;font-size:12px;font-weight:900}.nav+.nav{border-left:2px solid #24435b}.nav.active,.nav:hover{color:#fff;background:#107c70}.nav:active{box-shadow:inset 0 2px 0 rgba(24,48,68,.24)}.mode-menu{display:none;position:relative;margin-left:auto}.mode-menu-trigger{display:flex;align-items:center;justify-content:space-between;gap:9px;width:112px;white-space:nowrap}.mode-menu-trigger svg{width:11px;height:7px;flex:none;transition:transform .12s}.mode-menu-trigger[aria-expanded="true"] svg{transform:rotate(180deg)}.mode-menu-list{position:absolute;z-index:30;top:calc(100% + 7px);right:0;width:112px;overflow:hidden;border:2px solid #24435b;border-radius:8px;background:#fff9df;box-shadow:0 4px 0 #24435b,0 12px 24px rgba(24,48,68,.16)}.mode-menu-list[hidden]{display:none}.mode-option{display:block;width:100%;border:0;border-bottom:1px solid #c7d3c8;padding:7px 9px;color:#24435b;background:#fff9df;font:900 12px var(--sans);text-align:left}.mode-option:last-child{border-bottom:0}.mode-option:hover,.mode-option[aria-selected="true"]{color:#fff;background:#107c70} .button{border:2px solid #24435b;border-radius:8px;padding:6px 10px;color:#24435b;background:#fff9df;font-weight:900;box-shadow:0 3px 0 #24435b;transition:transform .12s ease,box-shadow .12s ease,background .12s ease}.button:hover{background:#fff1b7;transform:translateY(-1px);box-shadow:0 4px 0 #24435b}.button:active{transform:translateY(2px);box-shadow:0 1px 0 #24435b}.button.primary{border-color:#7d3027;color:#fff;background:#e75c49;box-shadow:0 3px 0 #7d3027}.button.primary:hover{background:#f06a55;box-shadow:0 4px 0 #7d3027}.button.danger{border-color:#923b31;color:#923b31;background:#ffe5dc;box-shadow:0 3px 0 #923b31}.button:disabled{box-shadow:none;transform:none}.help-trigger{border:2px solid #24435b;color:#24435b;background:#fff9df;box-shadow:0 3px 0 #24435b}.help-trigger:hover{color:#24435b;background:#fff1b7;box-shadow:0 4px 0 #24435b}.connection{gap:8px;border:1px solid #8db6a5;border-radius:999px;padding:5px 9px;color:#08786d;background:#e7f5ec;font:800 10px var(--mono)}.connection i{width:7px;height:7px;box-shadow:none}.connection.stale{border-color:#d7b86e;background:#fff1c6}.connection.down{border-color:#e0a49a;background:#ffe9e2} .hero{gap:2px;border:2px solid #24435b;border-radius:13px;background:#24435b;box-shadow:0 5px 0 #9fafaa,0 14px 28px rgba(24,48,68,.08)}.hero>*{background:#fffdf4}.hero .headline{color:#fff;background:linear-gradient(135deg,#118276,#08685f)}.headline strong{font-size:21px}.hero .headline span{color:#d8fff2}.hero .metric{transition:background .12s ease}.hero .metric:hover{background:#fff5cc}.metric b{color:#24435b;font-size:25px}.metric.warn b{color:#a5650a}.metric.live b{color:#08786d}.metric span{color:#607269;font:850 9px var(--mono);letter-spacing:.1em}.motion-stream::after{background:#8fc8b8}.motion-puck{border-color:#fff0a6;background:#ffd95a;box-shadow:0 1px 0 #6a5120}.motion-puck:nth-child(3n+2){border-color:#ffb1a5;background:#ef6653}.motion-puck::after{background:#664a0a}.motion-puck:nth-child(3n+2)::after{background:#7d3027}.worker-state.asleep{opacity:.52} .livebox{border:2px solid #24435b;border-radius:11px;color:#dff8ef;background:#20384b;box-shadow:0 4px 0 #142b3d}.livebox-head strong{font:850 11px var(--mono);letter-spacing:.04em}.livebox .button{border-color:#a9c3b5;background:#f8edc7;box-shadow:0 2px 0 #a9c3b5} @@ -52,15 +52,20 @@ .audit{gap:18px}.audit-panel{overflow:hidden;border:2px solid #24435b;border-radius:13px;background:#fffdf4;box-shadow:0 5px 0 #aebfb2}.audit-panel h2{padding:13px 16px;border-bottom:2px solid #24435b;background:#e2eee1;font-size:15px}.audit-item,.run{border-bottom-color:#ccd8ce}.audit-item:hover,.run:hover{background:#fbf7e6} textarea,input,select{border:2px solid #8da397;border-radius:7px;color:#183044;background:#fffdf4}textarea:focus,input:focus,select:focus{border-color:#08786d}.field-label{color:#49645b;font:900 9px var(--mono);letter-spacing:.1em}.icon-button{border:2px solid #24435b;color:#24435b;background:#fff5c9;box-shadow:0 2px 0 #24435b}.review-dialog-head{border-bottom:2px solid #24435b;background:#e2eee1}.review-dialog-head .eyebrow,.dialog-body>.eyebrow{color:#08786d;font-family:var(--mono)}.review-dialog-body{background:#fffdf4}dialog:not(#help-dialog){border:3px solid #24435b;border-radius:15px;background:#fffdf4;box-shadow:0 7px 0 #183044,0 24px 65px rgba(24,48,68,.3)}dialog:not(#help-dialog)::backdrop{background:rgba(24,48,68,.48);backdrop-filter:blur(2px)}dialog:not(#help-dialog) .dialog-body{background:#fffdf4}.dialog-body h2{letter-spacing:-.035em}.dialog-actions{padding-top:10px;border-top:1px solid #ccd8ce}.toast{border:2px solid #24435b;border-radius:9px;color:#24435b;background:#fff3bd;box-shadow:0 4px 0 #24435b,0 12px 24px rgba(24,48,68,.14)} .surface-badge{border-color:#aebdb2;color:#52665e;background:#e8eee7}.surface-badge.slack{border-color:#c8a9c6;color:#6d456c;background:#f5e7f3}.surface-badge.jira{border-color:#9cb9b2;color:#17685f;background:#e4f1e9}.surface-badge.email{border-color:#e2a99d;color:#963f32;background:#ffe8e1}.surface-badge.github{border-color:#aeb8b3;color:#394d48;background:#e9eeea} -@media(max-height:800px){.hero{box-shadow:0 3px 0 #9fafaa}.panel{box-shadow:0 3px 0 #aebfb2}.top{min-height:42px;margin-bottom:11px}}@media(max-width:620px){body{background-size:18px 18px,18px 18px,auto,auto}.shell{padding-top:10px}.top{gap:9px}.brand{min-width:0;flex:1}.brand h1{font-size:16px}.brand .eyebrow{font-size:8px}.navs{order:3}.nav{flex:1}.connection{padding:4px 7px}.hero{box-shadow:0 3px 0 #9fafaa}.panel{box-shadow:0 3px 0 #aebfb2}.panel-head{align-items:flex-start}.trail-tools{flex-wrap:wrap;justify-content:flex-end}.event{grid-template-columns:42px 8px minmax(0,1fr)}} +.briefings{display:none;flex:1;min-height:0;overflow:hidden;border:2px solid #24435b;border-radius:13px;background:#fffdf4;box-shadow:0 5px 0 #aebfb2}.briefings.open{display:grid;grid-template-columns:minmax(250px,310px) minmax(0,1fr)}.brief-index{display:grid;grid-template-rows:auto minmax(0,1fr);min-height:0;border-right:2px solid #24435b;background:#e2eee1}.brief-index-head{padding:18px 17px 14px;border-bottom:2px solid #24435b}.brief-index-head h2{margin:3px 0 4px;font-size:20px;letter-spacing:-.04em}.brief-index-head p{color:#526b60;font-size:11px}.brief-list{min-height:0;overflow:auto;padding:8px 0 18px;overscroll-behavior:contain}.brief-group-label{padding:12px 16px 5px;color:#526b60;font:900 9px var(--mono);letter-spacing:.12em;text-transform:uppercase}.brief-link{display:grid;width:100%;grid-template-columns:auto 1fr;gap:5px 9px;border:0;border-top:1px solid #bdcdbf;padding:10px 16px;color:#183044;background:transparent;text-align:left}.brief-link:last-child{border-bottom:1px solid #bdcdbf}.brief-link:hover{background:#f7f2cf}.brief-link.selected{color:#fff;background:#107c70}.brief-link-title{align-self:center;overflow:hidden;font-size:12px;font-weight:850;text-overflow:ellipsis;white-space:nowrap}.brief-link-date{grid-column:2;color:#60756c;font:10px var(--mono)}.brief-link.selected .brief-link-date{color:#d9f3ed}.brief-kind{align-self:start;border:1px solid currentColor;border-radius:4px;padding:2px 5px;color:#986000;background:#fff2d7;font:900 8px var(--mono);letter-spacing:.06em;text-transform:uppercase}.brief-kind.evening{color:#315276;background:#e6eff8}.brief-kind.weekly{color:#08786d;background:#dff3ec}.brief-kind.monthly{color:#9b3d36;background:#f9e5df}.brief-link.selected .brief-kind{border-color:rgba(255,255,255,.65);color:#fff;background:rgba(255,255,255,.13)}.brief-reader{min-width:0;overflow:auto;padding:34px clamp(24px,5vw,74px) 60px;overscroll-behavior:contain;background:linear-gradient(90deg,rgba(36,67,91,.04) 1px,transparent 1px),#fffdf4;background-size:32px 100%}.brief-paper{max-width:850px;margin:0 auto}.brief-reader-head{margin-bottom:26px;padding-bottom:18px;border-bottom:2px solid #24435b}.brief-reader-meta{display:flex;align-items:center;gap:9px;margin-bottom:10px;color:#526b60;font:800 10px var(--mono);text-transform:uppercase}.brief-reader-head h2{max-width:760px;font-size:clamp(25px,3vw,38px);line-height:1.08;letter-spacing:-.055em}.brief-copy{color:#263f50;font-size:14px;line-height:1.72}.brief-copy h1{display:none}.brief-copy h2{margin:27px 0 9px;font-size:19px;letter-spacing:-.025em}.brief-copy h3{margin:22px 0 7px;color:#08786d;font:900 11px var(--mono);letter-spacing:.08em;text-transform:uppercase}.brief-copy p{margin:0 0 13px}.brief-copy ul,.brief-copy ol{margin:4px 0 16px;padding-left:22px}.brief-copy li{margin-bottom:6px}.brief-copy a{color:#075fc6;text-decoration-thickness:1px;text-underline-offset:3px}.brief-copy code{border:1px solid #ccd8ce;border-radius:4px;padding:1px 5px;background:#eef3ea;font:12px var(--mono)}.brief-empty{display:grid;min-height:280px;place-items:center;padding:32px;color:#526b60;text-align:center}.shell.briefings-mode .hero,.shell.briefings-mode .livebox{display:none}.shell.briefings-mode .briefings{margin-top:0} +.brief-picker{display:none;position:relative;padding:9px;background:#e2eee1}.brief-picker-trigger{display:grid;width:100%;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:9px;text-align:left}.brief-picker-title{overflow:hidden;font:900 12px var(--sans);text-overflow:ellipsis;white-space:nowrap}.brief-picker-trigger svg{width:11px;height:7px;transition:transform .12s}.brief-picker-trigger[aria-expanded="true"] svg{transform:rotate(180deg)}.brief-picker-list{position:absolute;z-index:25;top:calc(100% - 3px);right:9px;left:9px;max-height:min(54dvh,430px);overflow:auto;border:2px solid #24435b;border-radius:8px;background:#fff9df;box-shadow:0 5px 0 #24435b,0 16px 30px rgba(24,48,68,.18)}.brief-picker-list[hidden]{display:none}.brief-picker-list .brief-group-label{padding:10px 11px 5px;background:#e2eee1}.brief-picker-option{display:grid;width:100%;grid-template-columns:auto minmax(0,1fr);gap:4px 8px;border:0;border-top:1px solid #c7d3c8;padding:8px 10px;color:#24435b;background:#fff9df;text-align:left}.brief-picker-option:hover,.brief-picker-option[aria-selected="true"]{color:#fff;background:#107c70}.brief-picker-option-title{overflow:hidden;font:900 12px var(--sans);text-overflow:ellipsis;white-space:nowrap}.brief-picker-option-date{grid-column:2;color:#60756c;font:10px var(--mono)}.brief-picker-option[aria-selected="true"] .brief-picker-option-date{color:#d9f3ed} +@media(max-height:800px){.hero{box-shadow:0 3px 0 #9fafaa}.panel{box-shadow:0 3px 0 #aebfb2}.top{min-height:42px;margin-bottom:11px}}@media(max-width:980px){.briefings.open{grid-template-columns:240px minmax(0,1fr)}.brief-reader{padding-right:28px;padding-left:28px}}@media(max-width:620px){body{background-size:18px 18px,18px 18px,auto,auto}.shell{padding-top:10px}.top{gap:9px}.brand{min-width:0;flex:1}.brand h1{font-size:16px}.brand .eyebrow{font-size:8px}.navs{display:grid;grid-template-columns:repeat(3,minmax(0,1fr))}.nav{width:100%;min-width:0}.connection{padding:4px 7px}.hero{box-shadow:0 3px 0 #9fafaa}.panel{box-shadow:0 3px 0 #aebfb2}.panel-head{align-items:flex-start}.trail-tools{flex-wrap:wrap;justify-content:flex-end}.event{grid-template-columns:42px 8px minmax(0,1fr)}.briefings.open{display:block;overflow:auto}.brief-index{display:block;border-right:0;border-bottom:2px solid #24435b}.brief-index-head{padding:13px 14px 10px}.brief-index-head p{display:none}.brief-list{display:flex;gap:0;overflow:auto;padding:0}.brief-group-label{display:none}.brief-link{min-width:210px;border-top:0;border-right:1px solid #bdcdbf;padding:9px 12px}.brief-link:last-child{border-bottom:0}.brief-reader{overflow:visible;padding:24px 18px 42px;background-size:24px 100%}.brief-reader-head{margin-bottom:19px}.brief-reader-head h2{font-size:25px}.brief-copy{font-size:13px}} +@media(max-width:620px){.brief-index-head,.brief-list{display:none}.brief-picker{display:block}.brief-reader{width:100%}} +@media(max-width:840px){.navs{display:none}.mode-menu{display:block}}@media(max-width:620px){.navs{display:grid}.mode-menu{display:none}}
-
Sidequestor

Sidequestor

Quest Control
Connecting
+
Sidequestor

Sidequestor

Quest Control
Connecting
Loading control state…
Worker detail

Quest Control

Inspect state, review work, or steer a quest.

Agent Trail

Meaningful activity across active quests.
+
Select a briefing to read it.

Approval history

Dispatch history

In your own words

Create a quest

Say what you want handled and who it involves. The agent reads it on the next triage tick and works out how to run it from there.

Picked up on a later triage tick, usually within a minute or two — longer if the loop is busy or paused.

Priority is recorded on the quest and shown in the UI. Nothing in the loop schedules or orders by it today.

@@ -69,53 +74,87 @@