Commit 7eb145a
authored
* feat(agentos): FM cockpit AgentCard control surface + forward lifecycle-intent seam (#14611)
Layer 1 of the B4 per-agent control cluster: control surface + forward intent seam.
- AgentCardController fires one lifecycleIntent {action, agentId}, reading the durable agentId
from the per-card state provider, and stops at the B4/C2 boundary (never imports the fleet
bridge; the cockpit->lifecycle round-trip is the Lane-C contract).
- AgentCard gains the fm-card-controls slot (start/stop/restart) the card anatomy already
anticipated; per-verb availability binds to session state.
- Spec locks the forward seam: a control fires exactly one lifecycleIntent carrying verb +
agentId, and the card never calls the bridge.
Honest round-trip states (pending -> settled/rejected/unauthorized; stale-pending per ADR-0032
2.2.1) are the leaf's core ACs and follow once the Lane-C back-signal contract is fixed with
Euclid. This commit is intentionally the forward-seam layer only, not a full #14611.
* feat(agentos): FM cockpit whole-fleet start control — intent-only, per accepted B4/C2 cut (#14611)
Extends the B4 control surface to the whole-fleet verb (SSOT §01 one-click morning start).
- FleetCockpitController.onStartFleet fires one lifecycleIntent {action:'start', scope:'fleet'} and
stops — intent-only, per the accepted B4/C2 cut (Lane-C owns the round-trip).
- FleetCockpit re-lays vbox: a control bar (fm-fleet-start button) over the SSOT §01 fleet/activity
split, which stays intact in an inner hbox (1.55fr / 1fr); the activity-stream reference +
loadActivity binding are preserved.
- Spec locks the forward seam: the bar fires exactly one fleet-scoped lifecycleIntent, cockpit never
calls the bridge.
Honest round-trip states stay the C2-back-signal-gated increment (uniform across per-agent + whole-
fleet). Forward-seam layer only.
* style(agentos): FM cockpit control SCSS + repair the zone divider under the new vbox nesting (#14611)
- fm-card-controls cluster geometry (AgentCard.scss); fm-cockpit-bar + fm-fleet-start (FleetCockpit.scss); token-only (var(--fm-*)).
- Repair: the whole-fleet vbox re-layout nested FleetGrid under a new fm-cockpit-body container, so the '> .fm-fleet-grid' zone-divider selector stopped matching — re-scoped to '.fm-cockpit-body > .fm-fleet-grid' + named the inner container.
- Honest-state visuals follow with the C2-gated state machine; browser NL-verify is the completion AC (post-C2).
* feat(agentos): FM cockpit honest round-trip control states — pending/reject rendering per the B4/C2 contract (#14611)
Renders #14611's honest round-trip states off two provider fields Lane-C sets (per the contract proposed on the ticket):
- pendingAction (String|null): while set, EVERY verb is disabled (no second intent mid-round-trip) and the in-flight verb renders pending — never optimistic success.
- controlReason ({action, kind, reason}|null): rejection/unauthorized/timeout renders its reason inline.
Controls restructured to a verb row + a status line; the card only RENDERS the fields — Lane-C (C2) owns setting them via the round-trip. Spec covers: forward seam (fixed for the new nesting), pending disables all verbs + renders pending, reject renders the reason. SCSS for the verb row + status line (token-only).
Render side against the proposed contract; C2 wiring lands when Euclid acks the field shapes. Not a full #14611 (end-to-end round-trip pends C2).
* feat(agentos): B4 control status prioritizes pending over a stale reason — clear-on-new-intent nuance, render side (#14611)
Euclid acked the two-field B4/C2 contract (pendingAction + controlReason) with a clear-on-new-intent nuance: C2 clears a stale controlReason when a new accepted intent enters pending. This makes the render defensive from B4's side too — the status line prioritizes pendingAction over controlReason, so a new attempt never shows a stale rejection even if the clear has a gap. Spec adds the coexist assertion (both set → pending wins).
* fix(agentos): FM control cluster — one power toggle (start|stop by state), not a start+stop pair (#14611)
Operator UX catch: a play button beside a stop button is one action pretending to be two — a resident is either off (only start is valid) or running (only stop is valid). Rendering both and disabling the invalid one is bloat, not safety; the invalid one is never legitimately clickable.
- Single power toggle: ▶ start when state==='off', ■ stop when running — onToggleLifecycle derives the verb from state and fires the same lifecycleIntent {action, agentId}.
- restart shows only while running (hidden when off — a stopped resident starts via the toggle; restart of a stopped agent is just start).
- Honest-state + pending disable unchanged. Spec rewritten: off→toggle-is-start + restart-hidden; running→toggle-is-stop + restart-shown; restart fires restart; pending disables controls.
Principle banked: model state→valid-actions and render only what's reachable per state.
* fix(agentos): FM cockpit roster = the 7 real agents — drop the fabricated 'Kepler' entry (#14560)
Operator caught a fabricated agent rendered in the fixture-fed cockpit: 'Kepler' (agentId neo-clio-limit, sonnet-5, avatar = the org logo) is not a real identity — who_is_online has no such maintainer. It was invented to demo the 'limited' state.
Removed → the roster is now exactly the seven real cross-family agents (Euclid, Vega, Ada, Grace, Mnemosyne, Clio, Gemini). JSDoc updated: identities + avatars are real; state + lane-line are the illustrative snapshot until the live source wires; no invented agents.
Also corrected the #14592 density evidence, which had over-counted the roster as 12/~8 (folding in the operator, test identities, and the legacy Opus-4.7 node) — the real agent count is 7. Lesson: V-B-A fixture rosters + counts against the real agent set.
* fix(agentos): FM cockpit stacks activity below the fleet (full-width bottom), not beside it (#14560)
Operator UX catch: the live-activity feed rendered as a right-hand column (an inner hbox beside the grid). It belongs full-width at the BOTTOM. Flattened FleetCockpit to a single vbox [control-bar | fleet grid | activity feed] — the grid now gets the full width (more ranked-card columns) and the feed is the full-width bottom strip. Zone divider moved from grid right-border to grid bottom-border.
Note: this changes the SSOT §01 'beside' arrangement to 'below' per operator direction — the design SSOT (fleet-manager-cockpit-plan.html) should be updated to match; flagged to Grace.
* fix(agentos): attach FM control controllers as bare class refs — {module:X} fails bare-test class resolution (#14611)
The 3 FM control specs failed with 'Class AgentOS.view.fleet.FleetCockpitController does not exist' at beforeSetController. Cause: I attached the controllers as controller: {module: X} — that object form routes through ClassSystemUtil.beforeSetInstance's Object-path (Neo.create({module: X, ...})), which fails to resolve the class in the bare unit-test registry. The idiomatic bare-class form (controller: X — as the working AgentOS Viewport uses controller: ViewportController) hits the NeoClass path (Neo.create(class, ...)) that resolves in both app + test. Cascade fixed: controller now resolves → controls render → down() finds the verb row + status.
* fix(agentos): B4 specs query controls by reference + controller-isolation for whole-fleet (#14611)
The 3 FM control specs failed on test-shape, not product bugs (the live app renders fine):
- down({cls:[...]}) returned null — a cls-array query isn't exact-match once baseCls is appended. Added reference: 'control-verbs' / 'control-status' to the controls slot + query by reference (the reliable Neo idiom; getReference is itself down({reference})).
- Neo.create(FleetCockpit) throws bare — it composes FleetGrid → the whole card wall, too heavy to instantiate without the full app (the existing loadActivity specs mock for the same reason). The whole-fleet test now uses controller isolation (spy component + onStartFleet), matching that pattern.
* fix(agentos): B4 control disable-guard robust to unset pendingAction + toMatchObject for injected source (#14611)
The 2 red B4 specs surfaced one real product bug + one over-strict assertion:
- disabled: data => data.pendingAction !== null wrongly disabled every control when pendingAction is undefined — the state a partial-seeded card provider carries (the factory seeds display fields; Lane-C sets pendingAction later). Boolean(data.pendingAction) is robust to null AND undefined: disabled iff a verb is genuinely in flight.
- Neo's component.fire() injects source (the component id) into the event payload, so toEqual on the fired lifecycleIntent was too strict. toMatchObject asserts the intent fields and still checks the fired count.
* feat(agentos): wire cockpit controls to the C2 adapter — per-card intents now drive the honest round-trip (#14611)
B4's control cluster fired lifecycleIntent into the void and C2's adapter (#14894, merged) was never called — two green fragments that didn't add up to a working control. This closes the seam:
- Each FleetGrid-built card carries listeners:{lifecycleIntent:'onAgentLifecycleIntent'}, which resolves UP the controller chain (card → grid[no controller] → cockpit) to FleetCockpitController — the composition root that may know transport.
- onAgentLifecycleIntent resolves the firing card from the event source (Neo.getComponent) and hands the intent + that card's state.Provider to handleFleetLifecycleIntent. The adapter calls the registry bridge and writes honest pending/settled/rejected state back onto the provider the card renders; no bridge → fail-closed unauthorized, never optimistic.
Delivers the cornerstone: start/stop/restart a single agent from its card. Fleet-wide 'Start morning fleet' fan-out is the next slice (onStartFleet still fires the fleet-scope intent).
* feat(agentos): fleet-wide morning-start fan-out — the cockpit bar now drives every card (#14611)
onStartFleet fired a fleet-scope lifecycleIntent that nothing consumed — the 'Start morning fleet' button was dead. It now fans out: the cockpit (composition root) enumerates the rendered cards via its fleet-cards reference (FleetGrid has no controller, so that reference resolves up here) and hands each a start intent + its state.Provider to the C2 adapter. Every resident drives its own honest round-trip; the collapsed-idle fold is filtered by ntype; no bridge → per-card fail-closed, never an optimistic fleet-wide success. Completes the cockpit control surface: per-card AND whole-fleet start now functional end-to-end.
* fix(e2e): scope Fleet cockpit lifecycle NL assertions to lifecycle verbs — the boot activity-poll is orthogonal (#14611)
The FleetCockpitLifecycleNL whitebox-e2e went red (both tests) after the Fleet cockpit became the default boot-mounted keeper-view (14846/14847): its always-on activity stream fires a read-observe fleetActivity poll on boot, which the recording bridge captured alongside the FleetSettingsPanel Start -> the all-requests count was 2 while the assertion expected 1. Scope the count to lifecycle verbs (start/stop/restart); the activity poll is orthogonal and credential-free, and the minimal-payload/no-credential assertion still guards the one lifecycle call. Verified green: 2 passed on the e2e config. This PR's B4 diff is innocent of the regression (V-B-A: the diff touches no fleetActivity/loadActivity/default-view path) — this restores the cockpit-lifecycle whitebox-e2e that the earlier keeper-view merge reddened.
* feat(agentos): B4 honest-state contract — unauthorized disables the cluster, timeout renders stale-pending (#14611)
Completes the #14611 honest-state matrix from Euclid's re-reviews. The C2 adapter already writes unauthorized/timeout; B4 now renders them per the accepted contract:
- unauthorized -> the verb cluster DISABLES with its reason (disabled = pendingAction OR controlReason.kind==='unauthorized'); no retrying into a closed door — a reason beside a live button was the gap.
- timeout -> stale-pending: '<verb>... stale — no response', an unfinished '...' NOT a resolved warning, because the outcome is UNKNOWN (the verb may still be running); retry stays open.
New unit test connects the adapter's unauthorized/timeout provider writes to the disabled-with-reason / stale-pending card render — the coverage Euclid named as missing. Cards stay intent-only; no faked success.
1 parent c691698 commit 7eb145a
10 files changed
Lines changed: 442 additions & 31 deletions
File tree
- apps/agentos/view/fleet
- resources/scss/src/apps/agentos/fleet
- test/playwright
- e2e/agentos
- unit/apps/agentos/view/fleet
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
2 | | - | |
3 | | - | |
4 | | - | |
5 | | - | |
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
6 | 8 | | |
7 | 9 | | |
8 | 10 | | |
| |||
42 | 44 | | |
43 | 45 | | |
44 | 46 | | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
45 | 53 | | |
46 | 54 | | |
47 | 55 | | |
| |||
56 | 64 | | |
57 | 65 | | |
58 | 66 | | |
59 | | - | |
60 | | - | |
61 | | - | |
62 | | - | |
63 | | - | |
64 | | - | |
65 | | - | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
66 | 76 | | |
67 | 77 | | |
68 | 78 | | |
69 | 79 | | |
70 | | - | |
71 | | - | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
72 | 83 | | |
73 | 84 | | |
74 | 85 | | |
| |||
111 | 122 | | |
112 | 123 | | |
113 | 124 | | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
114 | 191 | | |
115 | 192 | | |
116 | 193 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
2 | | - | |
3 | | - | |
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
4 | 6 | | |
5 | 7 | | |
6 | | - | |
7 | | - | |
8 | | - | |
9 | | - | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
10 | 12 | | |
11 | 13 | | |
12 | 14 | | |
| |||
16 | 18 | | |
17 | 19 | | |
18 | 20 | | |
19 | | - | |
20 | | - | |
| 21 | + | |
21 | 22 | | |
22 | 23 | | |
23 | 24 | | |
| |||
66 | 67 | | |
67 | 68 | | |
68 | 69 | | |
69 | | - | |
70 | | - | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
71 | 81 | | |
72 | 82 | | |
73 | | - | |
| 83 | + | |
74 | 84 | | |
75 | 85 | | |
76 | 86 | | |
77 | 87 | | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
78 | 99 | | |
79 | 100 | | |
80 | 101 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
205 | 205 | | |
206 | 206 | | |
207 | 207 | | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
208 | 212 | | |
209 | 213 | | |
210 | 214 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
49 | 64 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
6 | 16 | | |
7 | | - | |
| 17 | + | |
8 | 18 | | |
9 | 19 | | |
0 commit comments