Commit graph

18 commits

Author SHA1 Message Date
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é
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é
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é
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é
56823af672
docs(help): explain how context flows through a mission (#1215)
A user asked whether sessions and sub-agents in a mission can use earlier sub-agent sessions and mission context. The Missions help now explains how context flows: the mission map as shared, durable memory; what the coordinator, native-subagent tasks, independent-session tasks and their helpers each receive; how results come back; how later tasks build on earlier ones (dependencies, reused conversations, existing independent conversations) and what compaction means for model context versus stored history; daily runs and follow-up missions; and on-demand search of earlier conversations through CodeNomad's session search tool.

The section (`#context-flow`, `#context-search`) is in all ten locales with each locale's UI labels; every statement is mapped to the code in the PR description.

Validation: help structure and browser checks pass (31/31, 47/47); two review rounds, the second with no findings.
2026-10-30 23:51:35 +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é
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é
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é
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é
cfd47f3af1
docs(help): state that running one-time missions continue without the backend (#1137)
## Summary

The Missions help (Lifetime and service restarts) said one-time
coordination depends on the CodeNomad backend and told users not to
expect new dispatch while it is closed. In native task mode, the
default, a running one-time mission keeps going without CodeNomad: the
coordinator, its native subagent tasks and the mission tools all run in
OpenCode.

The **CodeNomad backend** row now says so, and lists what does wait for
the backend: assigning tasks to independent sessions and delivering
their reports to the coordinator (already admitted independent sessions
keep running in OpenCode), missions the coordinator prepares, stopping a
retired task's conversations, recovery, Mission Control, and continuing
a one-time mission after an OpenCode service restart.

## Why this is accurate

- `opencode/missions/desktop-plugin.ts`: `followPresence(..., () =>
retainMissionWork(...))` keeps the Missions tools registered after
backend presence ends while a mission is active
(`missions/lifetime.ts`).
- Native declarations, reports and briefings write the plugin's own
journal. Only the transport calls cross the authenticated loopback
bridge (`sendMissionInput`): root creation, assignment prompts and
report notifications for independent tasks, `prepare`, `observeTask`,
`interruptTask`, recovery, lifecycle and cleanup
(`missions/control.ts`).
- `desktop-plugin-presence.ts` registers the tools only with presence,
so after an OpenCode restart they return once CodeNomad is back.

## Changes

- `docs/help/<locale>/missions.md`: the **CodeNomad backend** row in
English and the nine translations, reusing each locale's existing terms.

## Validation

- `node scripts/test-help.mjs`: full multilingual check, 9/9 pass.
2026-10-25 12:42:00 +01:00
Pascal André
1dabb5110f
docs(help): bring the user guide back in line with the current app (#1079)
docs(help): bring the user guide back in line with the current app

The help was written on 2026-10-14 and about forty user-visible changes merged since without touching it, so several pages described controls that no longer exist: Missions still listed four playbooks and a Save step for preferences that now save on each change, the depth editor kept its old names, notifications still had their former switches, and voice input pointed to a microphone button that is not in the composer.

Every English page was re-checked claim by claim against the current labels (English UI messages) and behaviour, then the nine translations were updated to match, using each locale's own UI labels. Notable corrections: Missions (Compare two models, autosaving defaults with Apply to / Global / Reset, Subsession nesting depth, prepared hand-offs, retired-task interrupts, the named causes of "Mission Control is unavailable", standalone server support); conversations and history (reasoning/tool durations, token lines adding up the cleanup selection, presets including Nothing, timeline markers following Message content, in-place reveal of long text, Open in session, focus after send, Queue message / Steer now, Retrying); settings (notification routing table and defaults, Stats, Mode, info tips, Server setting badge, localized native menus); shortcuts (72 actions, Clear shortcut, Ctrl/Cmd+K); voice (More actions entries, Save status); files (live Workspace refresh, Show 200 more); OpenCode, install, server and updates (2.1.1 recommended, OpenCode 2 beside an older installation, version card first, private loopback listener for Missions, preview releases offered once all builds are published); providers, integrations, browser, SideCars, desktop and remote pages. Translations also repaired older drift, mainly the project-tab closing passages.

The Chat settings table header (Default state, its title, item column, the thinking row and its accessible label) was still English in eight locales; it is now translated so the help can cite what users see. AGENTS.md now asks every PR changing user-visible behavior to update the affected help pages in the same PR, since CI validates structure and links but not whether the text still matches the UI; it also corrects the needs-input notification default. USER_HELP.md no longer claims native menus stay English.

Validation: npm run test:help (all ten locales, 25 pages, heading ids unchanged), a full help build, and the i18n and transcript-visibility unit tests.
2026-10-21 14:38:29 +02:00
Pascal André
db6685cb55
fix(workspaces): closing a project tab closes the project in CodeNomad (#970)
Some checks failed
Publish help / build (push) Has been cancelled
Publish help / deploy (push) Has been cancelled
## Problem

Closing a project tab only hid it in that window. The backend workspace
record kept routing the folder's events, notifications, auto-accept and
pending-request recovery, invisibly, until the palette-only **Stop
Workspace** command was run on the active tab. That command still showed
the V1 message "Stop OpenCode instance? This will stop the server."
although V2 has one shared OpenCode service, and it called
`debug.location.evict`, which (per OpenCode source) rejects pending
permissions, cancels pending Forms, fails plugin RPC and kills terminals
of that directory, even for sessions started elsewhere. OpenCode loads
locations on demand and unloads idle ones itself.

## Change

- **Closing a project tab** (×, Ctrl/Cmd+W) removes that tab's backend
workspace record. CodeNomad stops following it; sessions keep running in
OpenCode and reappear when the project is reopened.
- **Another tab of the same folder** has its own record and is
unaffected.
- **The same record shown in another window**: that window's tab closes
through the existing `workspace.stopped` broadcast, with an info
notification ("closed from another window"); the initiating window shows
nothing.
- **CodeNomad never evicts OpenCode locations**: eviction, its
serialization helpers and `locationOwned` bookkeeping are removed. A
failed launch now always removes its logical record (the retention only
existed to retry a failed eviction).
- **Stop Workspace removed** (command, confirm dialog, i18n keys in all
locales).
- Unchanged: opening an open project still offers a new tab (recent row)
or switching (switch button); temporary projects keep Keep / Discard /
Cancel; window close, app quit, reload, restore and backend shutdown
delete nothing; disconnected-instance dismissal stays local.
- Delete failure: error toast, record kept and reachable from the home
page, no automatic retry.

## Docs

Help in all 10 locales (`projects`, `index`, `desktop`, `server`,
`opencode`, `missions`), `AGENTS.md`, `CONTRIBUTING.md`,
`dev-docs/architecture.md`, `packages/server/README.md` and the
architecture-guide skill now describe the new contract.

## Validation

- UI store tests (`--conditions=browser`): app-tabs,
temporary-instances, workspace-list-reconciliation-fence,
instances-restore-ownership — 30/30.
- Server: manager + shared service 54/54; event ownership and workspace
identity suites pass.
- New real-component browser regression (click ×, Ctrl+W, Cmd+W): 3/3;
project restoration (Electron/Tauri) and new-instance launcher browser
suites pass.
- `npm run test:help` 9/9 (250 pages); UI, server and Electron
typechecks.
2026-10-15 15:14:29 +02:00
Pascal André
c0d53f13d4
docs(help): multilingual help site with in-app Help menu and palette entry (#859)
## Summary

A complete public CodeNomad help site in the app's ten languages,
published on GitHub Pages (https://neuralnomadsai.github.io/CodeNomad/)
and opened from the app.

- **Content:** 25 pages in 8 sections (Start, Workflows, OpenCode setup,
Remote access, Customize, Extensions, Desktop, Help), written for
OpenCode power users and covering the current version only. It includes
everything user-relevant from `README.md`, `packages/server/README.md`
(server, network, auth, TLS, Remote Control) and the user-facing
dev-docs, with flags, defaults, labels and safety boundaries checked
against source.
- **Languages:** en, fr, de, es, he (RTL), ja, ne, ru, tr, zh-Hans.
Interface labels reuse each locale's own app strings; native desktop
menu names, code, commands, flags, paths and anchors stay unchanged.
- **Site:** `scripts/build-help.mjs` renders
`docs/help/<locale>/<slug>.md` with translated chrome
(`docs/help/i18n/`), stable `{#id}` anchors identical across locales,
same-article language links with hreflang, a root language chooser, and
a first-party offline per-locale search (Ctrl/⌘+K, substring matching
for CJK, Devanagari and Hebrew, hidden without JS). No analytics or
remote resources.
- **Validation:** `npm run test:help` fails on any missing page or
string, heading drift, broken link/anchor/asset or invalid search
target. `.github/workflows/help-pages.yml` validates PRs and deploys
only from `dev`.
- **App entry points:** one `open-help` command for the palette and a
new **CodeNomad Help** item at the top of the Electron and Tauri Help
menus. It opens the help in the invoking window's UI language through
the existing external opener, falls back to the root URL for windows
without the handler, and reports failures without replay.
- Documents the strict boolean environment-variable rule from #959 and
the updater entry points from #960.

## Review

Delivered as a CodeNomad mission with independent review loops, each to
zero findings:
- English content audit (5 passes, 14 fixes) and technical gatekeeper (2
passes) before translation.
- After translation: QA, UX review, and a final multilingual gatekeeper
(35 translation/label findings fixed, then verified), plus a visual fix
for the search focus ring and a verified sync with #960.

## Validation

- `npm run test:help`: 9/9 (25 pages × 10 locales, strict, including
negative tests); `npm run build:help`: 250 pages.
- `node scripts/test-help-browser.mjs`: 44/44; Playwright: 500 page
visits at 390 and 1280 px, all locales, no overflow, no external
requests.
- UI help-command and desktop-updates browser tests 19/19; help URL unit
tests; Electron menu tests; UI and Electron typechecks; Tauri `cargo
test` 272/272.
2026-10-14 22:44:56 +02:00