mirror of
https://github.com/NeuralNomadsAI/CodeNomad.git
synced 2026-11-05 21:50:36 +00:00
61 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
990f6ce06b
|
feat(missions): join launch criteria with or/if/else connectors and fallbacks (#1291)
## Summary Recurring Missions now launch from one ordered list of criteria (schedules and events). Small automatic connectors between the cards show how they combine; dragging the cards changes the meaning. - **or**: two schedules, or two events, side by side. - **if**: events after a schedule group are the conditions of every schedule in that group. They never trigger on their own. - **else**: a schedule group after events is a fallback for everything above it. At each of its slots it launches only if the criteria above it (or **Run now**) started no passage since that schedule's previous slot. Events after a fallback condition only that fallback. - Events at the top launch on each new occurrence (checked every 15 minutes). Schedules at the top launch at their times. An empty list is a one-time Mission. Example: *new commits, 18:00, new messages, 20:00* launches on each commit; else at 18:00 if new messages arrived; else at 20:00, unconditionally. ## Implementation - **One shared parser.** `missionCriteriaStructure` (`missions/mission-launch-kind.ts`) gives connectors, roles and per-schedule plans (`conditions`, `fallback`, `group`). The codec, engine, editor and list summary all use it. - **Fallback periods.** - A fallback's period starts at its previous slot (`lastDueAt`). Before its first slot, it starts when its calendar was armed (`armedAt`). - After an edit, an unchanged schedule keeps its origin, even when the calendar is re-armed. A changed or added schedule starts at its new anchor. - A passage counts by its slot once admitted, even if it fails later. A pending passage not yet admitted makes the decision uncertain, so the fallback does not launch. - **Grouped launches are decided at the slot.** Wakes that arrive while a passage runs are judged by a source read made then, measured against the references the running passage will consume. Settlement launches at most one grouped passage, and its `deferred` origin records the calendars it came from. - **Sources.** Source snapshots report each changed event key, so a schedule group reads only its own conditions. A skipped slot consumes no source. Trigger marks are removed. - **Editor.** - Cards still reorder with solid-dnd drag and with Alt+Up/Down on the grip, which announces the new position in a live region. - While dragging, connectors follow the projected order in a fixed-height line, so the layout does not shift. - The **i** tip explains if/else in two sentences and links to the OpenCode login-start setting. - **Stored legacy policies.** They keep their bytes and behaviour: multi-calendar conditions, stored end dates, and the single launch of an event-only policy until the schedule is edited. ## Validation - **Server Missions suites:** 1750 of 1752 pass, 1 skipped. The other failure is `mission-local-sources`, the known git timeout under parallel load; it passes alone (10/10). New tests cover: - the parser table: E,E / H,H,E,E / E,H,E,H / H,E,H / E,H,H,E,E,H; - the approved chain over six days, and fallbacks on consecutive days; - Run now and failed passages that were admitted; uncertain admission; - grouped decisions frozen at the slot; - calendar arming and re-arming after edits, through the owner; - legacy multi-calendar compatibility. - **Browser (mission, preferences, settings, service, provider-accounts, websearch and opencode-login-start files):** 571 of 572 pass. The failure is `mission-task-pages`, a timing flake under load; it passes alone (20/20). Coverage includes pointer and keyboard drags, connectors during and after a drop, focus, and no reflow. - UI and server typechecks, UI build, missions plugin bundle, UI unit and locale tests (100/100), help structure (31/31) and `git diff --check` all pass. - Dev docs (`MISSIONS.md`, `MISSIONS_RECURRING.md`) and the Missions help in ten languages are updated. ## Notes - A wake whose source read fails at its slot stays unconsumed and is decided by the next read, like any retried wake. - While a card is being dragged, it can visually cover the connector of the card below it. |
||
|
|
8195da0f33
|
fix(missions): disconnect Missions from service supervision so Play and Resume always run (#1285)
## Symptoms
Reported on the latest dev build (last healthy:
`1.0.1-dev-20261101-fb44655c`):
1. Starting a one-time Mission fails with **"Action not yet confirmed.
Check status before retrying."**, and nothing confirms the outcome.
2. After a Pause, **Resume** tries to resume but appears to hang.
## Cause
#1259 (
|
||
|
|
a579403b70
|
fix(missions): keep Scheduling stable while it revalidates (#1272)
## Summary Scheduling in the Mission editor no longer flickers while it revalidates, and it no longer shows the OpenCode configuration file path. ## Changes - `mission-editor-schedule.ts`: a single memoized owner (view, client, native generation) decides when Scheduling resets; capability and document reads are separate, started outside reactive tracking, fenced by owner and visibility, and coalesced to one active read plus at most one trailing read. Task changes, transcript releases and instance updates no longer trigger reads; config, plugin and reconnect events still do. Hiding the panel aborts the read; showing it again rereads. No polling, and the global generation getter is unchanged. - During a reread the form keeps its content, enabled state, geometry, focus and open select; the existing update icon in the Missions toolbar is the only progress cue (`mission-editor.tsx`, `MissionControl.tsx`). - `mission-scheduling.tsx`: removes the configuration path line. The shared i18n key stays because the subagent depth setting still uses it. - Writes still reread the document before sending; read errors stay visible with their refresh action. - Docs: `dev-docs/MISSIONS.md` and the Missions help page in all ten locales. ## Validation - New browser regression `mission-scheduling-stability.test.ts` fails on the base (4 capability reads on open) and passes with the fix. - Browser: 102/102 across mission-scheduling-stability, mission-control, mission-editor-scheduling, mission-scheduling, mission-preparation and mission-schedule-list. - Unit (`--conditions=browser`): 113/113 targeted; UI typecheck, `scripts/test-help.mjs` and `git diff --check` clean. - Independent QA also exercised a fence with abort disabled, hide → owner change → show, Edit mode document reread and a parent operation during a read: all passing, no P0–P2. ## Known follow-ups (minor) - After a failed document read, Retry briefly replaces the error message with the loading message. - `props.directory` is read once at creation; not reachable from Mission Control today. |
||
|
|
6b8baf2320
|
feat(missions): bound current recurring passage display reads (#1269)
Complete bounded display reads for daily and unified-scheduled current
passages. Project inventory is already merged; this branch includes the
Missions UX correction from #1271.
## Contract
- `recurrenceCurrent` reads only exact owner/project/Location,
schedule/passage/execution identity, frozen-source digest and separate
owner revision. Scheduled sources include configuration/time
generations.
- `recurrenceCurrentPage` validates that source, one accepted isolated
range, creation proof, coordinator/profiles/task mode and a fresh owner
identity before publication.
- Reuse ordinary bounded headers, task/field/activity windows and exact
body selectors: **64 KiB UTF-8 responses**, **16 KiB requests**, bounded
assembly, original deadlines/cancellation and exact digests.
- Both passage kinds use scoped inventories for tracking, graph, Needs
you, fields and central readers. Immutable report selectors retain
historical evidence; unrelated updates retain unchanged task/body
caches.
- Central promotion checks stale state, marks, exact expected digest and
the pending value's original live operation. Demand contraction, late
completion and a later owner cannot resurrect an unvalidated pending
body.
- Internal/admission/mutation maps and schedule controls retain
authoritative full reads. Display reads grant no replay/input/control
authority.
- No cumulative 32-MiB display-source cliff or arbitrary task cap. One
accepted journal range is still reconstructed per native page; this is
bounded display transport, not a constant-memory journal redesign.
## Preserved presentation
The scoped consumers retain #1271's continuous scroll-loading task tree,
one manual fallback, info tooltips, stable refresh geometry and existing
top-right indicator. They also preserve the incoming active/focus policy
and schedule publication/edit barriers. The indicator includes
actor/task reads owned by the selected passage. No new polling or
redesign.
## Measurements
Accepted source qualification includes 864 tasks / 17,518,776 bytes and
1,728 tasks / 35,037,353 bytes, with approximately 2.4-KB logical
headers. All task rows and exact briefs are reachable. Fresh actual
UI/HTTP flows for both owners use 180 tasks, no initial prose fan-out,
scroll continuation, an off-page native request, exact/raw-field Retry
and unchanged-cache checks; maximum response sizes are 63,153 and 63,209
bytes.
## Qualification
- Fresh integrated tree: UI/server types, production UI build, native
Missions plugin build, **255 focused UI contracts**, **31
current-source/HTTP/passage tests** and **31 help checks** pass.
- **141 affected browser cases covered across completed corrected
runs.** Initial 16-file run: 138 passed, three test-assertion failures.
Two-file rerun: 13 passed, two current-selector assertion failures.
Final exact daily/scheduled rerun: 2 passed. Corrections wait for growth
beyond actual initial rows, inspect the real nested task wire selector
and require honest fresh marks continuation while keeping
geometry/held-label checks. No failing product assertion was skipped.
- Independent R4 at exact `
|
||
|
|
fb4e244b5a
|
fix(missions): stabilize refresh and simplify task continuation (#1271)
## Purpose Repair Missions panel presentation regressions introduced by bounded display reads. Scaling approval did not authorize technical coverage paragraphs, loading-only body blocks or per-gap continuation controls. ## Changes - Keep loaded tasks and pins in one continuous dependency tree. A single trailing sentinel loads the earliest missing bounded window on scroll, with one standard manual fallback. - Keep coverage/search explanations and explicitly scoped whole-mission attention counts behind the existing focusable/touch-capable info tooltip pattern. - Preserve an existing continuation/info slot across passive refresh cycles and retain stable button labels. Loading alone never mounts a body block. - Use the existing top-right indicator for inventory, selected header, task, daily/current-passage and unified schedule reads. - Retain explicit Retry, last-good rows, provenance/sequence fences, off-page Needs you and recovery actions. Backend limits, ownership checks and transport budgets are unchanged. - Update the Missions help in all ten languages. ## Validation - UI typecheck and production build. - 29 store/task-page/native-event unit checks. - Real-component browser checks for panel geometry, long/short single-scroller layouts, dependency rails, held event-driven refreshes, schedule-only background reads, 400-task scroll loading, failed continuation Retry and sliced transport/reconnect fencing. - 31 help checks. - Screenshots inspected before/during/after held refreshes and on a large mission. The review's intermediate-page counterexample is covered: with 350 of 400 rows loaded and all 300 blocked tasks visible, the tooltip states only whole-mission counts. It never infers an unread-attention count from unrelated ordinary-window coverage, and no extra attention scan is issued. Before updating from dev, affected browser runs covered 93 distinct cases and independent review closed the intermediate-count finding with a fresh exact regression. The merge resolution retains upstream active/focus and schedule-publication guards and adds inactive-sentinel and held-keyboard regressions. UI typecheck/build, 110 units and 31 help checks pass on the merged tree; the final task-page file passes 20/20 and host file 18/18. Two incoming unfocused-window tests stop at a Windows headless Chromium focus setup precondition, reproduced on two blank pages without CodeNomad. Their tests stay enabled and unchanged. The final three-file run is 44 passed, 2 failed preconditions; no full-green-browser-suite claim or blanket waiver is made. Detailed English qualification and merge reviews are in the comments. No live mission controls, shared daemon restart, borrowed dependencies or worktree cleanup are used by this qualification. The window used by the user is not replaced by these fixture builds. ## Merge gate The user explicitly authorized resolving the conflicts and merging #1271. PR #1269 remains suspended and separate; this correction does not approve or merge its current-passage work. |
||
|
|
f9a97d8a51
|
feat(integrations): attach native applications and manage plugin sources
Deliver authenticated native application attachment, host installation, binding refresh and revision-fenced plugin source management. Removal warns and confirms possible disruption without coordinating active conversations; local detach remains explicit and uncertain mutations are never replayed automatically. Integrate accessibility and confirmed schedule-publication corrections, browser-fixture repairs and ownership of readiness writes during shutdown. Preserve the feature branch for the stacked integration-management work. Admin squash merge explicitly requested by the user before all remaining browser and build checks finished. Targeted and native validation evidence is retained in the PR; this merge does not constitute complete CI, extension, platform or manual pilot acceptance. Remaining qualification continues separately. |
||
|
|
8423db502f
|
feat(missions): adopt bounded panel inventory and selected headers (#1264)
Main **bounded-inventory UI adoption** following #1246, #1249 and #1250. The live panel now uses `createMissionPanelInventory` → `fetchMissionPanel`; public `fetchMissions` fan-out is removed. ## Contract and UI - Independently verifiable 50-row directory/field windows: echoed `{order,offset,end,total}`, window content digest, collection-order continuation fence. - Inventory summaries carry actor counts. A selected header excludes whole actor/receipt/task collections; those are separately paged. Native-scheduler `document`, cleanup `documentPending` and history `requestID` survive. - Initial durable `finishHighWater` is captured before discovery and seeds once: later-discovered old completions stay seen; new completions above the fixed baseline stay unseen, including deletion/purge counter gaps. - Explicit Show more/Retry for summaries, cleanup, fields, marks and actors; loaded-only search. Selected/reader identities work outside the first inventory page and do not wait for stalled discovery. - Last-good display, owner/generation/page-revision fencing, failed-middle Retry, no automatic changed-cursor restart. Separate actor observations never become atomic whole-mission absence or recovery authority. - Exact cleanup retry/lost-ACK identity preserved. Snapshot/admission/internal/mutation authority stays full. No mass snapshot fallback. - All ten help locales updated. Every response stays within **64 KiB**, request within **16 KiB**; logical reconstruction has explicit budgets/deadlines before allocation and publication. Native per-Location lists and producer-sized enumeration/hash costs remain disclosed. ## Measurements - 50/120 summary rows: 11,915 bytes, one transfer, zero warm range/map reads. - Valid 601-actor selected header: 3,226 bytes. - Explicit actor continuation: 160 root observations, largest measured body 61,471 bytes. - All 2,101 pending cleanups reachable; initial finish baseline remains fixed during new completions. - Notes-only revision: one wanted header, no task transfer or expanded-task reread. ## Qualification fixes included - Directory-only project Location omitted `workspaceID:undefined`, which strict document CAS rejects; adapter now omits only an absent optional field. Regression verifies directory-only and supplied-workspace placement without weakening validation. - Missing edit still returns mission-not-found/404. - Native document-cleanup receipts/replay are checked exactly; safe help-fixture link teardown and a data favicon fix remain test-only. ## Validation and recovery disclosure Implementation recovered after another session's dependency-junction cleanup deleted the original worktree. Tracked changes came from saved Git state; nine new sources were reconstructed from 137 archived successful operations and nine read checkpoints. All sources were checkpointed, then qualified in a new independently installed worktree. Original/evidence/recovery checkpoint remain untouched. Passing: typechecks, real UI build, 1,322 UI tests, 1,381 server tests plus one optional skip, 615 latest-complete serial browser cases, 31 help checks, plugin rebuild and 21 isolated OpenCode 2.2.3 gates. Twelve negative controls caught/restored. This is not a claim that the initial browser sweep was entirely green: one extension-fixture activation failure passed two fresh full-file reruns unchanged. Test manifests are explicit/nonempty; no borrowed dependencies or implicit Node discovery. Qualification report/provenance retained by the coordinator. Current-passage compaction (slice 5) remains the next separate work; scheduler #1259 is already merged. |
||
|
|
a5efdefc8d
|
feat(missions): resume recurring Missions after OpenCode restarts with native schedules (#1259)
Closes #926. ## What changes Recurring Missions now survive OpenCode restarts (update, crash and relaunch, reboot) without a manual **Resume**, and the scheduler offers more than "once a day at a fixed time". - **Native schedules.** OpenCode's own scheduler (`experimental.schedules`, OpenCode 2.2.3+) owns the calendar; Missions decides whether a passage may run (CAS admission, no duplicates, Pause/Stop, errors and uncertain states stay manual). A dedicated technical conversation with a local no-op model carries the wake, so wakes, titles and compaction make no provider HTTP requests. - **Richer scheduling**, in one collapsed **Scheduling** section of the Create/Edit editor (default: once, immediately, no condition): date, delay, interval (≥15 min), daily, chosen weekdays, up to eight times a day, day of month, native cron; optional end date/duration and a maximum number of launched passages; five event conditions (another schedule succeeds, a one-time Mission completes, new local commits, a new message in a followed conversation, explicit files change). Up to five upcoming dates are previewed in the saved IANA zone. - **Created paused, started with Play.** Everything is editable before the first launch; after it, edits follow the approved matrix (future parameters between passages, objective/notes journaled during a passage). - **Keep OpenCode running.** A Preferences setting supervises the shared OpenCode service through the OS (Windows Task Scheduler with a periodic recovery task, macOS LaunchAgent, Linux systemd user unit with lingering, WSL from Windows). ON by default at the first Play unless OFF was saved; OFF removes supervision without stopping OpenCode; a live service is never killed or duplicated. - **Existing daily schedules** keep their path and controls; no migration. - Help (English and nine translations) and dev-docs are updated. ## Validation - Typechecks (4 workspaces); server Missions/host-lifetime suites; UI unit tests 1945/1945; help 31/31 and build. - Real isolated OpenCode services (2.2.5, minimum 2.2.3): resting schedule resumes after service restart with no client and no Resume; 32 real passages with history/quota/phase preserved; concurrent wakes admit once; DST and catch-up for every frequency type; forced local auto-compaction and restart during maintenance with zero provider HTTP; real upgrade 2.2.3 → 2.2.5. - OS supervision on Windows, WSL, Ubuntu VM (two real reboots, healthy before login; OFF without a session) and macOS VM (Aqua session, crashes, OFF/adoption). On the final bundle: two real periodic Windows recovery relaunches; WSL OFF/Retry on a live HTTP 500 service keeps the same process. - Windows Electron and Tauri packages run in private profiles: paused creation, nine frequencies, preview. - Full browser suite: 956/957 in the last tier; the failure is `directory-browser.test.ts` (folder picker timeout), unchanged by this branch, and that file passes 20/20 on rerun. Earlier dev-baseline runs on the same machine also lost module loads to `net::ERR_NO_BUFFER_SPACE`. ## Known limits (not qualified in this PR) - macOS reboot followed by a real login, and a Windows host reboot/logon (needs a disposable bench). - Real screen-reader output (keyboard, ARIA and RTL checks pass). - Linux/macOS desktop packages. - OpenCode's background *pre*-compaction never applies to the technical wake conversations: their local model reports zero tokens, below the native minimum. Their automatic local compaction is qualified. ## Follow-up Integration with native `/goal` comes in a separate PR. |
||
|
|
dc71b88f50
|
feat(missions): consume byte-bounded task pages in the panel (#1250)
**Task-read UI adoption only**, a coherent first part of 4b.3. This does **not** remove the old list fan-out yet. ## Change - Selected task/attention windows and named task/report targets use `POST /missions/panel-page` with `source: "tasks"`. - Browser consumer bounds actual response bytes before parsing (64 KiB), validates the envelope and immutable selector, pins digest/length/provenance, and checks the final collection hash before publishing complete rows. Requests fit 16 KiB. - Secondary read budget and 30-second logical deadline. No legacy task GET fallback, automatic cursor restart or mutation. - Last-good rows/toolbars survive failures; changed content is distinct and explicit Retry starts at slice zero. - Store fencing covers queued ownership, project/client generations, reconnect/plugin invalidation and stale publication. - Ten-locale strings/help plus contract tests wired into CI. ## Validation - Eleven caught red checks: byte/digest/length/header validation, stale reads, reconnect, parked failures, changed messaging, queued ownership, unwanted revision reads and immutable selectors. - Fixture: 151 tasks, four logical windows, seven transfers, maximum 65,536 bytes; zero legacy task GETs/mutations. - Typechecks, actual UI production build, 1,865 UI/contract tests, 1,520 server tests, 456 serial browser cases and 31 help tests pass. - No server product changes; plugin/native qualification not rerun. ## Remaining 4b work Picker summary discovery, selected/wanted maps, activity/marks and cleanup display still use `fetchMissions`; exact cleanup reconciliation still uses `fetchMission`. Complete-inventory completion seeding is unchanged. Summary/cleanup windows, actor/activity windows and partial-inventory high-water remain unfinished. Passage slice 5 still waits for #926. #926 scheduling/document/passage and editor callback contracts, `document`, `documentPending` and history `requestID` remain unchanged. Only the internal task-fetch callback gains `{projectID, current, signal}` and failures may carry a reason. |
||
|
|
fb44655cdd
|
fix(missions): enable General delegation with project depth overrides (#1217)
Setting a project subagent depth did not enable nested delegation for OpenCode's General agent, whose built-in permission still denies the subagent tool. Treat a project depth override as the user's explicit request to enable delegation up to that depth. Saving any override, including 0 and 1, writes one CodeNomad-marked allow rule in the project's existing General config block. Keep OpenCode's depth semantics: 0 disables delegation and 1 prevents further nesting. The Inherit button and an empty field remove the override and marked rules, preserving unmarked user permissions and pre-existing containers. Explain the project rule's precedence and display OpenCode's resolved General permission state. If a legacy config cannot safely accept the rule, save the depth and explicitly warn that General's permission needs manual adjustment. Use OpenCode's winning agents/mode/agent block rather than creating a native block that shadows legacy prompts, limits or edit/shell denials. Keep JSONC comments and formatting, follow the winning block when config changes and record ownership of created containers for safe removal. No checkbox or admission/freshness/frontmatter-scanning machinery is needed. The documentation and help in ten locales explain the behavior and cite upstream anomalyco/opencode#60961. Validation covers native and legacy blocks, precedence, idempotence, marked-rule migration/removal, pre-existing empty containers, partial permissions and the explicit warning. Server, browser, i18n, help and typechecks pass. An isolated OpenCode 2.2.5 run verifies depth 2 nesting, depth 1 and 0 limits, preservation of legacy General settings and restoration after Inherit. Six review rounds resolved all actionable findings; the rebased patches are unchanged and the complete CI is green at d7cf2d35, including desktop builds. |
||
|
|
fe1506abc3
|
feat(missions): add bounded panel activity and attention reads (#1249)
Server-only **4b.2** of bounded Missions reads, following #1246. No UI conversion or user-visible behavior change yet. ## New sources on `panelPage` ```ts { source: "activity", missionID, actorOffset?, actorOrder?, cursor? } { source: "marks", missionOffset?, missionOrder?, cursor? } ``` - Activity: one named mission range, up to 160 actor roots. - Marks: up to 16 indexed mission contexts × 10 actors, sharing 160 roots. - RPC returns persisted `phase: "context"`; the owning HTTP backend validates it and observes native state outside the append lock, returning `phase: "observed"` with `observedAt`. - Rows carry context, honest coverage, real Form/permission requests and native activity. Actor windows make off-page requests reachable. - Range damage, graceful Pause/Interrupt now, retry identities and report-owed evidence are preserved. - Existing byte limits remain: **64 KiB per response**, **16 KiB per request**. Continuations re-observe and refuse changed content. No retained observation cache or automatic restart. Internal/authoritative reads, existing UI APIs and #926 metadata stay unchanged. Shared owed-work/task-attribution rules are reused by compact maps and projections. ## Measurements and validation - Warm marks across 50 missions: zero source scans. - A 31-root off-page window: 31 root GETs, 32 child lists, 32 inbox reads; one shared read each of active/Shells/Forms/permissions. - 800 exact requests: 22 transfers, maximum 65,536 bytes. - Thirteen red checks cover budgets, scope, coverage, ownership, saved Location, positive attention, uncertainty, digest, append lock, ordering, damage and receipt evidence. - Passing: typechecks; 1,507 server tests; 1,845 UI units; help; plugin build; isolated OpenCode 2.2.3 qualification. One existing environment-gated skip. No browser runs for this opt-in server-only surface. ## Remaining work UI adoption with per-window provenance, parked Retry and last-good display. Partial evidence must never become whole-mission absence or recovery authority. Native request lists still arrive whole per Location, and re-observation/source serialization can be expensive; busy sources may refuse continuations repeatedly. |
||
|
|
2b57a67df2
|
feat(missions): add byte-bounded panel page reads (#1246)
First server-contract slice of 4b, following #1241. **This PR adds an opt-in byte-bounded read; the UI still uses its existing APIs. Panel-level boundedness is not complete yet.** ## Contract - `panelPage` RPC and `POST /api/workspaces/:id/missions/panel-page`. - Responses fit **64 KiB of serialized UTF-8 JSON**, envelopes and continuations included; requests fit **16 KiB**. - Sources: summary/cleanup hints, one compact mission/root field, or an existing task selector. - At most 50 exact row pieces per response. Oversized rows are sliced as exact JSON text, never silently truncated. - Digest-bound cursors refuse changed collections. Partial rows prove neither absence nor mutation state. - Directory reads return no maps; mission/task reads use the named range only. No legacy-list/snapshot fallback. - Existing APIs and full authoritative/internal reads are unchanged. Future #926 metadata (`document`, `documentPending`, `requestID`) survives serialization. ## Measurements - 400 escaped/nested rows including a 5,000-actor row: recovered exactly in 95 transfers, maximum 65,536 bytes. - Real journal: 300 live missions, 300 pending cleanups, 300 task actors. - Directory: 9 summary + 6 cleanup transfers, no warm storage scans. - Actors: 7 transfers, maximum 44,605 bytes, one named-range observation per transfer. ## Validation Eight red checks cover byte accounting, cursor integrity, source scope, selector bounds and forbidden fan-out. Passing: typechecks; 1,498 server tests; 1,845 UI units; 409 serial browser cases; help; plugin build; isolated OpenCode 2.2.3 native gates. One existing environment-gated test skipped. ## Remaining 4b work Bounded live activity/attention for off-page actors and nonselected missions, UI adoption with per-page caches/discovery/completion seeding/cleanup retries, then removal of old list fan-out. Each transfer currently serializes/hashes its source collection afresh, with linear per-transfer CPU cost. |
||
|
|
abb14f83c6
|
feat(missions): page a mission's tasks in the panel (#1241)
Slice 4a of bounded Missions reads (#1220's R4; slices 1–3 are #1229, #1231 and #1237). **A compact map now holds one page of tasks, every row has a size budget, and every task stays reachable through a new paged tasks read.** The commit message has the full design. ## Change - **Row budgets:** - 32 KiB per task row, 8 KiB per report row, 4 KiB per plan change. - Observations leave the panel map. - A task keeps only its newest late report, plus a count. - A plan change's added, retired and rewired task lists become content bodies. - **What a map carries:** - the first 50 tasks of the shared display order; - up to 20 pinned rows (pending notifications, blockages, recovery candidates, the newest reports' tasks, briefing sources); - `taskPage`: an order digest, the window, totals and status counts; - `actorEvidence`, computed over every task. - **New `tasks` RPC and `GET …/missions/:id/tasks`:** - return a window of tasks, or tasks named by key, ID or report; - refuse a stale order with `order-changed`; - for a plugin without `tasks`, page through its single-mission read. - **UI:** - a page cache per mission; - tree gaps with Show more, and graph stubs for edges to tasks not shown; - reader targets that aren't loaded are read by key or ID before opening; - Show all for earlier results; - 13 strings in ten locales, and the help pages updated. - **Unchanged:** authoritative reads and recurring passage maps stay full. ## Payload (2,400 tasks × 20,000-character briefs, 1,224-task rewire) | Read | Bytes | |---|---| | Full map | 49,431,307 | | List | 49,220 | | Map | 48,862 | | Each of 48 pages | ≤ 38,780 | Before this PR, one task with 1,997 observations weighed 4,617,922 B; it now weighs 4,836 B. One with 100 late reports went from 173,666 B to 2,830 B. **Next (4b):** full maps only for the selection and wanted missions, paged summary rows and cleanups, and bounded actor-scale arrays. ## Validation - **Server:** page traversal reaches every task exactly once, in display order. Also covered: the `order-changed` refusal, the row and report caps, pins, taskless reports, and the route fallback. - **Store:** merging, reads recorded before fetching, coalescing, and `clear` fences. - **Browser:** tree gaps, graph stubs, Show more, re-measured rails, Needs you for a task outside the page, a dependency loaded by key, Show all, and reader targets read by ID. - **Red checks:** each new test fails with its fix reverted. - **Passing:** - server and UI typecheck; - 1,312 server tests; - 1,827 UI units; - all 41 `mission*` browser files plus 3 panel files; - `test:help`; - `test-missions-native.mjs` on isolated OpenCode 2.2.3. |
||
|
|
da8607d49e
|
feat(missions): send compact mission maps to the panel (#1237)
Slice 3 of 5 of bounded Missions reads (#1220's R4; #1229 added content reads, #1231 moved the readers onto them). **The panel's `list`/`mission` reads now return compact maps: the review's 2,400-task case lists in 1.09 MB instead of 48.7 MB.** ## Change - `compactMissionMap`, on copies at serialization time: - drops every `MISSION_BODY_FIELDS` body and keeps its `bodies` descriptor; - keeps bounded excerpts: the objective label, report rows (240-point excerpt plus `decidedBy`), plan-change reasons and the independent explanation; - reduces the briefing to its identity; - carries unknown fields through. - **Full, untouched:** snapshot, admission, mutation results, `mission.inspect`, current passages, and the maps `MissionControl` reads internally. A test fails if any of them loses a body. - **Older plugins:** - the route compacts full maps; - a plugin without `list` answers `plugin-unavailable` (the mass-loading snapshot fallback is gone); - without content reads, the ordinary read failure shows with Retry; - the admission route never compacts. - **UI:** - body presence comes from descriptors, and labels from excerpts; - the slice-2 fallbacks are removed; - new `MissionPanelMap` types. - **Visible:** Plan-change reasons are cut to 240 characters in the list, and the independent-mode tooltip shows an excerpt. The readers keep the full text. No help page describes either. - **Coordination:** coordinated with the parallel #926 branch, whose scheduling editor already reads through descriptors and the content route. Its added fields carry through. ## Payload (2,400 tasks × 20,000-character briefs) | Read | Bytes | |---|---| | Full list (review probe) | 48,673,523 | | **Compact list** | **1,093,511** | The remaining growth is the row count; slice 4 pages it. ## Validation - **New tests:** - `mission-compact-map.test.ts`: shape, idempotence, unknown fields, payload, and authoritative maps kept full; - route tests for the older-plugin cases; - store, bodies and display units; - new reader and intents browser cases. - **Red checks:** each test fails with its fix reverted. - **Passing:** - server and UI typecheck; - 837 missions tests, 484 routes tests and 164 opencode-missions tests; - 1,818 UI units; - all 40 `mission*` browser files and 8 panel files; - `test:help`; - `test-missions-native.mjs` on isolated OpenCode 2.2.3. |
||
|
|
edeafe586f
|
feat(missions): read one-time mission bodies through the content route (#1231)
Slice 2 of 5 of bounded Missions reads (#1220's R4; slice 1 is #1229). Readers stop rendering prose from the full maps and read bodies through the exact content route instead, so slice 3 can drop bodies from the panel's maps. ## Change - **Display cache** (`stores/mission-content-store.ts`, wired in `stores/mission-content.ts`): - **Key and reads:** keyed by workspace, project, mission and selector. One read runs at a time per body, every continuation is digest-pinned, and a body is never assembled from two digests. - **Refresh:** a newer map revision, a plugin reload, a reconnect or a new event stream marks shown bodies stale. The last good body stays shown until the newer value covers the page. - **Failures:** `content-changed` restarts from offset 0, at most 4 times. A failure keeps the display, with Retry. A late answer is aborted and never published. - **Memory:** unshown bodies are kept within 4 MiB and 128 entries, least recently used first. - **Budget:** requests use the shared background read budget, and nothing polls. - **Who reads through it:** - the overview, task, report, plan-change and briefing readers; - the editor's edit prefill, which stays read-only until both fields arrive, its authority still the opened revision; - Save as brief, which reads both fields when its name field opens and saves only from one revision; - the Compare and decision readouts. - **What a read loads:** only the prefix its page shows (`lib/mission-body-pages.ts`). Closed disclosures read nothing. - **Kept as before:** admission, mutations, lost-acknowledgement checks, Stop, Delete and recovery keep their authoritative reads. Admitted recurring passages keep their own path. - **Older plugin:** it answers `plugin-unavailable`, and readers show the map's own value. `MISSIONS.md` (Agent conventions) lists this fallback and every remaining map-prose use for slice 3 to remove. - **Visible:** "Reading…" until a first read lands, and a new "This content could not be read." line with Retry (10 locales). The help pages don't describe reader loading, so they need no change. ## Validation - **Store tests:** demand-sized reads, digest pinning, the last good display, the in-flight revision, late-answer fencing, `plugin-unavailable`, eviction and the page prefix rule. - **Browser test:** `mission-content-reader` reads a body slice by slice, and never shows a body changed between slices. - **Red checks:** each test fails with its fix reverted. - **Fixtures:** ten browser fixtures serve the content route through a shared helper, with assertions unchanged. - **Passing:** server and UI typecheck; 1,132 UI units; all 39 `mission*` browser files and 8 other panel files; 831 server missions tests; 647 route and opencode-missions tests; `test:help`. The browser runs are on a base with #1230. |
||
|
|
5bce23079b
|
feat(missions): read mission bodies exactly and bound tool results (#1229)
Slice 1 of 5 of bounded Missions reads: R4 of #1220, accepted for this follow-up series. Since #1220, a mission's content is bounded only by its 2,000 journal entries, so every body must be reachable through bounded, exact reads instead of whole maps. ## Series 1. **This PR:** an exact, digest-pinned content read (RPC and route), plus bounded mutation tool results. 2. UI readers, the editor, Save as brief and Compare/decision artifacts fetch bodies through the content route, behind a fenced display cache. Maps stay full. 3. Compact `list`/`mission` maps, with bodies replaced by `{ characters, digest }`; authoritative admission and mutation reads stay full. This covers the 48 MB panel case. 4. Paged compact task rows for the tree and graph, with omitted rows and edges shown and a way to read further. 5. The current passage: a compact projection plus content within its 32 MiB response and 256 KiB entry limits. This covers the 864-task case. ## Change - `MissionControl.content`, the `content` RPC and `GET /api/workspaces/:id/missions/:missionID/content` read exactly one body, from one range read of that mission. A body is: - a mission field or a task field; - a task's reports; - a report by ID, whether current, earlier or late, and never substituted; - a plan change by revision. - **Slicing and digests:** - Slices fit the 100,000-character inspect bound, in two-space JSON. - Each slice repeats its first answer's digest and is refused (`content-changed`) when the value differs. - Unrelated revisions don't invalidate a read. - **Route checks:** the route reuses the list's workspace, plugin and project checks. An older plugin answers `plugin-unavailable`, with no fallback read. - **Mutation tool results:** - Delegate, report, revise and briefing results fit the same bound. - An oversized frontier or assignment prompt becomes an exact `mission.inspect` continuation. - `revise` pages its affected tasks. - The delegate tool instructs reading the prompt whole and never passing the marker. - **Not visible yet:** no UI consumer, and maps are unchanged. ## Validation - Content reads recover every field exactly across slices (escaped text, emoji, late/historical reports, plan changes) and refuse a changed value. The route refuses a foreign project. Mutation results stay bounded with exact continuations. - Each test fails with its fix reverted. - Server and UI typecheck; 1,278 server missions/opencode-missions/mission route tests; `build:missions`; `test-missions-native.mjs` on isolated OpenCode 2.2.3 (20 gates). - Before the rebase, the author also ran all 37 `mission*` browser files, `automation-forms`, the UI units and `test:help`. |
||
|
|
2e585a7e83
|
feat(missions): lift the per-mission actor and task caps (#1220)
Step 6 of 6 toward removing the Missions limits (#1167 follow-up), after per-mission capacity (#1178) and panel summaries (#1180). **Each mission is now bounded only by its own 2,000-entry journal; the 8-actor and 96-task caps are gone.** ## Change - **Capacity:** the fixed deletion reserve becomes `2 x (1 + actors)` inside the 2,000 entries, so one Stop and then the deletion always fit (actors join only through ordinary writes, which keep it; a Stop keeps the deletion's share). 2,000 is both the write and the read bound of a range. - **Control:** Stop/Pause/Play in waves of 8 actors. - **Validation:** actor/task caps removed from the reducer, parser, schemas and tool inputs. The other bounds are now natural ones: - dependency updates must each name a distinct task; - control and cleanup targets are bounded by the receipts they need; - recurring passages lose their 8/96 and 32-target limits; - completed recurring control records drop their target list. - **Reads:** - `mission.inspect` pages maps over 100,000 characters (`taskOffset`, with page actors); - the actor context lists at most 32 open assignments and points to inspect for the rest; - activity keeps its 160-actor budget but prioritizes actors with open requests, then open work, and projects a `partial` family instead of giving up. - **UI:** keyed task tree/graph lookups; saved toggles keep the newest that fit; seen completions use a watermark on the new `finishedAt` plus a bounded list (any number tracked; a shown completion never reads as unseen). - Existing mission data is negligible: no legacy handling by design. Docs describe the remaining budgets. ## Validation - `mission-scale.test.ts`: a 40-actor, 500-task mission through every lifecycle step, then a full mission is stopped and deleted, with schema-checked RPC outputs and inspect paging. - Focused tests for capacity, inspect-view, activity priority, attention, seen-store overflow and reload, recurring control compaction, the current-passage route and dependency uniqueness. Each fails with its fix reverted. - Server and UI typecheck; 790 server missions tests; 451 route/plugin tests; 221 UI units; all 36 mission browser files; `test:help`; `test-missions-native.mjs` on isolated OpenCode 2.2.3, including a 12-actor / 107-task stage. - `presence-native` (opt-in, needs a CLI path) fails identically on unmodified dev. |
||
|
|
acadef7e2c
|
test(missions): qualify presence loss through the RPC it actually controls (#1224)
## Problem The opt-in `presence-native.test.ts` waits for the `mission_*` tools to leave the native tool list after the backend's presence lease ends. That never happens: tools register **once at plugin load, independently of presence** (`desktop-plugin.ts`), so scheduled passages keep them (`MISSIONS_RECURRING.md`). The test fails identically on OpenCode 2.2.0, 2.2.1, 2.2.2 and 2.2.3, and on every dev commit since registration moved to plugin load; it is unrelated to #1209. `MISSIONS.md` still said tools register through presence. ## Change (test and docs only) - The test now observes what presence actually controls: - the typed `codenomad.missions` RPC unregisters (only `rpc.unavailable`/`rpc.method_not_found` count as absent) and re-registers exactly once when the lease returns; - the tools stay registered; - the ordinary route refuses with the tools' own message; - the real provider request carries no `mission_*` tool without presence, and carries `mission_inspect` with it. - `MISSIONS.md`: the actual registration, and how to run the opt-in test. ## User impact None. Without presence, the context hook strips every `mission_*` tool from model requests and the executors refuse; the new test proves this natively. ## Validation - `presence-native` passes on isolated OpenCode 2.2.3, 2.2.2 and 2.2.0. - Red checks: it fails with presence retention always true, with the context hook keeping the tools, and with tools registered inside presence. - Server typecheck; 155 `opencode/missions` tests incl. missions-plugin/missions-reload; `build:missions`; `test-missions-native.mjs` on 2.2.3. |
||
|
|
b1e1618b6b
|
feat(missions): explain every playbook in help and a See details reader (#1226)
Playbooks were opaque: the Mission editor showed one sentence per playbook, and nothing told users which steps and roles a playbook runs, when they are asked something, what CodeNomad actually enforces, or which instructions the agents receive. A new help page, playbooks.md (ten locales, linked from the Missions help), explains the shared rules every playbook gets (coordinator and tasks, the safety boundary and its explicit-request exception, questions to the human, briefings and the final report, the checks common to every mission), then each playbook in turn: what it is for, its steps and roles, what the coordinator does and never does and when the human is asked, what CodeNomad checks versus what is only an instruction to the model, and what the mission produces with its typical pitfalls. Every statement was checked against the recipes and the server checks; decision tags and the Coordinator decision / Your answer labels are scoped to Native subagents. "See details" under the playbook description in the editor's Options opens a read-only view in the Mission reader, beside the editor: steps, roles with their localized labels, and, folded per recipient, the complete English texts each agent receives. They come from a new read-only route, GET /api/missions/playbooks?taskMode=, built by missionPlaybookDetails() from the real prompt builders (buildAssignmentPrompt, buildActorContext) with placeholders, so the view cannot drift from what agents get. The button is disabled while a creation request is in flight and opening the reader no longer cancels the editor's pending creation. A failed load offers Retry, and a saved playbook reader is restored, with unknown ids dropped. Tests: a drift guard comparing whole sent texts with the export for native and independent assignments, independent session context, and one-time and daily coordinators of every playbook, with a negative check and a test placing each question/safety rule on the right recipients; a route test; browser tests for See details (including a delayed response) and for opening it during a creation answered 200 and 400; view-state restoration. Server, UI unit, mission browser, help structure and help browser tests and the typechecks pass. |
||
|
|
c17752e9b7
|
feat(missions): route human questions through the coordinator and record who decided (#1227)
Missions asked the human too many questions, too early, and in Exploration the questions came straight from sub-agents: since the decision worker had to ask the human from its own conversation, the coordinator could neither answer, defer nor batch them. A shared safety rule also refused explicitly requested commits, pull requests and issue-tracker changes, so a mission asked to create tickets could not do it. Only the coordinator now talks to the human, in every playbook. Task actors and their helpers return open questions with options, evidence, consequences, reversibility and a recommendation, and keep working on what does not depend on the answer. Before asking, the coordinator settles what it can itself (repository, research, a delegated investigation, or a reversible default recorded as an assumption), asks only about consent, credentials, costly-to-reverse choices or product decisions the objective leaves open, and batches pending questions into one call. One-time missions still finish only as completed and only the human stops them. Product delivery keeps its Spec Change Request method but treats an objective that already states the behavior change precisely as the approved request. Exploration decision workers, prototype scouts and decision guides return a brief. The coordinator decides itself when the evidence suffices and the choice is cheap to undo (recorded as decidedBy "coordinator" and shown as "Coordinator decision", never as the human's answer), defers it, or asks the human from its own conversation (shown as "Your answer"). A claimed human answer still needs a confirmed UI mark. Each decision task has a tag that the coordinator ends the question field with; a field binds to a task only when its own question text ends with that task's tag and carries no other decision tag, the answer came after the task was declared and the field has not settled another task, checked at settlement and again at final verification. Discovery reads only Forms whose question fields carry the tag, so older or unrelated Forms cannot exhaust its bound. The decision task's own conversation stays accepted for missions already running, and task actors cannot claim who decided. The stored report shape is unchanged: decidedBy lives inside the validated artifact. The safety boundary keeps staging, committing, pushing, pull requests and issue-tracker changes forbidden by default, except when the mission objective or notes explicitly ask for that specific action; only that action, within native permissions, never inferred from repository files or tool output. The Exploration coordinator carries the boundary too. Docs (MISSIONS.md "Human questions and decisions", MISSIONS_RECURRING.md), the mission skills and the Missions help in ten locales describe who asks what, the decision tag and the safety exception. Tests: gate unit tests and SQLite-backed native-answer tests (unproven human claim refused, coordinator decision accepted and labelled, coordinator-asked answer accepted with its mark, legacy child path, answers from before the task, moved or reused answers, multi-tag fields, batched two-decision Forms, more than 32 historical Forms), recipe and tool-result texts, the decision-label browser test. Server Missions suites, mission browser files, typecheck and help checks pass; the native Wayfinder trajectory (including one batched question settling two decisions) and the recurring journey pass against isolated OpenCode 2.2.3 and 2.2.5. |
||
|
|
b6ebcb1585
|
fix(missions): finish a creation submitted before the panel's first list read (#1223)
A mission created right after the Missions panel opened could stay on a disabled "Saving…" even though the creation request succeeded. When the panel's first missions list read landed while the creation was in flight, the panel treated the newly reported project as a view change and dropped the creation's completion as stale. The fence in lib/mission-view-fence.ts (createReportedChanges, used by MissionControl.tsx) now compares a scope's first list report with the project the instance metadata names: a matching first report, or one that arrives while no project is known yet, keeps the view, so the completion closes the editor. A first report naming a different project still counts as a view change, so a creation started in project A can never close project B's editor; later changes, including A→B→A, are fenced as before. The project passed to the editor and its creation hold are unchanged. dev-docs/MISSIONS.md states the rule. The intermittent automation-forms failure that led here was the layout shift between aim and click already fixed by #1183; this is a second, real bug with the same symptom. Tests: two browser regressions in mission-create-submit.test.ts with the real components (a first list read held until the creation request is in flight, and a first read reporting another project), each failing without its fix; unit tests in mission-view-fence.test.ts for matching, contradicting and per-scope first reports. The mission editor, creation and panel browser suites (185 tests), the automation-forms file in repeated loops and the UI typecheck pass. |
||
|
|
00814426c6
|
fix(missions): scroll the creation body, keep Create outside it (#1213)
Follow-up to #1183, which merged with the sticky creation footer. That footer's second review left two P2s open: - **R3:** a stale-map or cleanup-unavailable banner rendered after the form moved Create by about 32 px during a native automation click. - **R4:** on short panels, the sticky footer covered the focused field or made Options unreachable. ## Change - **Layout:** while creating, the form fills the Missions panel as a column. Its **body is the only scroller**, and the footer sits below it, outside that scrollport, so native focus scrolling keeps the focused field visible. - **Banners** shown during creation are ordered above the form. They shrink the body and never move the footer; their text and roles are unchanged. - **Short panels** (the footer plus an 8rem body don't fit): the whole form scrolls in the panel, with the footer after it. This uses the existing short-layout helper, renamed `mission-panel-scroll.ts` and shared with the task tree, which re-reveals the focused element when the mode switches. - **Unaffected:** edit/delete forms and the Preferences depth editor. ## Validation - **automation-forms:** five held outcomes (three depth reads, a stale banner, cleanup unavailable) released during the real controller's click. Create's box is unchanged and exactly one POST is sent. - **mission-panel-host:** - A real-keyboard Tab pass at 1000x600 and 260x300, with Options and the brief prompt open, hit-tests every focused control. - At 220x120, Options opens and every control is reachable. - **Red checks:** the sticky CSS fails both banner cases (-32.5 px) and the focus checks; removing the fallback fails the short-panel checks. - **Passing on current dev:** UI typecheck; automation-forms (2 runs), mission-panel-host, mission-control, mission-panel-layout, mission-panel-geometry, mission-create-submit, mission-create-refusals, mission-editor-lifetime. |
||
|
|
33e97f4763
|
fix(missions): list mission models like the conversation selector, with their names (#1218)
The Model selectors of mission profiles (Settings → Missions defaults, Mission editor Options, Compare Model A/B) listed every catalog model as raw `provider/id`, unfiltered. They now show the same list and names as the conversation's model selector. The composer's catalog building moves unchanged into `lib/model-catalog.ts` (`buildProviderCatalog`, `listCatalogModels`), and preference filtering into `stores/model-picker.ts` (`preferredModelList`): hidden models are left out, favorites-only applies while a favorite remains, and models sort by provider then name. Mission controls reuse them, keep only tool-capable models, and group options by provider. Summaries show friendly names without their own catalog read (`knownModelName`), with names keyed by the workspace-root Location and connection generation so a fresher conversation catalog wins. A saved value the list leaves out stays selected under its name, or as "· Unavailable" when the selector cannot offer it; stored values keep the `providerID/id` format. A global `optgroup` rule styles provider groups in native selects. Help (ten locales) and MISSIONS.md describe the list. Tests cover the shared helpers, freshness and Location fences, and the real Settings, editor and Compare selectors, including hidden, favorite-filtered, unknown and tool-less saved values; three review rounds, the last with no findings. Rebased on #1219: typecheck, help checks and 77 mission/model browser tests pass. |
||
|
|
6058206699
|
feat(missions): let Pause finish the steps in progress, with Interrupt now (#1219)
A user reported that pausing a mission stopped it abruptly, mid-step; pausing by hand ("pause this session" queued to the coordinator and sub-agent) let the current step finish and resumed cleanly. Pause interrupted the whole native family at once.
Pause from the panel is now graceful (`graceful: true`): it records a paused control with no targets and no native side effect, so it settles at once. The paused context every actor reads at each model request asks it to finish only the step in progress, start no new work or sub-agents and end its turn; the plugin's context hook adds it for native descendants, walking ordinary sub-agents' ancestry (bounded, cached parent IDs) to their actor and re-reading a paused chain so only sessions still placed with the actor's admitted project and Location receive it. Dispatch blocking and report parking apply at once, as before.
While the activity projection observes a running, queued, background, Form or permission actor of a gracefully paused mission, the panel shows "Pausing: the steps in progress are finishing." and Interrupt now, which sends the existing immediate Pause (family interruption and receipts unchanged). An immediate Pause is valid from paused only after a graceful one. Play after a graceful Pause tells the coordinator that delegated work may have stopped early and should be continued, not restarted. The flag is carried by the route, RPC schema, plugin, journal (graceful means no targets), projection and UI intents; requests without it keep the immediate behavior.
MISSIONS.md, the Missions help (ten locales) and three UI strings (ten locales) are updated. Tests cover the lifecycle transitions and replay, the journal rule, the real plugin context hook for the coordinator and three levels of sub-agents (including moved sessions and warm caches), and the real panel for running and Form-waiting work. Three review rounds; the last had no findings.
|
||
|
|
3ff4dddd76
|
feat(missions): describe the selected playbook under its selector (#1216)
The Mission editor's Playbook selector showed only names; what each playbook does was documented only in the help. The selected playbook's one-sentence description now appears directly under the selector (referenced by its aria-describedby) and follows the selection. Five `missions.control.template.<id>.description` strings reuse each locale's help wording in all ten locales; the paragraph takes the editor's existing secondary text style. The Missions help (ten locales) and dev-docs/MISSIONS.md mention it. Validation: a browser test opens the real editor, checks the default description, its placement and fit, then Exploration and Compare two models; it fails without the component change. A unit test checks every locale has its own description for each playbook. Mission editor and automation browser tests (98/98), UI unit tests, help checks and the root typecheck pass. |
||
|
|
f3246e27c3
|
fix(settings): keep Store images once working in projects with their own experimental block (#1200)
## Problem
OpenCode reads `experimental.media_blob` from the whole `experimental`
object of the highest-priority configuration document that has one
(upstream: anomalyco/opencode#60410). A project
`.opencode/opencode.jsonc` holding only
`{"experimental":{"subagent_depth":8}}` — exactly what the Missions
depth editor writes — turned **Store images once** off in that project
even though the Global box was checked. Settings only showed a caption
("Another OpenCode configuration's experimental settings take precedence
here: images stay inside each message.").
## Change
- **Images** (`opencode/image-settings.ts`): when the selected
Location's own project document (the project write target) decides
`media_blob` through its existing `experimental` object, the snapshot
reports `mediaStorage.project`, the box shows the effective value with
no caption, and saving writes `media_blob` to the Global document
**and** to that project block. A block that disappeared after reading is
never recreated (conflict). Image generation is read per field and still
writes Global only. The caption remains for deciding documents CodeNomad
does not edit.
- **Subagent depth Inherit** (`opencode/subagent-depth-settings.ts`):
removes, in one comment-preserving edit, a project `experimental` object
left empty or holding only a `media_blob` equal to the value removal
exposes. That value counts only when certain (runtime declares
`media_blob`; the next native object belongs to the Global write target
and deep-equals its fresh document); otherwise the object stays, which
hides nothing new. `removeEmptyNativeObject` takes a removability
predicate; `samePath`/`maskedBy` move to
`opencode/experimental-layers.ts`.
- The depth editor writes only the depth. Predicting the inherited
object for a newly created project block needs OpenCode's full layer
order (explicit, managed, content, normalization, watcher lag) and stays
out of scope; the root cause is upstream (#60410), and Images now shows
and fixes the per-project value in one click.
- Docs: `dev-docs/SETTINGS.md`, `dev-docs/MISSIONS.md`, help
`providers.md` and `missions.md` in 10 locales.
## Validation
- `image-settings.test.ts`, `subagent-depth-settings.test.ts` (6/6):
project write, comment preservation, disappeared-block refusal, Inherit
cleanup with BOM/CRLF comments, kept block for lagging/normalized
entries, no Global object, missing capability
- `websearch-settings.test.ts` browser test (4/4); the new Images
assertion fails without the card change
- `scripts/test-subagent-depth-settings-native.mjs` (Global image
storage cleanup) and `scripts/test-image-settings-native.mjs
--expect-supported` against an isolated OpenCode 2.2.1 CLI
- `scripts/test-help.mjs` (31/31), server and UI typecheck, `git diff
--check`
|
||
|
|
1f5e04ed8f
|
fix(missions): keep the depth editor still while it reads (#1183)
The automation-forms test where an agent fills the real Mission editor failed intermittently at its Create step since the project nesting depth editor joined the creation Options. Opening Options starts a depth read that showed a "Saving..." status, then swapped it for the result; each swap pushed the footer down about 100 ms later, so native automation measured Create, the layout moved and the click landed on the footer, and nothing was submitted. The status also claimed a save during a read. The depth editor now shows its busy status only while confirming or saving; a read keeps the field read-only without announcing anything. In the panel, the creation form fills the height below the header and its footer sticks to the panel's bottom edge, so Create keeps its place while depth reads, catalogs or alerts resize the body. automation-forms answers the depth read like a native runtime and waits for the effective depth before clicking Create, as an agent would, and mission-control checks that a read only locks the field while a write announces Saving. The target test passed 8/8 including a cold first run (the old test failed 3/8 on dev), Create stayed in place across read timings and panel sizes, and every Missions, editor, preferences and drawer browser suite and the UI typecheck pass. |
||
|
|
a448bb1f6a
|
perf(missions): send summaries of closed missions to the panel (#1180)
Step 5 of 6 toward removing the Missions limits (#1167 follow-up), on
top of steps 1-4.
## Change
- Plugin RPCs `list` (summary row per mission from hints; full maps only
for open missions + up to 4 requested, each re-read from its range;
unchanged rows skipped via `summariesVersion`) and `mission` (one
range's map + its cleanups).
- `GET /missions?include=&summaries=` and `GET /missions/:missionID`;
fallback to the full `snapshot` when the plugin lacks the new reads
(`rpc.method_not_found`).
- UI store holds full maps + rows; picker/marks/completion seeding use
rows; detail, Needs you, task tree and readers use full maps of the
selection and reader target. Activity observes only the maps sent.
- Bridge routes that authorize native effects keep the full snapshot
(hint contract).
- Visible: a selected closed mission shows the loading line until its
map arrives; a closed unselected row's permission mark counts only its
declared actors' requests. No help change (nothing documented there).
## Read counts (10,000 history entries, 200 closed + 1 live)
`list()`: 0 full scans, live range only, 201 rows · with one closed
included: +50 entries, rows not resent · `mission(id)`: its 50 entries ·
`GET /missions`: 0 full scans after the index build; activity only for
open/included.
## Validation
- `mission-list.test.ts` (equivalence over random histories with
damage/purges, version skipping, staleness, label bound), read-budget
tests, route fallback tests, new `mission-summaries` browser test;
adapted fixtures keep assertions. Mechanism mutations each fail a test.
- Server and UI typecheck; 1,210 server tests; UI mission/i18n tests;
all 36 mission browser files; `test-missions-native.mjs` on isolated
OpenCode 2.2.0 and 2.1.4.
- `automation-forms.test.ts` fails identically on unmodified `
|
||
|
|
681b854865
|
feat(missions): bound journal capacity per mission, not per project (#1178)
Step 4 of 6 toward removing the Missions limits (#1167 follow-up), on top of steps 1-3 (#1175, #1176, #1177). ## Change - **Per-mission capacity:** each mission holds at most 2,000 ordinary entries plus its own 36-entry deletion reserve (2,036 per range). **No project-wide cap.** - Capacity checks and appends read only the target mission's range (no full scan). Safety decisions keep their full reads (accepted: their cost grows with history). - Reserve: only the mission's own tombstone and already reserved receipts; works on every storage. - Full project reads bounded per range (and stray entries together); memory = per-mission bound × missions, documented trade-off. - Purge is retention-only (20 newest settled deletions kept); capacity-pressure purge, creation pre-check and related re-checks removed. - Existing mission data is negligible (projects were capped at 20 missions, barely used): **no legacy, migration or rollback handling by design.** - Docs: `MISSIONS.md` safety limits; help sentence on freeing storage updated in 10 locales. ## Validation - Read-budget test at 10,000 history entries; project beyond one mission's bound fully works; capacity checks read only their range; full mission refuses ordinary entries/new Stop but deletes through its reserve (with and without removal). Each red with its guarantee mutated. - Native stage: 2,200 entries across 100 finished missions on an isolated runtime, then create/declare/readout/delete with retention purge. - Server and UI typecheck; 1,508 server tests; `test:help`; `test-missions-native.mjs` on isolated OpenCode 2.2.0 and 2.1.4. |
||
|
|
b38af07fbe
|
perf(missions): serve read-mostly paths from journal hints (#1177)
Step 3 of 6 toward removing the Missions limits (#1167 follow-up), on top of steps 1 (#1175) and 2 (#1176). ## Change Read-mostly paths take candidates from the activation's journal hints and re-read only those ranges: | Path | Before | After | |---|---|---| | Per-turn context hook | 2 full reads per model turn | 1 hinted membership read; 0 for sessions no mission names | | `inspect` (no start), list, `observeTask`, decision candidates | 1 full each | 0 full + candidate ranges | | Outbox listing | 1 full + 4 per item | 0 full + 1 range per open mission (per-item damage gates stay full) | | Presence retention | 1 full per interval | 0 when hints show open work; full read confirms an empty answer before release | | Change notifications after lifecycle/delete | 1 unused full read | 0 | Safety decisions keep authoritative full reads (creation, membership refusal, damage gates, delegation, reports, lifecycle, deletion, cleanup authority, purge, capacity). **Staleness (≤ 60 s, writers outside the generation protocol):** hook treats the coordinator as a non-member meanwhile (tools still select by full read); outbox only delays (deterministic message/receipt IDs, re-checked by a full read). `MISSIONS.md` lists the hinted callers. ## Validation - `journal-read-budget.test.ts` (1,900 finished-mission events): hinted paths do no full scan; safety paths still see writes the hints miss; presence release confirmed by full read; hook single read and tool hiding; ordering/damage parity; 60 s bound. 15 source mutations each fail a named test. - New native stage (100 finished missions, isolated runtime only): `inspect` and the model turn read no full journal; snapshot and damage gate do. - `receipt-projection.test.ts`: an outbox pass refused by its item's full read now counts as failed instead of throwing (same backoff, no duplicate message). - Server and UI typecheck; 1,358 server tests; `test-missions-native.mjs` on isolated OpenCode 2.2.0 and 2.1.4. Note for step 4: mutations stay O(history) (capacity scans, rule-1 full reads). |
||
|
|
21bfe855ce
|
feat(missions): keep an in-memory index of the mission journal (#1176)
Step 2 of 6 toward removing the Missions limits (#1167 follow-up), on top of step 1 (#1175). ## Change - New `MissionJournalIndex` (`missions/journal-index.ts`): activation-owned, never persisted. Holds per-range session references (coordinator, prepared-by, dispatching, native-bound), ordering data, open state, local damage, cleanups and stray-entry count; answers `refs`, `openIDs`, `aggregate`, `cleanups`. - Built by one key-ordered scan (`MissionJournal.scanRanges`) with `missionView`'s parsing/purge/reducer; kept current by marking each mission dirty **before** every ordinary write attempt; a per-project write generation (host-global, like the append lock) detects foreign writers and rebuilds; any failure drops it. Nominates candidates only: decisions must re-read storage. - Cannot observe bytes written outside `MissionJournal` after its build (other OS process, earlier builds); documented and pinned by a test. - Recurrence passage storage marked `isolated`: never feeds or serves the index. - Wiring only (activation creates, passes to `MissionControl`, disposes). **No caller reads it yet.** `MISSIONS.md` "Journal index" paragraph. ## Validation - `journal-index.test.ts`: property test (24 seeds × 70 steps, failed/unconfirmed writes, interrupted purges, damage, new activations, foreign writes) equals full `snapshot()`/`events()` on every answer and rescans only on activation/foreign write; deterministic cross-mission fixture; unreadable records; incarnation/blind-spot. Passage trap test in `native-passage-business.test.ts`. - 11 source mutations each fail a named test. - Server and UI typecheck; 1,343 server tests; `test-missions-native.mjs` on isolated OpenCode 2.2.0. |
||
|
|
9ed72ae068
|
refactor(missions): read one mission's journal range (#1175)
Step 1 of 6 toward removing the Missions limits (#1167 follow-up): make per-operation cost depend on active work, not history. ## Change - `MissionJournal.missionEvents(missionID)` / `missionView(missionID)`: one scan of `<prefix>/<missionID>/` with the same parsing, append/purge exclusion, read bound and purge-remnant resolution as `events()`, reduced by the same private `reduce()` that `snapshot()` now delegates to. - A view's discarded count and damage flags are its own; the snapshot's are their aggregate plus entries outside every mission range. - Recurrence passage adapter accepts its own mission's range. - **No caller switches yet**: behaviour unchanged. `MISSIONS.md` describes the read. ## Validation - `journal-mission-view.test.ts`: 24 seeded 70-step histories of real `MissionControl` operations (all 15 written event types, shared sessions, failed ACKs, pending cleanups, interrupted purges) with injected damage, prefix-extension IDs and timestamp ties; after every step each view equals its snapshot mission, views partition `events()`, counts/flags/cleanups aggregate to the snapshot. Red when the discarded count is dropped, the range's trailing slash removed, or the record check skipped. - `recurrence-passage.test.ts`: 121-event passage read by range. - Server and UI typecheck; all server missions/routes/opencode-missions tests; `test-missions-native.mjs` on isolated OpenCode 2.2.0. |
||
|
|
84d2d0a245
|
fix(missions): free journal capacity when deleted missions settle (#1173)
Follow-up to #1167. ## Problem A project's mission journal holds 2,000 entries and never removed any. Deleting a mission appended a tombstone and cleanup receipts on top of its events, so a project that keeps creating and deleting missions eventually filled the journal permanently: no create, and no delete either (the tombstone needed room). ## Change - **Purge on settlement:** once every cleanup target of a deleted mission has its receipt, its events are removed from native plugin storage (`storage.remove`, passed through a narrow `MissionStorage.remove`; storage without it keeps today's behaviour). - **Retained:** the 20 most recent settled deletions stay in full (cleanup history, lost-ACK checks); all eligible ones are purged when capacity is needed. A deleted mission stays while it references a session another unsettled mission could still clean up. - **Deletion record:** one compact record per purged mission at `codenomad-missions/v2/deleted/<projectToken>/<missionID>`, outside the scanned prefix (no journal capacity, exact-key reads). It refuses recreation (`mission-deleted`) before any native effect on create, prepare and coordinator start replays, and answers an exact deletion replay with the original summary. - **Crash-safe, resumable:** record first, then `mission.created`, tombstone, intents, receipts; an incomplete group with a record is dropped from readers; late writes are removed by the next purge; damaged journals are never purged. - **Full journal recovers:** a 36-entry reserve, usable only by tombstones and reserved receipts; reads bounded at 2,036. - Legacy tombstones are purged the same way. - Docs: `MISSIONS.md`; help (EN + 9 translations) cleanup-history sentence. ## Known risks - Rolling back to an older build reads a mid-purge journal as damaged, a reserve-using journal as over its limit, and ignores deletion records (a retried creation could recreate a purged mission). - Under capacity pressure the recent cleanup history is purged too; a delete whose ACK was lost in that window then shows a false "missing" error. - Deletion records accumulate (one small exact-key entry per purged mission). ## Validation - `journal-purge.test.ts` (10 tests, each red with its piece removed): 200 create/delete cycles, recreation refusal and replay, pending cleanup kept, crash at every step, late writes, legacy tombstones, full journal of live missions, no-remove storage, shared sessions, damaged journals. - Native `test-missions-native.mjs` gains a settled-deletion purge stage (red when the plugin lacks `remove`). - Server and UI typecheck; 1,331 server missions/routes tests; `test:help`; native run on isolated OpenCode 2.2.0. |
||
|
|
4e113583c8
|
fix(missions): surface a failed first recurring admission at once (#1171)
Follow-up to the #1158 review (pre-existing P2). ## Problem When the first native admission of a due recurring passage threw, the runner returned `unknown`, the due adapter reported `pending` and the clock treated the wake as successful: warning cleared, sleep up to 1 h. The passage looked silently `starting` for that hour. ## Change - `native-recurrence-due.ts`: new wake result `admission-unknown` for that case. - `native-service-clock.ts`: after its reread, while the passage is still pending and unadmitted, that result is a failed wake: the existing redacted `admission-failed` warning is published at once and the existing 30 s backoff retries the same pending identities. Nothing archived, no cursor advance, no new identity. - `MISSIONS_RECURRING.md` describes it. ## Validation - `recurring-day.e2e` test M: injected admission refusal and failing observation read (new fixture `failQuery`): warning on the first wake, no start before 30 s, exactly one admission with the original identities at 30 s, survives restart. Red without the fix. - Server typecheck; all missions tests; `test-recurring-simple-native.mjs ABCD` on isolated OpenCode 2.2.0 and 2.1.4. |
||
|
|
4d8db216b8
|
fix(missions): remove the 20-mission project limit (#1170)
Fixes #1167. ## Problem A project refused its 21st mission with `Project mission limit reached`, counting finished missions too. Long sessions whose coordinator starts missions itself hit it and could only continue after deleting missions by hand. The limit only bounded refresh cost. ## Change - Creation (panel, `mission_prepare`, the coordinator's `mission_inspect` start) no longer counts missions; the snapshot lists every mission. - The `mission-limit` code, its no-effect receipt path and `missions.control.creation.limit` (10 locales) are removed; `request-conflict` stays the only no-effect creation refusal. - Refresh cost stays bounded: activity observation reads at most 160 actors per refresh (the former 8 × 20), active missions first, then the most recently updated; the rest read as unknown. - Unchanged: 8 actors and 96 tasks per mission, the 2,000-entry project journal (follow-up), the 20 most recent settled cleanups. - `dev-docs/MISSIONS.md` states the current bounds. Help never documented the limit; nothing to update there. ## Validation - `control.test`: 25 missions created and listed. `activity.test`: 31 missions / 241 actors → exactly 160 session reads, the oldest active mission observed. - Creation-fence forged/mismatched no-effect receipt matrix kept on `request-conflict`; damaged-storage refusal kept. - Server and UI typecheck; all server missions, routes and native missions tests; UI mission and i18n unit tests. |
||
|
|
a868a8e720
|
feat(missions): blue prepared missions, quiet interrupted chevron, full-panel create form (#1155)
## Changes - **Prepared missions are blue.** A created, never-started mission gets a blue mark (`--accent-primary`), like a prompt ready to send. - **Interrupted no longer bubbles into the collapsed chevron.** Like a collapsed session parent, the chevron only counts answers, running work and unseen completions; interrupted, paused and prepared missions colour only their own rows. The unused `missionsPanel.attention.interrupted.*` keys are removed from all locales. - **The creation form replaces the list and detail.** While creating, the current-mission line, list, schedules and selected detail are hidden (still mounted, so pending controls and retries survive) until Create or Cancel. - **Nesting depth in the creation form.** Options embed the existing project depth editor (`MissionSubagentDepth`). OpenCode only reads `experimental.subagent_depth` per config file, so the value is the project's and applies to every conversation there; the editor says so. - **Button templates.** `.window-text-button` had no rule anywhere, leaving ~18 mission buttons unstyled. It is now a shared primitive in `styles/components/window.css` (borderless accent text, hover tint, muted when disabled). The creation footer's **Save as brief** uses the footer's `button-secondary` template; the bordered **Answer** button uses `.window-action`. ## Docs `dev-docs/MISSIONS.md`, `dev-docs/UI_CONVENTIONS.md` (window button primitives), and `docs/help/*/missions.md` in English plus the nine translations. ## Validation - UI typecheck; mission and i18n unit tests; `npm run test:help`. - All 34 `tests/browser/mission*.test.ts` files pass. Updated: `mission-status-marks` (blue computed colour, quieter chevron label), `mission-editor-profiles` and `mission-editor-lifetime` (the form replaces the list; leave it before browsing). - Visual check of the real components through the mission-control fixture: prepared mark, collapsed chevron, full-panel form with depth, footer buttons, selected detail. |
||
|
|
339a0844ca
|
fix(opencode): stop an emptied project experimental block from capping subagent depth (#1153)
## Problem
A mission asked to nest subagents three levels deep stopped at the first
level: every nested call returned `Subagent depth limit reached (1).
Increase "experimental.subagent_depth"…`, although the global OpenCode
config sets `experimental.subagent_depth: 5`. The project file
`.opencode/opencode.jsonc` contained only `"experimental": {}`.
## Cause
- OpenCode reads `Config.latest(entries, "experimental")?.subagent_depth
?? 1` (`core/src/tool/plugin/subagent.ts:129`): the **whole**
`experimental` object of the highest-priority document that has one,
unmerged (`core/src/config.ts:24-27`). An empty project block therefore
hides the global depth.
- CodeNomad's depth editor reset (**Inherit**) removed only
`subagent_depth`, leaving that empty block behind.
- The editor computed the effective depth as the last document defining
`subagent_depth`, so it showed the global 5 while OpenCode applied 1.
## Change
- **Inherit** also removes a project `experimental` object it leaves
empty; foreign experimental keys, comments and formatting are kept.
- The effective depth follows OpenCode's read; a project block without
the depth reports the native default (unknown when the contract declares
none) instead of the hidden global value.
- `dev-docs/MISSIONS.md` describes both. Help already says that an empty
field inherits, which is now true in every case.
## Validation
- `subagent-depth-settings.test.ts`: shadowing read, Inherit keeps
foreign experimental keys, an empty project block reported as hiding the
global depth and removed by Inherit after an override.
- Server typecheck.
- `scripts/test-subagent-depth-settings-native.mjs` against an isolated
OpenCode 2.1.4.
|
||
|
|
048cd65183
|
fix(missions): one status palette, no agent-decided failure, resumable missions (#1150)
fix(missions): one status palette, no agent-decided failure, resumable missions (#1150) #1147 reported a red chevron beside a running mission because another, older mission had failed. Missions mixed two colour systems: rows showed session-style attention when there was some and otherwise a lifecycle colour (blue running, green completed forever, red failed), the collapsed chevron added "failed", and the task tree had its own palette. Red meant both "your answer is expected" and "this ended badly", a failed mission offered no action, and a one-time coordinator could end a mission as failed on its own. Colour now follows the session list everywhere (mission and schedule rows, the selected mark, task icons, the chevron) and shows only what needs attention now: - red: an answer is requested, counted like Needs you (declared actors and observed family; schedule rows without a passage read count the live passage's conversation and loaded subsessions); - yellow, pulsing: observed work; - orange: interrupted or paused, with an action to continue (an interrupted schedule, or a running one whose latest run failed); - green: a completed mission this window has not shown yet; - everything else is neutral: outcomes (done, failed, blocked, waiting, retired) are status text and icon shapes. missionMark/scheduleMark in mission-picker-model.ts compute one mark per entry. Like a collapsed session parent, the chevron colours only while the list is collapsed, from the other entries (paused is not counted). "Seen" works like an idle session: mission-view-state keeps the completed missions this window has shown under its own layout key and signal, so recording one never wakes project-view readers (an early write through the project view re-ran session-view effects and aborted an in-flight outline read). The first read counts missions already completed as seen; a completion is marked when selected while the panel is visible in a focused window (createDocumentAttention, now shared with SessionView). One-time coordinators no longer decide a failure: the plugin's MissionControl sets completedFinalOnly, and the refusal, coordinator context and tool description tell the coordinator to ask the human through the native question tool. Only the human stops a mission. Recurring passages keep their own control and completed|failed. Interrupted missions get an action: - A running mission's coordinator owes its final report: observed idle with an empty inbox and no coordinator notification still being delivered (native returns excluded), it projects idle-without-report; when nothing else runs the mission reads Interrupted and coordinator recovery replaces Pause. - A failed mission reads as interrupted unless stopped since, and the same recovery resumes it: under the mutation lock it refuses with already-member when one of its sessions joined another active mission (as at creation), otherwise appends mission.reopened at the exact revision and sends the ordinary recovery nudge at the new revision. The reducer resumes only a failure, drops its summary and lets a later Stop end it; a later finish uses a revision-bound event identity. The unwired authority policy accepts the same resume. dev-docs/MISSIONS.md records the rules; the Missions help page gains "Colours and interrupted missions" in all ten languages, and new strings are added in every locale. Validation: mission-resume.test.ts (completed-only refusal, passage-style failed finishes, resume once with conflicts and no second nudge, shared-session refusal, reducer reopen/Stop rules); activity.test.ts (owed final report, queued inbox, pending and native-return reports, non-running missions); mission-picker-model unit tests; the new mission-status-marks browser test (marks, collapsed chevron, unseen completion surviving reload, recovery replacing Pause, neutral done and blocked tasks, failed mission resume, recurring requests including independent actors and a zero selected count, quiet stopped/paused schedules), red on dev; updated mission-control, mission-briefing and mission-recovery tests. All Missions server tests, the full browser suite (four shards), help checks, UI and root typechecks pass. |
||
|
|
77675a4a0b
|
fix(missions): keep an unsent mission draft across tabs and projects (#1142)
An unfinished mission draft behaved differently depending on how the user left it (#1130). After switching to another project and back, the draft was still shown but Create stayed disabled until the editor was cancelled. After switching the right panel to Files or Status, the draft was gone. Two causes: - MissionControl kept each open editor's activity as a one-shot view capture (createMissionViewFence). A capture is invalidated by any identity or visibility transition, including a round trip, so once the project was hidden the editor stayed inactive for good, with Create and its network-backed controls disabled. - The open editor and its draft lived in component signals. The right panel renders only the selected tab, so another tab disposed both. The open editor now lives in window memory per panel scope (stores/mission-editor-state.ts, keyed by instance and folder), never in the native view layout. Its activity is the live view activity, so a hidden project's editor works again on return. Operations still capture their own view fence at submit and ignore completions from an earlier view lifetime. When the panel unmounts, an idle editor stashes its draft in its entry and reopens with it: objective, title, notes, schedule, playbook, profiles and execution mode, the chosen brief, open sections, and its request identity. An editor with a request in flight or held still closes and settles through its creation or recurrence hold, which keeps precedence over a stashed draft. Cancel, a saved result or a deletion closes the entry, so a later editor starts blank. dev-docs/MISSIONS.md records the editor's window-memory scope and that a view capture guards one operation, never a component's lifetime. Validation: a new mission-editor-lifetime browser test drives a create and an edit draft through a hidden/visible round trip, a panel remount and an instance round trip. It checks the restored fields, that Create or Save is enabled and sends the kept draft once, and that cancelled or saved editors stay closed. On dev it fails at the first round trip, with Create disabled. The existing transition matrix also asserts that an editor unmounted while its request is in flight does not reopen. The Missions browser suites, the UI own-tsconfig and root typechecks pass. |
||
|
|
4ced2d18da
|
fix(missions): give mission roots OpenCode session identifiers (#1133)
## Problem Missions on OpenCode Zen's free models (for example `opencode/deepseek-v4.1-flash-free`) fail at the coordinator's very first model request with **"Free usage limit reached. Connect a provider or subscribe to OpenCode Go…"**, while ordinary conversations on the same model and network keep working. ## Cause Missions created its coordinators, recurring passage coordinators and independent task roots with `ses_` + 26 hex characters of a SHA-256. OpenCode passes that ID to the provider as the session identity (`x-opencode-session-id`, `x-opencode-session`, `X-Session-Id`, `x-session-affinity`; `core/src/session/model-request.ts`), and native children send their **root's** ID as affinity (`core/src/session/affinity.ts`), so one such root blocks the whole mission family. Isolated probes against the real free tier (keyless OpenCode 2.1.4, OpenCode-generated control IDs accepted throughout) characterized two checks: | Check | Requirement | Otherwise | |---|---|---| | Shape | `ses_` + 12 lowercase hex + 14 alphanumerics (any case/charset for the 14) | 403 `FreeTierError` | | Time | the 12 hex decode, as OpenCode's descending identifier, to a date between ~86–90 days before and 24–30 h after the request | 429 `FreeUsageLimitError` | A hash prefix decodes to an arbitrary date over the ~795-day cycle, so most mission roots failed the time check. A real mission A/B (UI-created vs. the same mission attached to an OpenCode-created root) reproduced it: derived coordinator refused, native coordinator completed; a child with an ordinary ID under a hash-ID parent was refused too. ## Change - `missions/session-identity.ts`: mission roots take OpenCode's descending identifier for their **real creation time** followed by 14 hex characters derived from the mission or task identity. Ownership stays provable from the ID alone. - Creation: one-time coordinators (`control.ts`), independent task roots (`control.ts`) and recurring passage coordinators (`recurrence-store.ts`, passage `createdAt`). The time comes from the real clock (`options.now ?? Date.now()`), not the journal's monotonic timestamp, which may run ahead of it. - Ownership checks match the derived suffix and also accept the original 26-hex derivation held by existing journals: managed-root creation (`mission-root-creation.ts`, `ensureActorSession`), cleanup targets (`session-cleanup.ts`), recurring passage parsing/archive/current reads, and the creation readback (`mission-creation-pipeline.ts`, which no longer needs the exact coordinator ID in advance; the hold's `sessionID` is optional). - `dev-docs/MISSIONS.md` documents the identity rule. Task roots and recurring coordinators record their ID before creating the session, so retries still reuse the exact root. A one-time coordinator takes the clock at creation. ## Compatibility Existing missions keep their coordinator IDs (and remain refused on free models); new missions are accepted. Like any OpenCode session, a mission root older than about three months is refused by the free tier. No UI, API or journal schema change; no user-facing help change. ## Validation - `session-identity.test.ts`: time prefix against IDs OpenCode 2.1.4 generated at known milliseconds, descending order, suffix derivation, legacy acceptance, rejection of other missions/tasks/shapes. - Server typecheck; all Missions server tests (`src/missions`, mission routes, plugin, `src/opencode/missions`): 1140 pass, 1 pre-existing environment skip. - Native scripts against an isolated OpenCode 2.1.4: `test-missions-native`, `-trajectory`, `-family-control`, `-contract` pass. - Real free tier: a mission created through the real CodeNomad routes and plugin had its coordinator's first request accepted with the new identifier. The remaining real checks (native subagent child, independent task root, recurring coordinator) are pending: the probing exhausted the network's free allowance (an OpenCode-generated control is now refused too) and will be rerun after it resets. |
||
|
|
7a1fc90310
|
docs(agents): keep AGENTS.md to universal rules and agent docs to the current state (#1082)
AGENTS.md loaded about 33,000 tokens into every agent session, subagent and reviewer, mostly per-feature contracts, and the agent documentation around it had accumulated duplicates, dated logs and history of removed designs. This PR makes AGENTS.md a short set of rules that apply everywhere plus an index of area documents, and leaves AGENTS.md, dev-docs and the repository skills describing only the current state. Three commits, reviewable separately: 1. **Move (lossless).** Every per-area rule moved verbatim from AGENTS.md into its area document (existing ones, or six new ones: UI_CONVENTIONS, UI_SHELL, COMPOSER, TRANSCRIPT, SETTINGS, PROJECTS). A script split the old file into 208 units, required one destination for each and checked all 199 content units verbatim at their destination. AGENTS.md went from 134,000 to 16,000 characters. 2. **Trim.** A per-area ponytail review removed rules stated two or three times (keeping the more precise copy), dated validation logs, per-version qualification sections below the current minimum, abandoned designs and self-references; seven documents whose surviving rules live elsewhere were deleted. It also fixed statements the code contradicts (locale registration, the Mission play/recovery condition, plugin package updates, cleanup progress UI, timeline index persistence, automation restart/targets, stale pins and minimums). 3. **Current state only.** The new AGENTS.md rule: no history (removed features and their names, former behavior, provenance, dated logs) and no denials; correct a contradicted statement at its source. DEVELOPER_MODE.md is merged into BROWSER_AUTOMATION.md and every Developer Mode mention is gone (including the README and the browser help page in ten locales). Names that only made sense against older designs are renamed with every reference updated, MISSIONS_ENVIRONMENT_ADMISSION is folded into SESSION_ENVIRONMENT, and INDEX lists every document. The server npm README now states the real OpenCode minimum (2.1.2). AGENTS.md is now about 14,000 characters (roughly 3,500 tokens); AGENTS.md, dev-docs and the vendor README went from about 11,200 lines to under 4,700. Validation: every relative Markdown link and every cited dev-docs path resolves; no reference to a renamed or deleted document remains; `npm run test:help` passes for all ten locales. Review: three independent GPT-6.1 Sol (high) reviews by area found binding rules lost in the trim and current-state passes; 25 were restored as present-tense facts or prohibitions and four contradicting statements corrected, each checked against code. A Fable 5.1 gatekeeper review of the whole PR passed; its follow-up routes session recovery from the index, points to dev-docs/INDEX.md and documents the family identity read. |
||
|
|
92ba215254
|
fix(missions): run Missions on the standalone server and name each unavailable cause (#1070)
## Problem Missions dispatch through the owning backend's loopback automation bridge, not through the desktop window. Yet the bridge and the Missions plugin were only provided when a desktop host launched the server. On the standalone server (`npx @neuralnomads/codenomad…`, whose default listener is HTTPS-only), Missions never loaded, and Mission Control told users to "Reconnect CodeNomad", which cannot help (#1069). Tauri on macOS/Linux sets no native parent and was affected the same way. ## Fix - **Private loopback listener:** the HTTP listener always starts. When HTTP was not requested, it is private and unannounced, on a random `127.0.0.1` port. - **Bridge and plugin:** the bridge is published whenever a loopback HTTP listener exists, and the Missions plugin is provisioned once that publication settles, so early connections never skip it. - **Automation stays desktop-only:** without a desktop host, the bridge's `browser-claim` answers only `purpose: "mission"`, which the Missions plugin now sends. Browser tools therefore never select an owner that cannot open a page. Remote Control keeps routing into the application users reach. - **One deletion fence per backend:** all listeners now share one `WorktreeDeletionFence`, and with it the mission creation/recurrence holds. Previously each listener had its own, so a deletion through HTTPS could not drain a mission admitted through the HTTP bridge. This could already happen in desktop dual-listener mode. - **Exact unavailable causes,** localized in all ten locales: - `host-unavailable`: this backend has no bridge; reported before reading OpenCode. - `plugin-unavailable`: OpenCode has not loaded the plugin. Refresh, then use **Reload OpenCode configuration**. - `plugin-failed`: OpenCode failed to load the plugin; its error is in the Plugins list. ## Validation - New real startup test: the actual entry point as a standalone HTTPS-only server, isolated profile, no OpenCode. It proves the private `127.0.0.1` bridge is published, authenticated (401 without token), unannounced and removed on shutdown. - Bridge route tests: mission-only claims without a desktop host, ownership still required, native requests fail closed. The plugin test asserts that the claim carries the purpose. - Missions route tests cover each unavailable reason, and `host-unavailable` reads nothing from OpenCode. - The isolated native Missions fixture (`scripts/test-missions-native.mjs`) passes on **OpenCode 2.1.2** through the real bridge route with no desktop host. - Server and UI typecheck: 0 diagnostics. Full server suite running. Refs #1069 |
||
|
|
debbdce33f
|
feat(missions): add a "Compare two models" Mission template (#1026)
## Summary Adds a fifth Mission template, **Compare two models** (`compare`). It runs the same bounded task on two different models and ends with a blind verdict. - **Creation:** the user picks **model A** and **model B** in the main creation form. Both are required and must name different models (provider + model id; a reasoning variant alone is not a different model). This is enforced on every creation path: HTTP, prepare, recurrence create and agent-started missions. - **Roles:** `model-a`, `model-b` and `comparator`. - A and B share one instruction text and report contract. - The coordinator writes the task, inputs and criteria once, and gives both byte-identical briefs. - The server refuses a run task whose brief differs from its sibling's, or whose per-task execution would change its role's model. - **Blind judge, always on:** the comparator works on neutral X/Y labels. - It reports one versioned `comparison` artifact: the A/B task references, per-criterion scores for both runs, sourced observations, and a verdict `a | b | equivalent | inconclusive`. - Completion requires both runs and that comparison. - **Judge model:** frozen once at creation, like every other role. - An explicit judge is kept; otherwise the judge copies the coordinator's effective model. When that model is unknown, both stay on the native default. - Replays and retries therefore reuse the stored profile. - The editor warns when the judge's model equals A or B. - **Readers:** the verdict, both model names, a score table and evidence by path. Hashes stay under Technical details, for current and archived recurring passages. The template came out of evaluating Harness2's comparison loop. It deliberately has **no** lesson, human approval, budget or duration handling, and leaves the Wayfinder human gate unchanged. Up to five models, per-run worktrees and launching from the composer's model picker (OpenChamber-style multi-run) are left for later. `AGENTS.md` records the single editor exception: the A/B selectors sit outside the collapsed Options disclosure. All ten locales are updated. ## Size and upkeep 62 files, +837/-96: | Part | Lines | |---|---| | Product code (about 16 files are one-line template-list hooks) | +280/-57 | | Locales (13 keys × 10) | +131 | | Tests | +409 | | Docs | +17 | `missions/control.ts` stays oversized (~1,500 lines). ## Validation - Full server suite: 2,426 tests pass, 7 existing skips. Root and server typechecks, the server build and the plugin build pass. - UI: 45 unit tests and 36 browser tests pass. - The native trajectory fixture, extended with a compare case, passes on OpenCode 2.0.31. It checks two distinct models, a one-newline brief difference refused with the journal unchanged, a real blind judgment and a completed mission. The native contract and recurring Q fixtures also pass. - Regressions cover: - distinct-model refusals on every creation path; - brief and override refusals; - artifact validation and the completion policy; - judge freezing at creation, revision replay, and retry after a coordinator model change; - editor inheritance and readable evidence. - Independent review rounds ended with no P0-P3 findings. - The editor and reader were checked from screenshots of the real Solid components in the browser harness, not in a packaged app. |
||
|
|
362efb5911
|
test(missions): fold the one-time decision journey into the trajectory fixture (#1024)
## Summary #1023 fixed one-time Wayfinder decisions with about 85 lines of product code but 410 lines of tests. Its 248-line native script re-implemented the daemon, bridge and workspace setup that `test-missions-native-trajectory.mjs` already has. `native-invocation.test.ts` also carried overlapping cases. - The decision journey is now one case of the trajectory fixture, reusing its private daemon, shipped plugin, bridge, workspace and real subagents. The case covers: - refusal before an answer, with no journal mutation; - a native child question answered through the authenticated UI route; - completion by discovery without a supplied artifact. - `scripts/test-missions-wayfinder-decision-native.mjs` is deleted. - `native-invocation.test.ts` (162 to 90 lines) keeps one test per distinct behaviour: - concurrent isolation and stale invocations; - exact `Exit.fail`/`Exit.die` preservation; - interruption joining a raw read that ignores abort, through the real `acquireNativeHumanAnswers` adapter; - the per-check deadline between two successful checks and after an expired one. - No product code changes. Net -260 lines. ## Validation - With the pre-#1023 `managed-owner-plugin.ts` substituted in memory, the trajectory case fails after the UI reply with `Native human answer graph unavailable`. Restored, all 9 checks pass on OpenCode 2.0.31. - Detaching reads from the runner, or converting errors to defects, makes the unit tests fail. - The full server suite passes (2,410 tests, 7 existing skips), as do the server typecheck, the plugin build and bundle test, and the native contract fixture. - An independent review ended with no P0-P3 findings. |
||
|
|
c42cfe740a
|
fix(missions): run the one-time human gate in the invoking native graph (#1023)
## Summary A one-time Wayfinder mission could never complete a decision task. The decision child asks its native question, the human answers it in CodeNomad (authenticated UI reply, durable mark confirmed `via: "ui"`), and the coordinator's decision report is still refused with `Native human answer graph unavailable`. The one-time human gate is built in `managed-owner-plugin.ts` from the plugin setup graph. That graph lacks the Location, Form and Database services that `acquireNativeHumanAnswers` requires. Recurring passages build their own gate in the Job's Location graph (`native-recurrence-due.ts`), so they were not affected. This is why the recurring Wayfinder journey (Q) kept passing while the one-time path was broken. ## Fix - `native-invocation.ts` captures each invoking native graph with `AsyncLocalStorage` around the plugin's tool executes and forwarded Missions RPC handlers. The one-time gate runs its reads in that graph. - Concurrent invocations stay isolated. A missing or finished invocation is refused, never served by another invocation's graph or the setup graph. - Each complete human check gets its own 10 s budget, the bound `native-human-evidence.ts` already uses. Its nested SQL/Form/storage reads share that budget, which is not reset per query. - Interruption or expiry aborts those reads. The borrowed graph is released only after every started read has settled. A truly non-interruptible native finalizer must still finish first; this is documented. - Expiry surfaces as an ordinary refusal. - Recurring runners are unchanged. ## Validation - New native fixture `scripts/test-missions-wayfinder-decision-native.mjs`. It drives the shipped plugin on a private daemon with a synthetic provider: a real child asks a native question, and the reply goes through the authenticated UI route. - Before the answer, the decision report is refused with no journal mutation. - After it, the decision completes by discovery, without a supplied artifact. - On unmodified `dev` it fails with the error above; with this change it passes (OpenCode 2.0.31). - Unit regressions in `native-invocation.test.ts` use the real `acquireNativeHumanAnswers`. They cover invocation isolation, missing or stale invocations, Effect errors, interrupted and blocked reads, and successive checks separated by slow work. Each new regression fails on the earlier code. - The full server suite passes (2,413 tests, 7 existing skips), as do typechecks, the plugin build and bundle test, and the native `test-missions-native`, contract, trajectory, family-control and recurring Q fixtures. After rebasing onto #1022 (UI preferences only), the server typecheck and the 34 targeted human-answer tests pass again. - An independent review loop ended with no P0-P3 findings. |
||
|
|
8cecb08704
|
feat(settings): compact Settings, unified notification routing and autosaving Missions preferences (#1014)
Settings had grown long captions, inconsistent groups and two separate notification circuits, and Missions preferences used an explicit draft with Save/Reset/Reload that no other section needed. This pass makes the screen shorter and the behaviour uniform without changing what is stored. Compact layout. Rows keep a title; secondary explanations move behind a focusable "i" tip (settings-info-tip.tsx). Folded parts share one quiet disclosure style (settings-disclosure.css). In-group row lines use a new --border-row token, fainter than the --border-divider between groups. Providers splits Models and Web search into folded cards; Web search starts closed and mounts nothing until opened, so its metadata is read on demand. The separator between them spans the content pane. A global `details > :not(summary)` box-sizing rule fixes folded content inheriting the details slot's content-box, which made full-width selects overflow. Unused messages are removed in all ten locales. Notifications. Every showToastNotification declares a category, and Settings > Notifications routes each category to On screen / History / System from one table (lib/notification-routing.ts). Errors always reach the screen and History so a rejection is never lost; transient progress messages stay on screen only. Session needs-input and completion use the same routing. Saved legacy choices (OS notifications off, needs-input/idle toggles) seed the table until it is first saved. The tab-bar bell now always opens History. Keyboard shortcuts. The catalog lists the 24 initially unbound palette commands too (73 actions), reusing their translated names and keeping existing aliases as one row. Missions preferences. Each choice is written at once, compare-and-swapped against the value stored at its turn. Edits made during a write stay enabled (disabling a focused control drops keyboard focus) and queue behind it. A rejected or unconfirmed write is never resent: later queued edits built on it are dropped and a read-only reload shows the stored values. Reload appears only when that read fails or the document is unreadable. Agent/model choices now use one Agent/Model/Reasoning row per role in every scope and at creation, with no second selector level, sub-fold or Debugging special case. In a mission type, rows show the inherited global values with a "Global" mark. Editing a field customizes the role from those values, and a revert icon inherits again. Inherit, explicit native default and a selected profile stay distinct. Reset (confirmed) lives in that fold and clears only the shown scope's agents and models. The project depth saves on Enter or blur; invalid values are flagged and never written. Reads and writes make the field read-only so focus stays, and refreshing is offered only after an error or an unconfirmed write. The depth group is titled "Subsession nesting depth" and shows "OpenCode effective depth"; scope badges are gone. Validation: typecheck; unit tests for notification routing, mission i18n alignment and profile summaries; browser suites mission-defaults-models (new autosave, conflict, queue, reset and inherited-row tests), mission-control, mission-editor-profiles, mission-editor-lifetime, mission-create-*, mission-panel-*, websearch-settings, preferences-startup(-state), plugin-controls, project-tab-close, new-instance-launcher, tab-chrome, appearance, git-history and hotkeys-settings. Review fixes. Missions defaults: a queued edit now touches one role, or one scope's task-session policy, and is refused when that stored value is no longer the one it was made from. A choice made while a rejected write was being reread could otherwise copy the whole scope from a stale view, resend the rejected choice and erase another window's change. Independent edits still apply to the value stored at their turn. The Settings rows report one role at a time (MissionProfileControls.onRoleChange); creation keeps its whole-request change. Project depth: pressing Inherit keeps focus in the field, so leaving the field for the button no longer saves the typed value first and loses the click; it stays focusable while a read or write runs. A write acknowledged after the section was left is accepted and reread on the next activation instead of being shown as unconfirmed. Escape discards an unsaved value without writing. Notifications: the routing table builds a quick second click on the pending table instead of reverting the first. Routing is read untracked so effects that notify do not rerun on every settings change. The link-open error goes through routing. Web notifications without permission are skipped silently. Wording that still mentioned a global enable switch now names the System column. A never-saved former system switch deliberately takes the new defaults (product decision); a unit test records it. Settings: info tips toggle on tap for touch screens (Kobalte tooltips ignore touch hover and close on click, and a tap first focuses the trigger, so the target state is decided at press time). Settings switches expose role=switch. The palette strip remeasures when it mounts late. AGENTS.md now records the measured palette layout, the Settings switch round exception and the shared OpenCode disclosure styles; dev-docs/MISSIONS.md the autosave semantics. Removed dead CSS and the unused MissionTaskModeControls children prop; the depth input uses the field surface and its info tip sits outside the heading. Validation: typecheck; unit tests for routing, profile role edits and mission i18n; browser suites mission-defaults-models (new queued-role and touch tip tests, updated rejection test), mission-control (Inherit after typing, write acknowledged after leaving, Escape), mission-editor-profiles, appearance, hotkeys-settings, mission-settings-brief, notification-icon-badge, plugin-controls, preferences-startup(-state), preferences-window, provider-accounts, git-history, remote-access/control-settings, websearch-settings. |
||
|
|
b4f1dde4f8
|
feat(missions): coordinator hand-offs, task interrupts and native decision evidence (#1013)
Completes the coordinator frictions of #1009 left after #1011 (points 4 to 9) and repairs the recurring Wayfinder human-mark journey checks. mission.interrupt({ taskKey }) stops a retired native task's conversations through the authenticated bridge: the coordinator's direct children whose first user message carries that task's canonical assignment header. Each family is interrupted deepest first, then the conversation, with lifecycle Stop's post-interrupt recheck, settle and quiet verification; queued inputs are cancelled under the worktree-deletion fence. It refuses while any open task lives in that conversation's family (reuse or nesting chains, or its actor), and never touches the coordinator, other tasks or the plan. mission.prepare creates a paused one-time hand-off through the editor's prepareMissionCreation pipeline, holds and fences, and never starts it. A trusted preparedBy gives the preparing coordinator read-only visibility (inspect({ missionID }), inspect({ list: true })), never control. Retries read the journal's creation event first and are reported uncertain, never as nothing created. mission.inspect({ taskKey }) adds the task's observed native conversations as advisory evidence; the task-key parser is shared with the UI. A native Wayfinder decision readout may omit its artifact: the gate's discover() derives candidates from exact native structure only and verifies each; only a complete discovery with exactly one candidate fills it. requestID "auto" briefings may let the server pick the revision. One replacement may merge several retired tasks, and the task reader lists every predecessor. Scheduled passages, which have no backend route, are not offered the new tools. The Q and N journeys queried a retired human-marks key pattern; they now read HUMAN_MARK_STORAGE_PREFIX, and the Q UI run exercises discovery natively. Validation: the full server suite passes (2,383 tests, 7 existing skips). Isolated native fixtures pass against a private OpenCode 2.0.31: test-missions-native, trajectory, contract, family-control, and recurring journeys ABCD and QN. The task-reader browser suite and bundle regression pass; typechecks show only existing diagnostics. Three gatekeeper rounds; the last found no P0-P3 issue. Closes #1009. |
||
|
|
1173beb37c
|
fix(missions): compact coordinator tool results and state the decision rule (#1011)
Coordinators driving a real Wayfinder mission hit three costly frictions (#1009): every mission tool mutation echoed the whole map (about 50 KB after ten tasks), the canonical assignment prompt only existed inside that response, and the decision role read as a self-contained analysis although completing it requires the human's own answer to a native question. mission.delegate, revise, report and briefing now return a compact readout (missions/tool-result.ts): disposition, current revision, status, run state, frontier and the affected tasks, plus the contract and exact assignment prompt for a declared native task. Briefs, reports and the objective stay out; mission.inspect keeps the full map, and mission.inspect({ taskKey }) rereads one task with its contract and current prompt. The returned revision feeds expectedRevision and basedOnRevision directly. Reports name the task the control actually resolved, including an independent actor's omitted taskKey. The native Wayfinder decision role, the coordinator contract and native decision declarations state the rule verifyHumanDecision enforces: the task's own conversation asks through the native question tool and the user answers in CodeNomad; self-contained analysis belongs to research. Independent-mode coordinators, which that gate does not check, do not get it. The refusal shows the exact provenance shape the gate validates, kept aligned with nativeDecisionProvenance by a test. Refusals for a withdrawn readout and unsettled withdrawn work explain what to do, a briefing citing missing or retired tasks names them, and the Missions skill matches. No journal, gate, ownership, RPC or UI contract changes. Interrupting a retired task's native child, multi-task replacement, a paused-mission creation tool, native execution visibility and Form IDs remain open. Validation: 1,104 Missions server tests pass (one existing skip), with new compact-result, task reread, retired readout, briefing source, implicit report key, refusal shape and decision-rule tests. The isolated native Missions, trajectory and contract fixtures pass against OpenCode 2.0.31. Server typechecks show only the existing diagnostics. Two gatekeeper rounds; the second found no P0-P3 issue. Refs #1009. |
||
|
|
af075a6fa5
|
feat(missions): add a Product delivery playbook based on NomadWorks (#946)
## Summary Missions gains a fourth playbook, **Product delivery** (`delivery`; FR "Livraison produit"), next to Flexible, Debugging and Exploration. Like them, it is named after what it does. It adapts the NomadWorks Collective method from the original [NeuralNomadsAI/NomadWorks](https://github.com/NeuralNomadsAI/NomadWorks) (`main` at `55eec7e`, v0.1.1). It works without installing that plugin. - **Coordinator as Product Manager.** - **Classification:** it classifies each request by complexity (tiny, standard, complex) and track (implementation, investigation, spec). - **Specification:** for behavioral work, it has the business analyst and tech lead draft a Spec Change Request, then asks the human to approve it through a native Form. - **Delivery:** with the technical architect, it splits complex work into slices (foundation, core, logic, ui, polish, qa, docs). Only one task at a time edits the shared checkout. - **Closure:** it closes only with QA and tech-lead evidence, and once the documentation impact is resolved. - **Six roles:** business analyst, technical architect, tech lead, developer, QA engineer and UI/UX designer. Each returns the NomadWorks specialist output sections: summary, work performed, acceptance-criteria coverage, documentation impact, open risks and next step. Each role can have its own agent and model in the profile selectors and in Missions defaults. - **Deliberate differences from NomadWorks:** - Request approval is coordinator guidance, not a gate enforced by the server. - The Missions safety boundary replaces NomadWorks' agent commit authority: the human owns commits. - One playbook covers both NomadWorks team presets (mini and full), because the coordinator declares only the roles each complexity needs. - **Fix found along the way:** task-role validation checked every non-Debugging playbook against the **Exploration** roles. A new playbook's tasks would have been rejected. Each playbook now admits exactly its own declared roles. ## Changes - **Server:** - the recipe in `missions/recipes.ts` and the roles in `playbook-profiles.ts`; - the `delivery` template id in every server, plugin, RPC and route enum, and in the journal decoder; - the role check in `contracts.ts`. - **UI:** the Playbook selector, the defaults-scope selector and saved-brief validation. - **i18n:** the playbook name and six role labels in all ten locales. - **Docs:** `dev-docs/MISSIONS.md` (Included playbooks) and the `codenomad-missions` skill. ## Validation - Server and UI typecheck. - All Missions server tests, including a new regression showing that Product delivery admits exactly its own roles and that other playbooks reject them. - CI "Missions UI units" (36 files), plus every Mission and i18n UI unit test (40 files). The locale-ownership test now covers 25 profile labels. - `build:missions` plugin bundle. - Browser tests `mission-defaults-models` and `mission-editor-profiles` pass 24/24. The Flexible-exception loop now also covers the new playbook. Not yet validated: a live model run of a NomadWorks mission. |
||
|
|
81fb513558
|
feat(missions): native project coordination, briefings and tracking (#866)
# Missions: product specification ## Product goal Missions coordinates complex work through native OpenCode conversations. CodeNomad provides creation, controls, human attention and result reading; OpenCode owns execution. One-time objectives and finite daily recurring passages share the same planning and reading experience. No OpenCode modification is required. ## Create and configure - **Flexible mission**: free-form objectives and bounded delegation. - **Debugging**: diagnosis, implementation, independent standards/specification reviews and fresh validation. - **Exploration**: bounded decisions and parallel investigation, with explicit native human questions where required. Creation presents an objective, a short optional title, verbatim working notes, coordinator/task profiles and native or independent task-session policy. Optional choices stay collapsed. A single **Create** button saves a prepared one-time mission or paused recurring schedule without starting anything. **Play** is a separate explicit action for both. Settings → Missions contains defaults for **all new missions**, with optional exceptions for each of the three types, including Flexible. A type exception overrides the global value; an unset field shows the inherited value. Defaults never reconfigure existing missions or busy actors. Reusable briefs contain creation inputs only; loading copies them into a normal draft and deletion preserves executions. **Save as brief** is also available from a selected one-time mission, including a completed mission: it copies objective, verbatim notes, template, stored profile choices and task-session policy; the mission title prefills the brief name, never copying execution identities or results. Profiles choose an agent, model and optional reasoning variant. Coordinators use primary/all profiles; native task actors use subagent/all and independent actors use primary/all. Business roles remain separate from native session identities. Actor continuation preserves the accepted profile. The native subagent-depth editor is separately Location-scoped and applies to all OpenCode conversations there, not just missions. It shows the configuration file, effective value and override. Conditional Save preserves the original file expectation and never automatically reloads or restarts OpenCode. ## Missions panel The first toolbar holds only general actions: **Create**, **Settings** and **Refresh**, aligned right. Settings opens the Missions settings section directly, using the existing native Preferences window on desktop. A native refusal shows an error rather than opening a second embedded window; browser and remote windows retain their embedded settings. The current-mission line contains a leading chevron, the selected title and a small status mark: - Clicking the **chevron** expands a persistent inline list; selection leaves it open. - Clicking the **title** opens a transient popup; selection, outside interaction or Escape closes it. - Both lists show titles and status marks, with full labels in tooltips and accessible text. Titles truncate only at the available row edge. - Search appears only when the measured list exceeds its maximum height, and remains available while a query is present. - The closed chevron indicates activity/attention in other missions using the same visual vocabulary as the session list. - Only the picker changes the selection, remembered per project; opening a mission's conversation never reselects a mission. Selected-mission controls are separate: **Play/Pause**, **Stop**, optional recurring **Run now**, then **Summary**, **Open coordinator conversation**, **Edit** and **Delete**. Destructive actions require confirmation. **Request an update** is a text button below the icons, available only while meaningful; its pending/uncertain state is retained without automatic replay. The **Tasks** disclosure contains only the declared dependency tree, not a duplicate task list. It is initially open, remembers its state and uses the remaining panel height down to the window bottom rather than a fixed task-height cap. Rows retain full-width selected/hover backgrounds across connectors and controls. A task's exact conversation shortcut appears on desktop hover/keyboard focus, and remains visible on touch. Mission selection is remembered per project and changes only through explicit mission selection/creation. Opening a coordinator or task conversation in the session list never changes the selected mission. Conversation shortcuts remain available from the mission panel to the coordinator and exact task conversations. The session-list files are unchanged relative to `dev`; restoring the full cached session hierarchy is supplied by the separately merged #841. ## Plan and native execution The coordinator declares tasks, dependencies, roles and expected outcomes. Ready work can run in parallel through ordinary native subagents. Recursive decomposition follows OpenCode permissions and configured depth; the product imposes no delegation target or root fallback. Independent sessions remain available for an explicit Location, lifetime or existing-root requirement. The task-session policy is frozen per mission. Tasks have stable keys/generations; conversations have exact native identities. Business dependency edges never stand in for native ancestry. Native task returns and business settlement are separate: a coordinator records the actual returned result/evidence, then finalizes the mission. A notification/admission acknowledgement is not proof of consumption or completion. ## Results and human interaction Long objectives, briefings, task results, evidence and plan history open in the central reader above the transcript. Reader eyes toggle the exact target and highlight it. Task results come before the original brief and technical details. A missing/unavailable reader target must not leave the transcript hidden. The coordinator publishes dated, bounded briefings explaining delivered results, remaining work, obstacles and the next step in plain language. Briefings remain separate from task settlement and observed activity; exact request identity is required to acknowledge an explicitly requested update. Attention contains real native Forms and permissions. Obstacles reported in prose are not automatically human requests. Native interruptions retain conversation provenance and drafts in the shared interruption dock. A template's required human decision counts only when answered through the CodeNomad UI, locally or from a live paired Remote Control device; a simple UI-answer mark records that provenance. Users give direction or ask questions by writing in the coordinator conversation (**Open coordinator conversation**). From the panel itself, the only coordinator message is **Request an update**; its pending/uncertain state is retained by identity and never replayed automatically. Reports, plan history and cleanup receipts stay secondary in reader/technical surfaces. Optional managed-session cleanup preserves coordinators, shared/reused/moved sessions and roots with descendants. ## Lifecycle and recovery For one-time missions: - **Play** starts/resumes explicit mission intent; **Pause** parks mission dispatch/notifications and interrupts the observed active native family. - **Stop** is terminal and interrupts observed descendants deepest-first, then registered roots/coordinator, with bounded rechecks for parent wakes caused by child cancellation. - Native inbox cancellation uses exact authorized mission/subagent work. Pause preserves deferred results for explicit Play; Stop removes the identified queued work/results that would wake a stopped coordinator. - Per-target receipts record observed interruption and family coverage. Incomplete or unconfirmed interruption is shown as **Some sub-agents may still be running**, never as proven recursive suspension. - **Refresh** reconciles state first; an explicit refresh click may retry a still-unconfirmed lifecycle action under its original identity. Reconnect and ordinary display reads never resend mutations. - **Play** offers targeted recovery only when native observation establishes the applicable interrupted state, not merely because activity is unknown. Active background descendants retain the active/Pause presentation. Native interruption does not undo completed filesystem/tool effects. Resuming a paused mission lets the coordinator decide how to continue or re-delegate interrupted work; there is no hidden task replay engine. ## Recurring missions Recurring setup adds permanent instructions, a title, a daily local time/timezone and explicitly followed project conversations. Saving creates a paused schedule; Play arms it and Run now requests one finite passage. The OpenCode service/plugin owns scheduled passages while both CodeNomad UI and its intermediary backend are closed. One native Job per running schedule sleeps until the earlier of the next due time and one hour. On waking it rereads the clock/document and admits only the latest due civil day newer than the stored daily cursor; there is no backlog. Native execution terminal events also wake observation after a debounce, but are not settlement evidence. Before native effects, conditional storage records deterministic passage, coordinator and start-message identities. Lost replies reconcile those exact identities rather than creating new attempts. A passage archives only after the coordinator and descendants are inactive, inboxes are empty and no relevant native Form, permission, Shell or background subagent remains. Unknown coverage stays pending. Passages end as completed with a final report, native terminal failed, or ended-without-report/interrupted. Only completed passages advance followed-conversation cursors. Schedule history is bounded to 30 passages. Civil-time DST gaps/repeated times and entirely skipped days resolve deterministically. Pause/Stop cancel the schedule Job; those schedule actions do not prove an already-running passage family stopped. Stopped is terminal. After service restart, a desired-running schedule without its Job is **Interrupted**, awaiting explicit Resume. Resume reconciles pending work, never sends a continuation; a turn cut by restart settles interrupted/ended-without-report. ## Durability and UX Mission journals and schedule documents retain tasks, roles/profiles, results, plan revisions, controls, notifications and bounded history. Conditional writes and original request identities protect mutation retry. Authority, Location ownership, fresh per-send environment and connection/deletion fences are checked at admission. Ordinary native permissions govern tool use. UI remains compact and square, localized in all supported languages, keyboard-accessible and usable on touch/narrow/RTL layouts. Cache-first display reads revalidate on visible demand and native invalidation, without polling or clearing known data on uncertain recovery. Loading, missing capability and uncertain effects remain explicit. ## Shared integration scope The PR also includes supporting shared-runtime corrections; these are not limited to the Missions tab: - Session outcome and generation-recovery reconciliation retains uncertain interruption state across reconnects and checks native outcomes before clearing it. Native message pages clamp to the service's 200-message limit. - Pending-request recovery retains known requests through incomplete coverage and connection transitions; native ownership and deletion fences remain authoritative. - Keyboard offset and the compact composer help strip preserve reachable input controls without changing composer height limits. Touch overflow actions suppress duplicate compatibility clicks. - Workspace text saves enter the existing worktree mutation fence; sandboxed panel asset reads fence connection changes. Worktree scans retain validated family identity and custody. - The worker-based Git process runner uses an ESM-safe worker entry, and CLI service discovery shares bounded command deadlines. - Desktop/backend profile identity is explicit for Missions' owned storage. Profile-scoping signals are internal to that relationship, not permission for independently launched applications to inherit a profile. - Shared rendering/localization includes a Turkish tool-call label correction. - Session-list startup restoration exposes a settled signal (`stores/session-list-restoration.ts`, hooked in `session-api.ts` and `instances.ts`). Missions display reads wait for it so the session list loads first; it settles on success, failure, timeout or abandonment and is reset when a workspace is removed. Session-list components and styles are unchanged. These callers are included in integration review and their regression tests, rather than treated as an unchanged dependency surface. ## Current limitations - Editing/deleting recurring schedules is not implemented; those icons in the selected mission's action bar are disabled for schedules. - An uncertain one-time creation (lost reply after the request was sent) is held in window memory with no status endpoint; Create stays blocked for that window until reload. Definitive refusals before any native effect re-enable Create immediately. - Saving a brief from an existing recurring schedule is not implemented; the selected-mission action currently covers one-time missions only. - Task conversation lookup for older unbound native tasks reads a bounded coordinator-message window and recognized exact assignment evidence. Older/unrecognized delegations may have no shortcut; no conversation is guessed from titles or timings. - Running native calls may not expose their child ID until completion. The panel can instead identify a direct child by its first canonical assignment message, within a bounded direct-child page; unrecognized or out-of-bound children remain unlinked. - Native task profiles are recorded, but ordinary subagent launch still relies on the coordinator passing the declared agent/model/variant to OpenCode. Omitted model parameters can inherit the coordinator's model; profile storage alone does not enforce launch parameters. - One-time recursive interruption is best-effort and bounded, not a durable guarantee that every descendant stays stopped. Partial/unknown coverage remains visible. - Recurring Pause/Stop cancel scheduling, not guaranteed recursive suspension of a running passage. - Native qualification uses isolated services; mock-only and source-review evidence never substitute for real native acceptance. Final validation results are published separately in the PR reviews. - Managed-service restart and some lost-reply/clock-conflict/human-decision journeys still have offline-only evidence. - If an admitted passage start is removed by pruning before its lost durable write is reconciled, that original start identity can become unavailable for deduplication. ## References - [Mission contract](https://github.com/NeuralNomadsAI/CodeNomad/blob/experiment/missions-native-subsessions-20261003/dev-docs/MISSIONS.md) - [Recurring contract](https://github.com/NeuralNomadsAI/CodeNomad/blob/experiment/missions-native-subsessions-20261003/dev-docs/MISSIONS_RECURRING_SIMPLE.md) - [Native qualification](https://github.com/NeuralNomadsAI/CodeNomad/blob/experiment/missions-native-subsessions-20261003/dev-docs/MISSIONS_RECURRING_SIMPLE_QUALIFICATION.md) |