feat(desktop,buzz-acp): add harness-agnostic config bridge and setup-listener mode#1411
Open
wpfleger96 wants to merge 27 commits into
Open
feat(desktop,buzz-acp): add harness-agnostic config bridge and setup-listener mode#1411wpfleger96 wants to merge 27 commits into
wpfleger96 wants to merge 27 commits into
Conversation
wpfleger96
added a commit
that referenced
this pull request
Jul 1, 2026
Seven Playwright-captured screenshots walking the CreateAgentDialog gate firing and clearing, plus the extracted Edit dialog fields. Added agent-readiness-screenshots.spec.ts to the smoke project in playwright.config.ts (alongside config-bridge-screenshots.spec.ts). Shot 06 (provider-mode bypass) is not captured: the mock bridge's discover_backend_providers always returns [] so the Run-on selector never renders in the test fixture. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wpfleger96
pushed a commit
that referenced
this pull request
Jul 1, 2026
wpfleger96
pushed a commit
that referenced
this pull request
Jul 1, 2026
wpfleger96
added a commit
that referenced
this pull request
Jul 1, 2026
Seven Playwright-captured screenshots walking the CreateAgentDialog gate firing and clearing, plus the extracted Edit dialog fields. Added agent-readiness-screenshots.spec.ts to the smoke project in playwright.config.ts (alongside config-bridge-screenshots.spec.ts). Shot 06 (provider-mode bypass) is not captured: the mock bridge's discover_backend_providers always returns [] so the Run-on selector never renders in the test fixture. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
4255b29 to
c5c8671
Compare
wpfleger96
added a commit
that referenced
this pull request
Jul 1, 2026
Seven Playwright-captured screenshots walking the CreateAgentDialog gate firing and clearing, plus the extracted Edit dialog fields. Added agent-readiness-screenshots.spec.ts to the smoke project in playwright.config.ts (alongside config-bridge-screenshots.spec.ts). Shot 06 (provider-mode bypass) is not captured: the mock bridge's discover_backend_providers always returns [] so the Run-on selector never renders in the test fixture. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
c5c8671 to
e71375a
Compare
Collaborator
Author
Shot 01 — Create: buzz-agent selected, provider empty → required marker shown, save allowedLLM Provider field shows required marker ( Shot 02 — Create: buzz-agent + anthropic, model empty → required marker shown, save allowedProvider selected, model field shows required marker; Create agent button still enabled. Shot 03 — Create: buzz-agent + anthropic + model set, API key missing → amber required row, save allowed
Shot 04 — Create: all required fields satisfied → Create button enabledProvider, model, and credential all set; button enabled as expected. Shot 05 — Create: claude (CLI-login) runtime → no provider/model required, button enabledClaude uses out-of-band auth; provider/model fields hidden; button immediately enabled. Shot 07 — Edit: goose runtime, extracted provider + model fields shownEdit Agent dialog showing the shared Shot 08 — Create: goose runtime, provider empty → required marker shown, save allowedSame required-marker behavior as buzz-agent (both support provider selection). |
wpfleger96
pushed a commit
that referenced
this pull request
Jul 1, 2026
9c9065a to
f2f7151
Compare
wpfleger96
added a commit
that referenced
this pull request
Jul 1, 2026
Seven Playwright-captured screenshots walking the CreateAgentDialog gate firing and clearing, plus the extracted Edit dialog fields. Added agent-readiness-screenshots.spec.ts to the smoke project in playwright.config.ts (alongside config-bridge-screenshots.spec.ts). Shot 06 (provider-mode bypass) is not captured: the mock bridge's discover_backend_providers always returns [] so the Run-on selector never renders in the test fixture. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
4ff2ef0 to
ef83d5e
Compare
wpfleger96
added a commit
that referenced
this pull request
Jul 1, 2026
Seven Playwright-captured screenshots walking the CreateAgentDialog gate firing and clearing, plus the extracted Edit dialog fields. Added agent-readiness-screenshots.spec.ts to the smoke project in playwright.config.ts (alongside config-bridge-screenshots.spec.ts). Shot 06 (provider-mode bypass) is not captured: the mock bridge's discover_backend_providers always returns [] so the Run-on selector never renders in the test fixture. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
7a7ef98 to
94beb72
Compare
…dicate Introduces managed_agents/readiness.rs with: - EffectiveAgentEnv: resolved process env a spawn would receive (baked floor -> runtime metadata -> user env_vars, last-wins) - resolve_effective_agent_env(): assembles EffectiveAgentEnv from record + personas + KnownAcpRuntime; no AppHandle dependency - Requirement enum with surface discriminator: NormalizedField (provider/ model dropdowns), EnvKey (credential env rows), CliLogin (claude/codex) - AgentReadiness: Ready | NotReady(Vec<Requirement>) - agent_readiness(): evaluates effective env against runtime requirements (buzz-agent/goose: provider+model+creds; claude/codex: CLI login probe; unknown command: always Ready) - Databricks token is NOT required (OAuth PKCE is the normal path) - 17 unit tests covering all providers and surface variants Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
… dialog Adds provider-aware required credential rows to EnvVarsEditor: - requiredCredentialEnvKeys() in personaDialogPickers.tsx: pure function mapping runtime+provider to required env keys (mirrors Rust readiness.rs) - EnvVarsEditor gains requiredKeys prop: locked rows at top with amber highlight, read-only key name, editable masked value, Required badge when empty, inherited-value hint when persona has the key set - EditAgentDialog wires requiredEnvKeys memo (selectedRuntime + provider) into EnvVarsEditor so the required set updates live as provider changes - Databricks shows DATABRICKS_HOST only (DATABRICKS_TOKEN not required) - claude/codex show no required env rows (handled via CLI login surface) - 10 new tests covering all provider+runtime combinations Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…se 3) When a managed agent is missing required credentials, provider, or model, the desktop now spawns buzz-acp in setup-listener mode rather than the normal agent pool. Agents in setup mode respond to @mentions with a surface-correct nudge message that names exactly what to configure and where. Desktop side (runtime.rs): - After resolving runtime_meta, calls resolve_effective_agent_env() + agent_readiness() to detect missing requirements - If NotReady, serializes requirements as BUZZ_ACP_SETUP_PAYLOAD JSON (format mirrors SetupPayload serde tags in buzz-acp) - Normal pool env vars are still set; buzz-acp detects the payload and branches before starting agents buzz-acp side (setup_mode.rs + lib.rs): - New setup_mode module: SetupPayload / RequirementPayload deserialization, run_setup_listener() event loop - setup_mode is entered via early branch in tokio_main when BUZZ_ACP_SETUP_PAYLOAD is present; normal pool path unchanged - Listener: connects to relay, subscribes to channels (mentions-only), applies author gate + event_mentions_agent filter, emits a nudge reply naming each missing requirement and the UI surface to fix it - Per-channel 30s nudge cooldown; per-event-id dedup guards replay - Membership add/remove events handled so newly-joined channels get subscriptions without a restart - 6 unit tests covering payload parse, nudge body, codex CLI copy, etc. Also extends the frontend config-surface path: - isMissingRequiredDropdownField() helper in personaDialogPickers.tsx - EditAgentDialog shows required (*) labels on model/provider dropdowns when the normalized config surface reports them as missing - reader.rs: unwrap_or fallback on resolve_with_override to tolerate agents with no provider configured (avoids panic on unset agent) Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…tically Replace the async useAgentConfigSurface() query with a pure static check based on runtime ID. Only buzz-agent and goose require normalized model and provider fields; the set is known at load time and does not change. - Add runtimeRequiresNormalizedField(runtimeId, field): pure fn that returns true for buzz-agent/goose + model/provider combinations - Simplify isMissingRequiredDropdownField signature: takes a boolean isRequired flag instead of a field descriptor object - Remove useAgentConfigSurface call from EditAgentDialog: no longer needed; required-mark computation is now synchronous - Update test to call runtimeRequiresNormalizedField in the unknown-field case so the test stays accurate under the new signature Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Restore useAgentConfigSurface() as the source for model/provider
required-mark state, replacing the static runtimeRequiresNormalizedField()
predicate introduced in the previous commit.
The static helper duplicated backend runtime knowledge in TypeScript.
A new runtime or changed required-field set on the Rust side would be
correct in KnownAcpRuntime.required_normalized_fields and the
config-bridge reader, but silently unbadged in EditAgentDialog because
the TS predicate was not updated.
The config-surface path is already used by AgentConfigPanel and
ModelPicker; NormalizedField.isRequired flows from:
KnownAcpRuntime.required_normalized_fields
→ read_config_surface() required_fields.contains()
→ build_provider_field(is_required) / build_model_field(is_required)
→ NormalizedField { is_required }
→ useAgentConfigSurface().data?.normalized.{model,provider}.isRequired
Restore isMissingRequiredDropdownField(field: { isRequired: boolean } | null | undefined, value) signature and remove runtimeRequiresNormalizedField.
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Three findings from Thufir's Pass-1 seam review:
[CRITICAL] BUZZ_ACP_SETUP_PAYLOAD was not in RESERVED_ENV_KEYS and
the Ready path did not remove it, so a saved agent env var or ambient
parent-process value could forge setup mode on a Ready agent or
suppress it on a NotReady one. Fix: add the key to RESERVED_ENV_KEYS;
compute the optional payload first, then unconditionally
env_remove("BUZZ_ACP_SETUP_PAYLOAD") after user env is written, and
set it only when desktop computed NotReady.
[IMPORTANT] run_setup_listener() broke on relay close instead of
reconnecting, making the advertised nudged_event_ids replay-dedup
guard unreachable. Fix: mirror the normal-mode reconnect branch
(relay.reconnect().await, exit only if background task is gone).
[IMPORTANT] The six existing setup-mode tests covered payload parsing
and nudge copy only — not the loop-wiring for the two safety-critical
guards. Fix: extract should_nudge_for_event() as a pure helper that
captures the author-gate verdict, event-id dedup, and per-channel
cooldown; refactor the loop to call it; add two targeted tests:
test_non_allowlisted_author_returns_no_nudge (author_allowed=false →
no nudge, dedup set stays empty) and test_same_event_id_twice_nudges_
exactly_once (replay dedup via should_nudge_for_event).
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Two setup-payload tests raced on BUZZ_ACP_SETUP_PAYLOAD under Rust's
default parallel test runner: setup_payload_from_env_returns_none_when_
unset read the global env while setup_payload_from_env_returns_err_on_
malformed_json was mutating it with set_var/remove_var, causing
non-deterministic failures on filtered runs.
Fix: extract SetupPayload::from_raw_env_value(raw: Option<String>) as
the pure parser core; refactor from_env() to delegate to it (no
behavior change). Rewrite the two flaky tests to call from_raw_env_value
directly with None / Some("not-valid-json{{{"): no global env mutation,
safe to run concurrently. Add a third test for the empty-string case.
Delete the misleading safety comment that claimed same-process test
serialization (wrong: cargo test is multi-threaded by default).
Also fix a stale comment at the env_remove call site in runtime.rs that
said the key is removed "after user env has been written (above)" —
merged_user_env() actually writes below. Rewrote it to name the two
real guards: RESERVED_ENV_KEYS strip (guard 1, handles user/persona env)
and env_remove (guard 2, clears ambient parent-process env), with a note
that ordering relative to merged_user_env() is NOT what makes this safe.
Also drop unused mut on ids vec in handle_setup_membership().
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Run cargo fmt and biome check --write to satisfy CI formatter gates. No logic changes — formatting only. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Run cargo fmt for the desktop/src-tauri manifest (separate from workspace). Bump runtime.rs override 2150 → 2207 (env-boundary CRITICAL fix growth). Add reader.rs override at 1016 (config-bridge reader growth, queued to split). Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
The px-text gate rejects raw pixel sizes that don't scale with zoom. Use the established rem-based text-2xs token (0.6875rem) instead. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…sing Fold modelRequired, providerRequired, and hasRequiredEnvKeyMissing into EditAgentDialog's canSubmit guard. The dialog already computed all three values for the required-mark UI; this wires them to the submit button. CLI-login runtimes (claude, codex) return [] from requiredCredentialEnvKeys and are never blocked — their requirement is an out-of-band CLI step with no in-dialog remedy. The runtime setup-listener nudge stays as the backstop for out-of-band degradation after save. CreateAgentDialog is not changed: in local mode it has no provider/model dropdown state to key the env-key gate off of, and the existing providerConfigComplete already handles the backend-provider path correctly. Adds 11 tests covering: missing key blocked, provided key allowed, empty string blocked, claude/codex not blocked, databricks host cases, and the isMissingRequiredDropdownField predicate for required/optional/null fields. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…g semantics; gate on prospective runtime Two correctness fixes closing the block-save/nudge contract: 1. Rust readiness.rs: credential checks (ANTHROPIC_API_KEY, OPENAI_COMPAT_API_KEY, DATABRICKS_HOST) used contains_key, so an empty-string value bypassed the requirement. Provider/model likewise treated empty-string as present. Changed all six credential checks to map_or(true, |v| v.is_empty()) and added .filter(|v| !v.is_empty()) on provider/model extraction in both buzz_agent_requirements and goose_requirements. Empty-string now triggers the runtime nudge, closing the drift with the dialog gate. 2. EditAgentDialog.tsx: requiredEnvKeys keyed off the current dropdown runtime, not the post-submit runtime. On the inherit-runtime transition (e.g. claude pin -> inherit buzz-agent persona), the gate validated the old pin's requirements instead of the prospective runtime's. Hoisted effectiveRuntimeIdForSubmit to component scope as prospectiveRuntimeId (useMemo over the same dual-match derivation), then wired both requiredEnvKeys and the submit path to consume it. Single source of truth — gate and write always agree on which runtime is saved. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…ider visibility
The block-save gate for required credential env keys was calling
requiredCredentialEnvKeys(prospectiveRuntimeId, providerForDiscovery).
providerForDiscovery is suppressed to "" when the CURRENT selected runtime
is provider-locked (claude/codex) — so on the claude-pin → inherit-buzz-agent
transition, the gate computed requiredCredentialEnvKeys("buzz-agent", "")
= [] and falsely allowed saving a config missing ANTHROPIC_API_KEY.
Add providerForRequiredKeys = runtimeSupportsLlmProviderSelection(
prospectiveRuntimeId) ? provider : "", keyed off the PROSPECTIVE runtime.
This is intentionally separate from providerForDiscovery, which remains
keyed off the current visible runtime for live model discovery.
Update transition tests to mirror the component's providerForRequiredKeys
computation via runtimeSupportsLlmProviderSelection(prospectiveRuntimeId),
so the test exercises the same path the component uses rather than hardcoding
the provider directly. Add file-size override for EditAgentDialog.tsx at 1004.
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Extract AgentProviderField/AgentModelField from EditAgentDialog into personaProviderModelFields.tsx and import into both dialogs — no duplication. This also removes the 1004-line EditAgentDialog.tsx file-size override (now 791 lines, 793 per gate counter). CreateAgentDialog local mode (buzz-agent/goose) now has: - Structured provider + model fields with live model discovery via usePersonaModelDiscovery — rendered when the runtime supports provider selection. - localCredsSatisfied gate on canSubmit: requiredCredentialEnvKeys(selectedRuntimeId, providerForRequiredKeys).every(key => envVars[key].length > 0). Create has no inherit checkbox so selectedRuntimeId IS the prospective runtime; no prospectiveRuntimeId hoist needed. - provider/model from structured state included in local-mode submit payload. Thread provider through the Rust create path: - Add provider: Option<String> to CreateManagedAgentRequest (types.rs:329). - In the create handler, provider field on record falls back to input.provider (after snapshot_provider) mirroring how model falls back to input.model. - Add provider?: string to CreateManagedAgentInput (types.ts:383). Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…env rows on Create
Two correctness gaps closed (Thufir Pass 1):
1. Provider/model normalized fields now required in Create's canSubmit.
readiness.rs buzz_agent_requirements and goose_requirements both require
non-empty BUZZ_AGENT_PROVIDER / BUZZ_AGENT_MODEL; empty string = NotReady.
Introduce computeLocalModeGate() in personaDialogPickers.tsx — a pure helper
that returns missingNormalizedFields + missingEnvKeys + satisfied so canSubmit,
field isRequired, and EnvVarsEditor.requiredKeys all share the same predicate.
AgentProviderField and AgentModelField now render isRequired={true} when
llmProviderFieldVisible (i.e. when the runtime requires provider selection).
2. Pass requiredKeys={requiredEnvKeys} to Create's EnvVarsEditor, matching Edit.
Previously the button could disable for a missing ANTHROPIC_API_KEY with no
amber locked row naming the key — user had to know it manually.
Tests rewritten to exercise computeLocalModeGate directly (not a re-derived copy
of the predicate): missing provider blocked, missing model blocked, all required
present allowed, claude CLI-login unblocked, provider/mesh bypass unchanged,
requiredEnvKeys ⊆ full required key list (EnvVarsEditor parity).
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
The local-mode gate (computeLocalModeGate) now blocks create if provider, model, or required credential is empty for buzz-agent/goose runtimes. The smoke test 'create agent supports parallelism and system prompt overrides' defaulted to buzz-agent with no provider/model, so the submit button was disabled and the test timed out. Update the test to select provider=anthropic, a custom model, and fill the ANTHROPIC_API_KEY required row before opening Advanced setup. Parallelism and system-prompt assertions are unchanged — the mock bridge writes both fields to the agent log regardless of provider/model. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Seven Playwright-captured screenshots walking the CreateAgentDialog gate firing and clearing, plus the extracted Edit dialog fields. Added agent-readiness-screenshots.spec.ts to the smoke project in playwright.config.ts (alongside config-bridge-screenshots.spec.ts). Shot 06 (provider-mode bypass) is not captured: the mock bridge's discover_backend_providers always returns [] so the Run-on selector never renders in the test fixture. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…primitive) Add a structured fenced sentinel block (buzz:config-nudge) to the setup-mode nudge message body so the Buzz desktop app can detect and render the payload as a rich Attachment card instead of plain text. Non-desktop clients see the existing plain-text fallback unchanged. Changes: - buzz-acp setup_mode.rs: add agent_pubkey to SetupPayload; nudge_body() appends a fenced json block with agent_name, agent_pubkey, requirements array - desktop runtime.rs: include agent_pubkey in setup payload env-var JSON - desktop configNudge.ts: extractConfigNudge(), stripConfigNudgeSentinel(), isConfigNudgePayload() type guard, ConfigNudgePayload/ConfigNudgeRequirement types - desktop config-nudge-attachment.tsx: ConfigNudgeCard built on Attachment primitive with state=error tint; per-requirement rows; AttachmentTrigger opens Edit Agent - desktop openEditAgentEvent.ts: window-event bus for requestOpenEditAgent(pubkey), consumePendingOpenEditAgent, subscribeOpenEditAgent; mirrors openCreateAgentEvent - desktop UserProfilePanel.tsx: subscribes to openEditAgent event and auto-opens editAgentOpen when panel mounts for the matching pubkey - desktop markdown.tsx: detect sentinel in rendered body, strip it, render ConfigNudgeCard below prose content - desktop check-file-sizes.mjs: bump overrides for markdown.tsx, runtime.rs, UserProfilePanel.tsx (all load-bearing additions, not debt) Un-block save: remove local-mode config-completeness gate from canSubmit in both CreateAgentDialog and EditAgentDialog. Required field markers (isRequired amber rows) remain as visual cues; users can save incomplete config and the nudge card guides them to fix it. Tests: 416 Rust + 1453 TS tests pass; biome clean; tsc clean Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
The mock bridge resolves prereqsQuery asynchronously — the dialog's provider field becoming visible confirms only that providersQuery resolved, but prereqsQuery resolves in a separate tick. The 5s default Playwright timeout was insufficient in some environments; bumped to 10s for shots 01/02/03/08 (the ones that assert enabled with incomplete fields). Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Three fixes from Thufir's mandatory re-review (pass 1): Fix 1 (IMPORTANT): authenticate config-nudge card before rendering. Any message body with a valid buzz:config-nudge fence was previously rendered as a card regardless of message author, allowing untrusted content to forge an official-looking destructive Attachment. - Add configNudgeAuthorPubkey?: string | null to MarkdownProps - Gate the extractConfigNudge useMemo: only extract when prop is set AND normalizePubkey(payload.agent_pubkey) === normalizePubkey(author) - Import normalizePubkey into markdown.tsx - In MessageRow, pass the message.pubkey only when the author is a known agent (resolvedAgentPubkeys). All other Markdown callsites pass nothing, keeping the card path off by default. - Add memo comparator entry for configNudgeAuthorPubkey. - Tests: authGuard_mismatchedAuthor_returnsNull, authGuard_noAuthorPubkey_returnsNull, authGuard_undefinedAuthorPubkey_returnsNull, authGuard_matchingAuthor_returnsPayload, authGuard_matchingAuthor_caseInsensitive (configNudge.test.mjs) Fix 2 (IMPORTANT): clear stale pending open-edit request. subscribeOpenEditAgent's live handler called handler() without clearing pendingEditAgentPubkey, leaving a stale request that could reopen Edit Agent on a later panel remount. Set pendingEditAgentPubkey = null inside the matching branch before handler(), mirroring openCreateAgentEvent. - New test file openEditAgentEvent.test.mjs with 6 tests; key invariant: after subscribeOpenEditAgent handles a live event, consumePendingOpenEditAgent returns false. Fix 3 (MINOR): update stale runtime.rs comment. The JSON shape comment at line 1764 omitted agent_pubkey; the code at line 1796 was already correct. Comment updated. Gates: biome clean, tsc clean, 1497 TS tests pass, 416 Rust tests pass. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Thufir pass-2 finding: the pass-1 fix checked message.pubkey (the resolved display-author), which resolveEventAuthorPubkey can override via a caller-controlled actor/p tag before falling back to event.pubkey. A human-signed event carrying actor=<agent-pubkey> + a matching buzz:config-nudge payload would still pass the guard and render a forged card. Fix: add signerPubkey (raw event.pubkey, normalized) to TimelineMessage and populate it in formatTimelineMessages. MessageRow now gates configNudgeAuthorPubkey on signerPubkey — not pubkey — AND additionally requires message.kind === KIND_STREAM_MESSAGE, restricting the card upgrade to the setup-listener wire format. Also update the configNudge.ts wire-format comment to include agent_pubkey, matching the runtime.rs fix from the pass-1 commit. New regression test authGuard_signerIsHuman_tagAttributedToAgent_returnsNull in configNudge.test.mjs: raw signer = human pubkey, payload agent_pubkey = agent pubkey (as if actor-tagged to agent), configNudgeAuthorPubkey passed as signer (human) → guard returns null, fence not stripped. Gates: biome clean, tsc clean, 1498 TS tests pass, 416 Rust tests pass. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Thufir pass-3 finding: the prior regression test passed HUMAN_PUBKEY directly into the guard shim, so a revert of MessageRow from signerPubkey back to message.pubkey (the spoofable attributed author) would still pass. Fix: extract the MessageRow author-selection predicate as a pure exported helper getConfigNudgeAuthorPubkey() in configNudgeAuthPubkey.ts, wire MessageRow to call it, and add configNudgeAuthPubkey.test.mjs exercising three cases via a real TimelineMessage from formatTimelineMessages: - signerIsHuman_actorTagAttributedToAgent_returnsUndefined: signer=human, actor-tag=agent, pubkey resolves to agent; helper returns undefined. Directly pins the seam: a revert to message.pubkey would cause this to return the agent pubkey and fail. - signerIsAgent_genuine_returnsAgentPubkey: signer=agent; helper returns the agent pubkey (positive case). - nonKind9_agentSigner_returnsUndefined: kind:9 restriction enforced even when the signer is a known agent. Gates: biome clean, tsc clean, 1501 TS tests pass, 416 Rust tests pass. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…tion Two render bugs reported on the config-nudge card: 1. Prose + card both rendered simultaneously. The markdown node was emitted unconditionally, so the human-readable fallback text appeared above the card on desktop. Gate markdownNode behind `configNudge === null` so the card fully replaces the prose on desktop; the wire body is unchanged so CLI/non-card clients still see the plaintext fallback. 2. Card text truncated unreadably. The Attachment had a fixed w-80 (320px) width, and AttachmentTitle's built-in truncate plus truncate on each RequirementRow clipped content to a single line. Drop the fixed width in favour of max-w-lg, override the title to whitespace-normal line-clamp-2, and let requirement rows wrap with overflow-wrap:anywhere so long env key names don't overflow. Also adds two render-suppression tests to configNudge.test.mjs that pin the prose-suppression contract at the parse level. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…iner cap Address Thufir pass 1 findings on the render-only fix commit: IMPORTANT: The parser-level tests in configNudge.test.mjs did not pin the render guard in markdown.tsx (`configNudge === null ? markdownNode : null`). They would have passed even if the guard reverted to always rendering prose. Fix: extract the configNudge computation into a pure module (computeConfigNudge.ts) so it can be imported without the full markdown.tsx dependency chain (emoji-mart crashes the node:test loader). Add three render-level tests in markdown.test.mjs using a GuardStub component that imports the same computeConfigNudge function MarkdownInner calls: - nudgeGuard_sentinelPresentMatchingAuthor_cardRenderedProseAbsent (fails on revert to always-render-prose, verified manually) - nudgeGuard_sentinelPresentWrongAuthor_proseRenderedCardAbsent - nudgeGuard_noSentinel_proseRenderedCardAbsent Wire MarkdownInner to call computeConfigNudge (removing the inline useMemo body), and remove the now-redundant normalizePubkey and extractConfigNudge direct imports from markdown.tsx. MINOR: max-w-[min(100%,32rem)] replaces max-w-lg shrink-0 on the card root, capping both readability width and container width to prevent overflow in narrow timeline columns. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…ion guard The regression guard in markdown.test.mjs previously re-implemented the prose/card selection ternary in a local GuardStub, so a revert of markdown.tsx's guard would leave tests green. This commit extracts the branch into a named export — selectProseOrNudge — in computeConfigNudge.ts. MarkdownInner calls the helper instead of an inline ternary; the tests import and call the same function, so any change to the helper is directly observable at unit-test time. No behavior change: the helper is a one-line extraction of the existing configNudge === null ? markdownNode : null branch. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
94beb72 to
2dfff07
Compare
…ention Two fixes from Will's manual E2E test on #1411. ## Task A — PersonaDialog readiness gate PersonaDialog ("New agent" menu) predates the shared gate components and rendered its own bespoke 'Optional' label for LLM provider, so it missed the #1411 readiness gate entirely. Wire it in: - Import computeLocalModeGate + requiredCredentialEnvKeys from personaDialogPickers (same gate used by CreateAgentDialog). - Replace the static 'Optional' span with RequiredFieldLabel; shows a red asterisk when the provider field is required and unset. - Thread requiredEnvKeys through PersonaAdvancedFields → EnvVarsEditor so missing credential keys render as amber locked rows, matching CreateAgentDialog behavior. - PersonaAdvancedFields gains an optional requiredEnvKeys prop (defaults to [], no call-site breakage). AddChannelBotDialog audited: selects ACP runtimes, no LLM provider/API-key surface — no gate needed. ## Task B — nudge every mention setup_mode.rs had a 30s per-channel cooldown (NUDGE_COOLDOWN / channel_last_nudge) that silently swallowed the 2nd–4th rapid @mentions in Will's repro. The user's intent is clear on every mention, and a mention is an intentional act — rate-limiting is not needed here. Remove the per-channel cooldown entirely; keep the event-id dedup which guards against reconnect-replay double-nudging the same event. - Drop NUDGE_COOLDOWN constant, channel_last_nudge HashMap, and the Duration/Instant/HashMap imports. - Simplify should_nudge_for_event signature: remove channel_id, channel_last_nudge, nudge_cooldown params. - Update both call tests to match the new signature. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.







