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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
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
//! User actions issued through `cli_outbound` (ARCH §3.4 / §3.5).
//!
//! This root holds every **enablement predicate** (pure, no egui): whether an
//! action is offered for the current selection. The **short, piped, logged**
//! verbs — message/stop/scan and `bl` close/unclaim/create/update — live in
//! [`verbs`], which appends each outcome to `ops.jsonl` (§8.2, §15 Y16); the
//! **detached** `lernie prompt` (the new-root launch, §8.1) is now the one
//! [`start::execute_prompt`](crate::start::execute_prompt) path (Y17 moved it
//! onto `spawn_detached`, unifying the composer's new-prompt with the start
//! flow's final prompt — one detached, logged launch, no piped-and-drained
//! variant whose `Stream` drop would SIGTERM the loop on yog's exit).
//!
//! Predicate discipline (§8.2): **Stop** needs a Live/InFlight executor to
//! signal ([`stop_enabled`]); **Message** is the resume gesture and works on
//! *any* selected agent ([`message_enabled`], ARCH §2.9 — no resume verb);
//! **Close** needs the ball bound to a local workspace ([`close_enabled`] =
//! `JoinState::Bound`); **Unclaim**/release and **Move** need the same
//! ([`unclaim_enabled`], [`move_enabled`]); **Assign** needs a ready, unclaimed
//! ball ([`assign_enabled`] = `JoinState::ReadyStartable`) — each predicate
//! refuses exactly what the underlying `bl` verb would (§8.2/§3.5); **Scan** is
//! unconditional (offered for any focused workspace — no predicate). All are pure
//! functions of inputs and carry no egui dependency, reusable by any future
//! frontend. Per §3.5 the UI holds no persistent state — `ActionsState` is
//! in-memory only and discarded on exit.
use crate;
use crateJoinState;
use io;
/// Ephemeral action-surface state. Held in memory by the running
/// frontend and discarded on exit (ARCH §3.5: frontends hold no
/// persistent state).
/// True iff the new-prompt input has at least one non-whitespace
/// character. Pure derivation per §3.5; no I/O.
/// A new ball's Create & Start is offered iff its title is non-blank (§8.1): a
/// ball with no title has nothing to name the work. A distinct §3.5 rule from
/// [`new_prompt_enabled`] though the current bodies coincide (as [`close_enabled`]
/// / [`unclaim_enabled`] / [`move_enabled`] share `Bound`) — each names the rule
/// its `bl`/start path enforces. Covered here, not inlined in coverage-excluded
/// shell glue.
/// A composer draft is RAM until it is cleanly *deposited* (§5.3, STORIES S1): a
/// message clears its draft iff the verb both launched (`Ok`) and exited 0
/// ([`Outcome::ok`](verbs::Outcome::ok)) — every failure keeps the text so the
/// operator can retry. Pinned here as a covered predicate, never in coverage-
/// excluded shell glue, so a regression that ate the draft on failure fails a
/// test instead of slipping through.
/// True iff `selected_branch` names an agent (by id, §2.3) in `agents`
/// that is **live** — [`AgentState::Live`] or [`AgentState::InFlight`],
/// the two states where a driver holds the executor lock (§2.11). Stop
/// targets a live executor (§2.9), and it is wanted precisely during tool
/// execution (a `Live` agent between model calls), not only mid-model-call
/// — so both live states are stoppable. Returns `false` for `None`, for an
/// id not present, and for a `Quiescent` or `Stopped` agent (no executor
/// to signal).
/// Message is the resume gesture (§8.2, ARCH §2.9: no resume verb — the
/// deposit restarts a driver). Unlike [`stop_enabled`] it is *not* gated on
/// agent state: a Quiescent or Stopped agent is precisely what you message to
/// continue it. Enabled iff an agent is selected, present in `agents`, and the
/// composer text is non-blank. `false` for no selection, an id absent from the
/// set, or whitespace-only text.
/// Stop offers `--stop-children` iff `agent_id` has a descendant in the id set
/// (§8.2) — another agent whose id extends `<agent_id>-…` (the hyphenated
/// descent, §2.3). Pure over the ids; no lone agent offers it.
/// Close is offered iff the focused ball is bound to a local workspace — exactly
/// [`JoinState::Bound`] (§8.2, §3.5): the ball's claimant names a workspace here,
/// so there is a loop to deliver from.
/// Unclaim is offered iff the focused ball is bound to a local workspace —
/// [`JoinState::Bound`] (§8.2, §3.5). Release is `bl unclaim <id> --as <name>`.
/// Assign is offered iff the ball is **ready and unclaimed** —
/// [`JoinState::ReadyStartable`] (§8.2, §3.5: "ready ball → ▶ Start or Assign to
/// an existing workspace"). Assign binds an unbound ball, so a bound / blocked /
/// claimed-elsewhere / delivered ball refuses — exactly what `bl claim` refuses.
/// Move is offered iff the ball is bound to a local workspace —
/// [`JoinState::Bound`] (§8.2, §3.5). Move is unclaim-then-claim (release + assign
/// to another workspace): only a ball this yog owns can be re-homed, so an
/// unclaimed or claimed-elsewhere ball refuses (what `bl unclaim` would refuse).