Commit graph

61 commits

Author SHA1 Message Date
Pascal André
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.
2026-11-04 17:59:56 +01:00
Pascal André
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 (a5efdefc8) made Play default the shared service supervision
("Keep OpenCode running") to ON. Four routes called
`supervision.missionPlay` before dispatch: one-time Play and Resume,
unified schedule Play, and recurring Play. On any host where supervision
cannot be acquired, that call throws. Examples: an unsupported platform,
OpenCode not running as the registered native service, a wrapper
executable, or a namespace that cannot be read. The route then answered
a codeless 503, and the Mission never started. The panel kept the
request as unconfirmed, and no read could ever settle it.

## Changes

- **Missions no longer touch supervision.**
- The one-time control route, the unified schedule route and the
recurring Play preparation have no supervision dependency, and the HTTP
server no longer wires one into them.
- The supervision-only human check on one-time Play is gone. Fenced
connection and workspace checks still refuse before dispatch, and
schedule and recurring Play keep their own human proofs.
- `ServiceSupervisionSettings.missionPlay` and `firstPlay` are removed.
- **Supervision is voluntary.**
- A never-chosen preference is OFF, and only the Settings switch turns
it on.
- A stored ON stays ON: no migration and no uninstall. Retained
`first-play` receipts still parse.
- Settings drops **Keep off at first Mission Play**, and its info text
no longer refers to Missions in any of the ten locales.
- **Lifecycle controls** (`components/mission-lifecycle-controls.tsx`):
when a re-read shows the exact request saved with nothing pending, the
request is settled without a resend. A missing control never settles a
request, and durable partial controls keep their explicit
refresh-and-resend path.
- **Docs**: the dev-docs and the help pages (all ten locales) describe
supervision as a standalone OpenCode setting that stays off until turned
on. Neither the Missions docs nor the supervision docs mention a link
between the two.

## Validation

- Route tests now check that Play never calls an injected supervision
dependency that would throw: one-time Play and Resume, schedule Play and
recurring Play.
- **RED** on the previous code (503, or a supervision call), GREEN now.
- Lifecycle browser tests (a lost reply whose saved control confirms a
Play or a Resume): **RED 0/2** on origin/dev, GREEN now.
- Service-supervision browser tests (a never-chosen preference shows
only Off, in all ten locales): **RED 0/2**, GREEN now (12/12).
- Server Missions and supervision suites: 1802 tests, 1799 passed, 3
platform skips, 0 failed.
- Missions browser suites: 511 of 513 passed.
- The two `mission-panel-focus` unfocused-window preconditions fail
identically on the base.
- Server and UI typecheck, Missions plugin bundle, help checks 31/31,
`git diff --check`.

## Limits

