fix(cli,plugins): resolve executed optional modules from the tool's own install (replaces #3954; security review in PR body)
Co-Authored-By: RuFlo <ruv@ruv.net>
Leftovers of #3817/#3822 and #3818/#3823.
Numbers from CLI JSON (#3822). JSON.parse('1e999') is Infinity; a count can
also be negative, fractional or absurd. Bounded where read with data/safe.ts
countOf / ratioOf and a new measureOf (finite, within +-1e12):
- secure.ts scanReader: every severity count and the total are at least
the listed findings, so a summary never reads CLEAN beside a listed
finding. A count or total the tool printed but no reader can trust stays
UNKNOWN (SecFindings.unreadable / isTotalUnknown), with the listed
findings only as its lower bound: "critical ?" / "high ≥2 ?",
"? findings (total unreadable)", COUNTS UNREADABLE. securityBand() is the
one rule for the band (views/bar.ts) and the menu badge (menu-style.ts):
it warns on high/critical ("N+" when anything is unknown) and on any
unknown count or total. Threat confidence via ratioOf, composition score via measureOf.
The verdict-exit judgement (#3934, SCAN_FOUND/countsWith) is unchanged.
- Same class swept in perf.ts, mh-lab.ts (labLines cells, gepa chars,
genome ids, receipts), neural.ts, memory-lines.ts,
data/evolve.ts, data/research.ts and data/cli.ts.
Mission resume (#3823). setPaused takes who resumed. The mission-resume
palette entry built inside Claude's console_run (state.control.viaModel)
resumes as 'model': hand-out counts are kept and an advisor stop-the-line
is not re-opened, so an exhausted task gets no new billed hand-out. The
person's resume (page buttons, palette, a typed /ruflo run) still gives
every task its hand-outs back.
Co-Authored-By: Ruflo & AQE
Claude-Session: https://claude.ai/code/session_01CGoza3gyxBjpaiMPhGHcHs
The CLI, two MCP tools and two plugin scripts imported or ran code found
relative to the cwd, which is the opened project:
- doctor (metaharness check), `metaharness` command and metaharness MCP
tools: plugins/ruflo-metaharness/scripts under the cwd;
- testgen MCP tools: plugins/ruflo-testgen/scripts under the cwd, reached
on every published install because that plugin is not bundled;
- init: <cwd>/node_modules/@claude-flow/codex;
- embeddings: <cwd>/node_modules, <cwd>/../node_modules and a fixed
/workspaces/claude-flow path for agentic-flow;
- ruflo-ruos ledger: the CLI's mcp-client resolved from the cwd;
- cost-tracker compact.mjs: @claude-flow/integration resolved from the cwd.
Each now resolves from the tool's own install (module location /
createRequire(import.meta.url)), plus the user's marketplace checkout for
the plugin scripts. The project remains the data/scoring target.
Test: optional-module-resolution-trust.test.ts (10 cases, all fail on main).
On a project whose policy state is large, every `ruflo mcp exec` waits on the
policy state lock and some calls fail with policy-state-lock-timeout
(#3164, #3892). Mission create made 15 sequential task_create calls; one
transient failure aborted the whole mission, cleanup cancels were
fire-and-forget (orphan pending tasks while the page said they were
cancelled), the CLI mission record was left planned, and the reason was never
shown. The outcome of a run that reports for itself (mission create, ADR
writes) never reached console_state.lastResult. resultOf also read the
`Parameters:` object the CLI prints before any `Result:` line as the answer.
- data/mcp-run.ts: read only after the CLI's own `Result:` line (string-aware
bracket matching); the failure reason from data/failure.ts. Transient only
on exactly policy-state-lock-timeout, the caller's own structured time
limit, or a non-zero exit with stdout and stderr both empty. Any answer,
any other error line (a denial mentioning a timeout, command not found)
and any host rejection are final.
- mission-create.ts: retries after 1, 2, 4 s. task_create has no request id:
after each wait, and once more after the last failed try (a lost answer),
the store is read for exactly this step's task (description and both
tags carry the new mission id; a 5 min skew window); one is used, several
stop the chain, an unreadable store stops a retry. Strict time: no call,
wait or read starts after its deadline (3 min, cleanup 2 min more), each is
cut to the time left, and a call the host never answers is let go then.
One create at a time per console; unique request ids. On abort: cancel
every open task of the mission in the store (not only acknowledged ones),
cancel the mission record (again at the revision a conflict names; also
when mission_plan is refused), then READ BACK the store and the mission
observation and report what they show, and what was left for lack of time.
- ActionSpec.run may answer a RunReport; the runner keeps it as the console
outcome when the run ends, unless the outcome slot was replaced meanwhile
(another action reported; then it goes to the Events feed). The
verdict-exit judgement in runner.ts is unchanged. Mission create and ADR
init/propose/status writes answer theirs.
- tests: real `ruflo mcp exec` transcripts (ruflo 3.56.1) as fixtures.
Co-Authored-By: Ruflo & AQE
Claude-Session: https://claude.ai/code/session_01CGoza3gyxBjpaiMPhGHcHs
Console 0.42.0: Overview "Grounding" line (on / not installed / installed, disabled / unknown), a default-on nudge setting that adds one fixed line pointing at search_ruvnet to the mission context when the brain is on and the mission names the ruvnet stack (through modelLine, nothing interpolated), and a default-off, dismissible once-per-session install hint. Mods 0.5.0: option `grounding` (default off) adds a read-only /ruflo-mods row.
Detect-only: never calls, installs, gates, blocks or copies anything of the third-party plugin; ruflo behaves identically without it (smoke steps console 20, mods 15).
Co-Authored-By: RuFlo <ruv@ruv.net>
- Sessions list starts folded; jumping from the queue opens it.
- First reads past a pass's byte budget wait for the next pass ("not read yet").
- A Codex sub-agent rollout's trailing parent session_meta is lineage, not a second identity.
- ADR/CHANGELOG/README say discovery spawns nothing; the only write is the four-count summary.
Co-Authored-By: RuFlo <ruv@ruv.net>
Needs-you queue (approvals, questions, failures, finished-unread) across Claude Code, Codex and Ruflo
sessions, a browser grouped by repository and worktree, and a preview of the selected session. Local,
read-only discovery (no model call, no process); ambiguous identity goes to an unassigned bucket;
finished work clears only after its preview was drawn; transcript text never reaches a model, the
state, the disk or a text answer. ruflo-mods gains an optional counts-only attention row.
Co-Authored-By: RuFlo <ruv@ruv.net>
- feat: skills name connector tools in plain language; allowed-tools with Claude Code namespaced identifiers removed so claude.ai and Cowork read them correctly
- feat: lifecycle skill covers run_complete; skills state real statuses, template ids, budget range
- feat: README examples per surface, auth and data section, troubleshooting
- fix: descriptions lead with what and when, no angle brackets
- chore: smoke asserts tool names, descriptions, README examples and directory bundle parity; directory bundle 0.2.0
Co-Authored-By: RuFlo <ruv@ruv.net>
- adr-scope: each `*` became a stacked `[^/]*`, so an ADR scope entry with
many stars hung the console (10 stars: 8-15 s, ~6x per star) during verify
and goal suggestions. A segment-wise glob matcher with the same semantics.
- ADR-481: a title over 120 is refused with the count at the proposal itself
(page field, adr-propose palette, console_run); a mission-objective draft
title that is shortened says so on the confirm card; cuts and counts are in
code points (no lone surrogates; 120 emoji read back whole).
- ruflo-swarm: .claude-flow/console/adr-digest.json (committable by a cloned
repo) was appended to every subagent prompt unframed. The reader now accepts
only the console's shape (statuses, file identity, washed text, no
duplicates), quotes each record's text with marker words neutralised, and
frames the block as project data. A model-level mitigation, not a boundary.
- What's new: plugin-name maps are null-prototype with own-key lookups;
__proto__/constructor/prototype never enter seen, toasted, pinned or
dismissed records.
Co-Authored-By: Ruflo & AQE
Claude-Session: https://claude.ai/code/session_01CGoza3gyxBjpaiMPhGHcHs
The console wrote every file through GNU-only argv: dd oflag=append
iflag=fullblock, dd conv=excl, install -D and cp --no-clobber. BSD dd,
install and cp (macOS) refuse all of them, so on a Mac events never
persisted ("345 waiting to write · write failed: the write exited 1"), ADR
initialise/propose never created a file (and blamed a race), exports and
first-time state files failed, the autopilot journal was never appended or
rotated, and its cross-session kill switch was never written.
- data/write-flavor.ts detects the platform once (uname -s, own 3 s
deadline, process.platform fallback) before the controller exists; every
flavor-dependent write awaits it and its builder requires the flavor.
- Linux keeps the GNU argv byte for byte.
- macOS/BSD use a frozen set of seven constant sh scripts with the path as
"$1" (and "$2" for the copy source), never interpolated: each sets its
umask, refuses a link or a non-regular target, uses noclobber for
exclusive creates and creates missing folders. smoke.sh enforces the set.
- adr.ts reports the real write failure; worktree /proc specs skip off Linux.
Co-Authored-By: Ruflo & AQE
Claude-Session: https://claude.ai/code/session_01CGoza3gyxBjpaiMPhGHcHs
- AgentDB mod panel read updatedMs through countOf, whose 1e12 cap (added
in 44b157b52) clamps every epoch-ms time to 2001-09-09: "written 9161d ago
(an earlier session)" for a file written minutes before. Times are read as
times; implausible ones (before 2000, more than a day ahead) read n/a.
- Overview/Swarm/Workflows/status bar/topology credited a swarm with the whole
agent store ("running · max 3 · 259 agents" for a swarm with 0 agents) and
disagreed on its status (running vs finished). One helper
(data/swarm-status.ts) gives each page the swarm's own listed/found count
and its age; "stale" only on evidence (old record, no busy member, no
recent member). An empty fresh swarm is still its own Workflows run; the
roster of unlisted agents keeps the run id saved views and pins used.
- mission-goal's label said "(nothing is written)" but it is a write action;
perf-bottleneck/optimize were gated as delete (needs full) because their
note said the probe file is "removed". Labels and notes now match the gate.
Co-Authored-By: Ruflo & AQE
Claude-Session: https://claude.ai/code/session_01CGoza3gyxBjpaiMPhGHcHs
security defend / scan / channel-scan / scan-plan and the metaharness alert
scripts exit non-zero to say "found something" while printing a valid JSON
verdict. The runner treated every non-zero exit as a failure, so exactly when
a safety check caught something, Claude (console_run / console_state) was told
it failed and got no verdict. Failure reasons took the first stderr line, which
on many installs is "[WARN] Skipped helper auto-refresh", hiding the [ERROR].
- ActionSpec.findings = { exits, isAnswer, found } per entry, taken from the
CLI source: such an exit is an answer only when stdout holds a complete
answer of that command's shape, judged on its top-level keys (a hostile span
quoting "[ERROR]" or a nested failure cannot flip it), the answer says it
found something, and nothing outside it (trailing stdout, stderr) reports a
failure. A banner object before the verdict is skipped.
- The one-line outcome carries the verdict ("answered: UNSAFE · 2 threats").
- data/failure.ts: one failure-reason helper (wrapped MCP isError, top-level
error, [ERROR] line, error-naming line; [WARN]/[INFO]/Parameters skipped),
used by the runner, CLI probes and mission verify notes.
Co-Authored-By: Ruflo & AQE
Claude-Session: https://claude.ai/code/session_01CGoza3gyxBjpaiMPhGHcHs
task_update stored any string as a task's status, so an agent writing
"complete" left a finished task every reader misread: the ruflo-console
Automation kanban listed it under PENDING (offering assign/cancel), and
Mission Control derived it as ready, so nextTask() would hand finished work
to Claude again as a new billed turn.
CLI (mcp-tools/task-tools.ts)
- statusOf(): trimmed, case-insensitive, aliases complete/done/canceled/
running/in-progress; anything else (incl. "" and prototype keys) is
refused with the allowed list and the task is left unchanged.
- No schema enum on purpose: the HTTP/WebSocket registry validates enums
before the handler, which would refuse aliases there but not over stdio.
- Completing through task_update now finishes the task as task_complete
does (progress 100, completedAt, worker idle and counted once per
hand-out); cancelling frees workers as failing already did. Worker
effects run after the task store is saved.
Console
- taskStatusOf() reads the spellings older CLIs already stored; used by the
kanban lanes and verbs, Mission Control derive (no re-dispatch), mission
cancel, workflow links and the workflow guide.
Co-Authored-By: Ruflo & AQE
Claude-Session: https://claude.ai/code/session_01CGoza3gyxBjpaiMPhGHcHs
A refused mod or a permission deny has no action, so it held 'n to approve' up
with nothing to press. The band, menu badge, change notices and the Approvals
header now count waitingApprovalsOf(); notices stay listed, named apart.
Co-Authored-By: RuFlo <ruv@ruv.net>
- A Yes runs only the card it was pressed on: every pending card has an id,
the Yes / Always allow / Always accept buttons carry it, runner.confirm(seen)
runs nothing (and says so) when another card took its place. A card Claude
raised is re-checked against the level and Stop when it lands and when the
Yes arrives.
- Everything returned to the model goes through modelLine (escapes, then
control and invisible characters removed, cut, withheld whole when it holds
a secret shape or an invite code) and every answer leaves through
modelLines; a version such as v2.1.0-beta.3 is not an invite.
- ESCAPES is linear: an unterminated OSC ends at the next introducer or the
end of the text (7.7 s on the old pattern became milliseconds on 1 MB).
- Mission state/status, hive role, audit severity and Settings plugin names
that are Object.prototype keys read as unknown (own()).
- Approvals and a mission objective wrap at 80 columns instead of being cut
with an ellipsis (#3899).
Specs ported from #3820, #3821 and #3822, adapted to main.
Co-authored-by: Dragan Spiridonov <spiridonovdragan@gmail.com>
Co-Authored-By: RuFlo <ruv@ruv.net>
Read ~/.grok/sessions/<urlencoded-cwd>/<id>/usage.json (GROK_HOME override)
into the multi-provider ledger alongside Claude and Codex. One row per turn
when turns[] is present, else session totals; cached input treated as a subset
of inputTokens. Add grok-4.7-build / grok-4.6-build / grok-4.7 / grok-4.6 and
family fallback prices from docs.x.ai (Build Fast rates marked approx).
With the ruflo CLI option on npx, a confirmed action ran a cold online npx under the 90 s default and aborted with 'still running after 90000ms'. Resolve ruflo on PATH, a project-local bin or the cached npx copy first; give a cold download a 300 s limit and a first-run label; turn a timeout or ENOTCACHED into an error naming the fix. Console 0.40.1; ADR-407 update.
Co-Authored-By: RuFlo <ruv@ruv.net>
Default-off Settings rows: a read-only claude -p consult (asked first, exact argv, turn budget, mission spend cap) before a plan locks, when the same check fails twice in a row, and before done; a third failure pauses the mission. Honest about not being the in-session advisor tool. Console 0.40.0.
Co-Authored-By: RuFlo <ruv@ruv.net>
The mission goal was cut at 500 characters and drawn on one clipped line; the host Input is one line and hides the rest. A live mirror
under every field shows all lines, views draw text in full with an explicit marker, limits are checked before sending with the exact
count, and every silent slice/plain cap on typed text is removed or made a counted refusal. console 0.39.1.
Co-Authored-By: RuFlo <ruv@ruv.net>