This PR adds a harness-agnostic config bridge that detects unconfigured managed agents and spawns buzz-acp in a minimal setup-listener mode instead of the normal agent pool.
Before this, a managed agent missing its provider, model, or credential keys would fail silently or crash-loop on spawn. Users had no indication what was missing or where to configure it.
readiness.rstodesktop/src-tauri/src/managed_agents:EffectiveAgentEnvresolver,Requirementenum (NormalizedField/EnvKey/CliLogin), andagent_readiness()predicate; covers buzz-agent, goose, claude, codex runtimes with 11 unit testsrequiredCredentialEnvKeys()topersonaDialogPickers.tsxmapping runtime+provider to required env keys;EnvVarsEditorgains arequiredKeysprop rendering locked amber rows at top with a Required badge when emptyisMissingRequiredDropdownField()inpersonaDialogPickers.tsxwired touseAgentConfigSurface().data?.normalized.{model,provider}.isRequired;EditAgentDialogshows required*labels on model/provider dropdowns sourced from the config-bridge normalized surface (single source of truth — flows fromKnownAcpRuntime.required_normalized_fields→read_config_surface()→NormalizedField.isRequired)setup_mode.rstobuzz-acp:SetupPayloaddeserialization,run_setup_listener()event loop — connects to relay, subscribes channels (mentions-only), applies author gate, and replies with a surface-correct nudge naming each missing requirement; 30s per-channel cooldown and per-event-id dedup; 6 unit testsbuzz-acptokio_main: ifBUZZ_ACP_SETUP_PAYLOADis set, enter setup mode and skip the agent pool entirelyspawn_agent_child(runtime.rs): callsresolve_effective_agent_env()+agent_readiness()after resolvingruntime_meta; ifNotReady, serializes requirements asBUZZ_ACP_SETUP_PAYLOADJSON