- The Resume symptom was reported as "hangs in *Waiting to run…*".
Resume went through the same `start` path, so on affected hosts it
failed for the same reason. However, no isolated OpenCode service was
available to reproduce that exact display.
- The supervision resolvers still accept a connection-pinning argument
that no caller passes now. That is left for the planned supervision
simplification.
- A control refused before dispatch for another reason (changed
connection or workspace) still answers a codeless error, which the panel
keeps as unconfirmed.
2026-11-04 00:12:04 +01:00
Pascal André
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.
2026-11-02 22:49:26 +01:00
Pascal André
6b8baf2320
feat(missions): bound current recurring passage display reads (#1269)
Some checks are pending
Publish help / build (push) Waiting to run
Publish help / deploy (push) Blocked by required conditions
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 `a7ad14f06`: 255 contracts including all 56
promotion cases, four browser cases including both real 31-second
page-back cases, original pending/fresh/expired matrix and additional
assignment/deadline probes all pass.
- Independent integration reviews cover scoped inventories, preserved
UX/fences and the test-only corrections. Detailed English reports are
linked in comments; their exact tree/runtime scopes remain explicit.
- Earlier full UI/server, native 2.2.3 and source-cliff qualification
remains prior evidence. The separately documented wider
offscreen-grandchild failure and Windows headless unfocused-window setup
limitation are not silently waived or claimed green.

Ten-locale help and developer contracts are included. All
checkouts/dependencies/evidence remain separate and retained; no shared
daemon restart, OpenCode modification, borrowed dependencies or live
mission controls were used.

**Explicit approval is still required before merging #1269. Approval to
administratively merge #1271 did not authorize this merge.**
2026-11-02 14:17:44 +01:00
Pascal André
fb4e244b5a
fix(missions): stabilize refresh and simplify task continuation (#1271)
Some checks are pending
Publish help / build (push) Waiting to run
Publish help / deploy (push) Blocked by required conditions
## 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.
2026-11-02 11:17:10 +01:00
Pascal André
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.
2026-11-02 10:47:02 +01:00
Pascal André
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.
2026-11-02 01:58:36 +01:00
Pascal André
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.
2026-11-01 14:40:24 +01:00
Pascal André
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.
2026-11-01 10:11:00 +01:00
Pascal André
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.
2026-11-01 07:10:26 +01:00
Pascal André
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.
2026-11-01 05:12:23 +01:00
Pascal André
2b57a67df2
feat(missions): add byte-bounded panel page reads (#1246)
Some checks are pending
Publish help / build (push) Waiting to run
Publish help / deploy (push) Blocked by required conditions
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.
2026-11-01 03:39:14 +01:00
Pascal André
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.
2026-11-01 01:58:10 +01:00
Pascal André
da8607d49e
feat(missions): send compact mission maps to the panel (#1237)
Some checks are pending
Publish help / build (push) Waiting to run
Publish help / deploy (push) Blocked by required conditions
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.
2026-10-31 19:22:10 +01:00
Pascal André
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.
2026-10-31 14:56:03 +01:00
Pascal André
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`.
2026-10-31 07:49:59 +01:00
Pascal André
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.
2026-10-31 06:27:51 +01:00
Pascal André
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.
2026-10-31 06:21:12 +01:00
Pascal André
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.
2026-10-31 04:50:52 +01:00
Pascal André
c17752e9b7
feat(missions): route human questions through the coordinator and record who decided (#1227)
Some checks are pending
Publish help / build (push) Waiting to run
Publish help / deploy (push) Blocked by required conditions
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.
2026-10-31 03:19:24 +01:00
Pascal André
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.
2026-10-31 02:01:57 +01:00
Pascal André
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.
2026-10-31 01:04:10 +01:00
Pascal André
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.
2026-10-31 00:45:20 +01:00
Pascal André
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.
2026-10-31 00:33:06 +01:00
Pascal André
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.
2026-10-30 23:44:20 +01:00
Pascal André
f3246e27c3
fix(settings): keep Store images once working in projects with their own experimental block (#1200)
Some checks are pending
Publish help / build (push) Waiting to run
Publish help / deploy (push) Blocked by required conditions
## 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`
2026-10-30 03:04:15 +01:00
Pascal André
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.
2026-10-30 01:06:41 +01:00
Pascal André
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 `681b85486`
(pre-existing, investigated separately).
2026-10-28 20:16:17 +01:00
Pascal André
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.
2026-10-28 15:44:19 +01:00
Pascal André
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).
2026-10-28 14:35:25 +01:00
Pascal André
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.
2026-10-28 13:56:25 +01:00
Pascal André
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.
2026-10-28 12:27:05 +01:00
Pascal André
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.
2026-10-28 11:59:23 +01:00
Pascal André
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.
2026-10-28 10:37:25 +01:00
Pascal André
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.
2026-10-28 10:13:37 +01:00
Pascal André
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.
2026-10-27 14:51:10 +01:00
Pascal André
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.
2026-10-27 13:03:36 +01:00
Pascal André
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.
2026-10-27 05:05:26 +01:00
Pascal André
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.
2026-10-25 18:32:03 +01:00
Pascal André
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.
2026-10-25 02:10:47 +02:00
Pascal André
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.
2026-10-21 16:59:43 +02:00
Pascal André
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
2026-10-21 12:17:49 +02:00
Pascal André
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.
2026-10-20 13:55:12 +02:00
Pascal André
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.
2026-10-19 16:15:50 +02:00
Pascal André
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.
2026-10-19 14:52:29 +02:00
Pascal André
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.
2026-10-19 01:23:35 +02:00
Pascal André
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.
2026-10-19 00:34:27 +02:00
Pascal André
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.
2026-10-18 22:28:39 +02:00
Pascal André
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.
2026-10-14 14:45:38 +02:00
Pascal André
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)
2026-10-11 14:55:49 +02:00