feat: ship iOS simulator, signed CLI updates, and Chrome automation - #67
Merged
Merged
Conversation
…uch and annotations - macOS-only panel (Xcode 26/27) with MJPEG stream, low-cost simctl fallback, real rate measurement and controls: attach/detach, tap, drag, typing, keys, rotate, screenshot and screen recording - element/area annotations from the simulator enter the chat as a visual attachment (the browser-annotation family became visualAttachments) - sessionless WDA bridge (scripts/tauri/wda-sessionless) + simulator MCP registered in verboo-in-chrome; the verboo-ios-simulator sidecar is packaged and copied at build time (copy-wda-resource) - panel suspension/restore on fullscreen, topbar button with recording indicator, clean shutdown on app exit (8s budget) Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
…licit risk acceptance - integration cards on the Providers tab (connected/account/Connect/Disconnect) with per-provider state from the bridge (provider_auth_status) - interactive login over PTY (provider-login:event: awaiting_browser | connected | error); neutral cwd for the CLI; explicit Claude risk dialog before the policy is accepted (never auto-accepts) - model selector with per-provider groups, official icon and a "not connected" entry offering Connect; the verboo-only selector stays unchanged - verboo-bridge bridge (providerLoginStart/Cancel/ConfirmRisk/AuthStatus); strip_terminal_controls becomes pub(crate) for the login PTY Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
- node_runtime: cfg(test)-only test hook (VERBOO_TEST_NO_NODE) — the missing-Node check can never leak into the production build (Cadinho remark 2: the old check ran without cfg(test)) - child_signal/cli_spawn: interrupt and escalation target the whole process GROUP (kill(-pid)) — CLI subagents and forks die together with Stop; safe fallback to the direct child when it is not a group leader - cli_credentials: credential protected via DPAPI on Windows - turn_service: a new chat runs in a neutral workdir (app-data/chat-workdir) instead of scanning the user's cwd — the CLI no longer hangs on project read Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
…adation - model_service: field-based dedup with merge and provider model attach (attach_provider_models); VerbooModel gains a provider field (omitted from the serialized form when absent — the current Verboo catalog stays intact) - selector: when the selected model disappears from a transient catalog snapshot, the pill/row keep showing the last known model instead of falling back to the generic label; the listing stays faithful to the catalog Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
…der in the header - API errors presented readably: usage limit with account/plan and renewal, provider retries shown as "retrying (N of M)" instead of a mute "Thinking…" - stdout dedupe keyed on emitted result text (never swallows a repeated streaming delta); quota error suppressed on interrupt and recovery; turn errors get a subtle mark (thin border, no red card soiling the transcript) - turn header: model/provider stamp on send (T10) with the canonical label — a Claude turn no longer announces itself as "Verboo"; offer a new conversation when a turn gets stuck in an empty thinking block - persistence proof of the stamp (chatStore.test) and the started-event race fixed (App.turnModelStamp) Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
…l failure cause - removes the "independent development version" screen (notice, support contacts and the sidebar devBuild) — the app opens straight into real login - the raw cause of a rejected validateAccess appears behind a "Show technical details" toggle in the login warning; "Checking local session…" never stays stuck when validation rejects - dedicated drag strip at the top of the login screen (does not swallow scroll) Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
- "Providers" tab with a Blocks icon (not the browser logo) — provider cards move out of Integrations, where they did not belong - SettingsTab gains the providers value in the TS contract (mirror of Rust) Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
- context menu guard: the native menu no longer opens in the app (Ctrl+click preserved); the guard is installed at the renderer root - browser controls get native tooltips (title) and a consistent aria-label: tabs (real URL), close, new tab, back/forward/reload, edit pencil/arrow and the editable URL field Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
…the repo - README/INSTALL: the packaged app REQUIRES Node.js >=22 on the host (Homebrew, nvm, fnm, Volta or PATH) — the "self-contained" prose was lying; the JSON requirements contract was always honest - requirements/macos-arm64.json: cli-package 0.14.5 -> 0.15.2 - removes the simulator planning documents (owner rule: planning material does not go into the repository) Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
- version bump 0.7.0-beta (package.json, Cargo.toml, tauri.conf.json) and @verboo/code 0.14.5 -> 0.15.2 — already in the worktree, Cadinho-approved; no new bump was created in this operation - build-release-app.sh: the DMG is rebuilt from the signed .app (signing the existing image left the unsigned copy inside the DMG) - ci-verify: permanent canary of the app<->CLI boundary (--list-models under isolated HOME) accepting only the two known CLI 0.15.2 contracts - tauri-release: channel comment updated to v0.7.0-beta Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
- .gitignore: .obsidian/ (personal editor config) out of the repo - comments citing a nonexistent agent (TORNO) fixed to the real fence (PERISCOPIO) in soundStorage, reservedSlashCommands and types.rs - dead SettingsTab enum removed from Rust (the renderer already navigates by string) - lib.rs: rustfmt reflow incidental to the cycle's edits Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
…mments - remaining App.tsx imports left out of the theme 1 hunks (apiErrorPresentation, ProviderRiskDialog, visualAttachments family) — inert import lines, no behavior change - remaining TORNO->PERISCOPIO comments in shared/types.ts (real fence) Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
graseeel
marked this pull request as ready for review
August 8, 2026 23:13
Keep browser work in one durable unfocused workspace, support long and resumable turns, expose interactive controls, and surface staged Chrome integration health in the desktop app. Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
Keep native Chrome tasks in the durable background workspace, retain task-scoped approvals until the native turn boundary, enforce the 60-minute executor budget, and register iOS simulator runtime state only on macOS. Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
graseeel
added a commit
that referenced
this pull request
Aug 30, 2026
* chore(browser): log drained page messages to close the delivery diagnosis
Previous instrumentation proved the WebKit calls our handler, the
parsed envelope is accepted, and the messages enter the queue. Yet
wait_for_page_loaded does not recognize any as page-loaded — meaning
the payload shape does not match the predicate.
This commit logs the first drained message payload (truncated, non-empty
only). The next CI run will show whether the "type" field exists at the
top level and what its value is — the difference between fixing the
producer or the consumer of the message.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* fix(browser): accept page-ready so the smoke works where frames are never composited
Root cause of the Linux bridge timeout: NOT a bridge bug. The full
multi-platform chain works end to end — instrumentation proved the
message arrives with correct url, title "Tab-One", and viewport.
The block was the gate itself.
The smoke waited for "page-loaded", which browser_inject.js emits
inside TWO nested requestAnimationFrame callbacks. rAF only fires
when a frame is composited, and CI headless runners do not compose
frames — not even under xvfb on Linux, and not without an interactive
session on Windows. It is the same limitation that already forced us
to tolerate missing snapshots.
Change: wait() now accepts "page-ready" in addition to
"page-loaded". The function and error messages were renamed (the old
name would start lying). A comment records why rAF has no place in
CI, so no one hardens it back.
This is not a relaxation: page-ready carries url, title, and viewport,
and the title proves the DOM was processed. The in-app annotation
capture (which needs a real composited frame) still requires
page-loaded — the smoke is the only path affected.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* fix(browser): stabilize Windows packaged runtime smoke
* fix(ci): make packaged browser smoke tolerate cold starts
* fix(ci): stabilize headless multi-tab browser pacing
* fix(ci): launch browser smoke from active event loop
* fix(ci): avoid nested main-thread webview dispatch
* fix(ci): replace browser smoke delay with readiness
* fix(ci): keep browser smoke event loop active
* fix(windows): avoid nested WebView2 message pump
* fix(ci): synchronize browser smoke with UI turns
* fix(windows): preserve COM reentrancy during WebView2 waits
* fix(windows): use COM event waits for WebView2
* fix(windows): reuse WebView2 environment across tabs
* fix(windows): make WebView2 contract test CRLF-safe
* ci(macos): sign and notarize release artifacts
* ci(macos): sign and validate embedded native dependencies
* fix(release): unblock native browser and macOS signing
* test(windows): make browser contracts CRLF-safe
* fix(windows): defer WebView2 script completion
* fix(windows): restore sequential WebView2 script pump
* fix(windows): chain WebView2 document scripts
* fix(windows): batch WebView2 initialization scripts
* fix(windows): isolate embedded browser initialization
* fix(windows): keep browser environment on UI thread
* chore(windows): trace controller completion signaling
* fix(windows): restore WebView2 script completion helper
* fix(windows): pump browser bridge script registration
* ci(beta): skip packaged browser runtime smoke
* ci(macos): allow extended notarization queues
* test(manifests): make single-flight check deterministic
* ci(release): make macOS notarization resumable
* fix(release): bound macOS DMG creation
* ci(release): extract macOS signing identity as cert common name
The Tauri Release macOS bundling jobs failed because the signing
identity was extracted from `security find-identity` output using
whitespace-separated `print $2`, which captured the SHA-1 hash. The
output format is `1) <SHA1> "Developer ID Application: Name (TEAM)"`,
so cargo-tauri received the hash while it expects the certificate
common name, raising "certificate from APPLE_CERTIFICATE ... does not
match provided identity".
Switch the awk parser to field separator `"` so $2 yields the CN
exactly as required by cargo-tauri, e.g.
`Developer ID Application: Gabriel Grasel de Moura (6444BXPL32)`.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* chore(version): bump to 0.6.1-beta and @verboo/code 0.14.5
Bump desktop app version 0.6.0-beta.2 → 0.6.1-beta across
package.json, src-tauri/Cargo.toml and src-tauri/tauri.conf.json,
and bump the bundled @verboo/code CLI dependency 0.13.0 → 0.14.5
in package.json + package-lock.json.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* feat(updates): probe Stable channel availability and enable chip dynamically
The Stable update chip was hard-disabled while the app was beta-only.
This adds a silent probe of STABLE_UPDATE_ENDPOINT alongside the
user's active channel check, and surfaces the result via
UpdateSnapshot.stable_channel_available.
- update_service.rs: set_stable_channel_available() + run_stable_probe()
helper. Fail-closed: 404/network error/malformed manifest → false.
Silent — does NOT call mark_error (missing stable is normal, not an
error).
- lib.rs: check_for_updates() now probes the stable endpoint and emits
the updated snapshot. Reuses active_check_ok when user is on Stable.
- tauri.conf.json + update_service.rs: STABLE_UPDATE_ENDPOINT moved to
releases/download/updater-stable/latest.json (was releases/latest,
which 404s for prerelease-only repos). Beta endpoint unchanged.
- types.rs: UpdateSnapshot.stable_channel_available: bool.
- SettingsView.tsx: Stable chip disabled={!stableChannelAvailable};
hint only shown when stable is unavailable.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* feat(goal): make the goal cycle actually run end-to-end in the packaged app
The goal feature never ran inside the packaged .app. Root cause and
nine chained defects, all fixed and verified in the field:
- G-C1 (root cause): maxTurns arrived as MAX_SAFE_INTEGER (JS) into a
Rust u32, so serde rejected the invoke before the spawn. Migrated
goal_evaluator.rs from cli_path::resolve() (broken global-fallback)
to CliSpawn::new() — the same bundled-CLI route chat uses. cli_path.rs
docstring rewritten to point new code at CliSpawn.
- G-C6: timeout 30s killed every real eval (~105s measured). Raised to
240s (≈2.3× margin), env-configurable via VERBOO_GOAL_TIMEOUT_SECS
with a 10s floor. CliTimeout carries timeout_secs.
- G-C2: loop detection (noProgressCount + recentFingerprints) was dead
code — never written in the cycle. Now written post-evaluateGoal.
Fingerprint is whitespace-normalized textual, not semantic.
- G-C2-FIX: App.tsx resume reset forgot recentFingerprints → re-block
on resume. Added recentFingerprints: [] to the resume reset.
- G-C3: tokens were zero at the serde boundary (camelCase mismatch).
Aligned field names across the Rust↔TS boundary.
- G-C4: goal completed prematurely with zero turns executed. Fixed
completion predicate.
- G-C5: redundant confirmation turn caused by toolOutput existing in
TS but not Rust. Removed the phantom field.
- G-C15-TS: usage line rendered in flow.css.
- Token accounting: tokenAccumulator.ts + tests; goalState tracks
total/completed usage; App.tsx + chatStore wire the counters.
- Completion render: GoalActivePanel shows error/retry/usage states.
goalCompletionRender.test.tsx covers the panels.
- Contract tests: rustSerdeContract.test.ts pins the Rust↔TS serde
shape; goalState.contract.test.ts pins the state machine;
goalScheduler.test.ts covers the cycle.
Measured: trivial goal 445s → 54s; 5-step goal closes in 1 turn with
arithmetic conferida. Suites: Rust 998, renderer 739, tsc zero.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* fix(build): abort instead of signing a stale artifact, verify delivery by hash
build-release-app.sh used to exit non-zero on any `npm run tauri:build`
failure, which masked a benign case (updater-tarball signing without
TAURI_SIGNING_PRIVATE_KEY) and a dangerous case (cargo reusing a stale
.app) under the same error code.
- Snapshot the pre-build .app mtime. On tauri:build failure, look for
the .app: missing → real abort, exit 1. Present with the same mtime
→ cargo did not recompile, exit 1 (refuse to sign a stale artifact).
Present with a newer mtime → benign updater-signing failure, proceed.
- Delivery verification: extract the Vite index-<hash>.js literal from
the binary with `strings` and compare to dist-renderer/assets/. If
they diverge, the binary embeds a renderer from a different build —
exit 1, refuse to sign.
- Resolve CARGO_TARGET_DIR once up front (env → src-tauri/.cargo/config.toml
→ $HOME/.cache/verboo-target fallback) so the mtime probe and the
hash probe agree on where the artifact lives.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* ci(workflows): detect channel-only prerelease tags and bump Node to 22 LTS
tauri-release.yml: the prerelease regex `-(beta|alpha|rc)\.[0-9]+$` only
matched legacy tags (v0.6.0-beta.2) and missed the new channel-only
form (v0.6.1-beta), so those tags were not marked prerelease on GitHub
and the release workflow misrouted them. Generalize to
`-(beta|alpha|rc)(\.[0-9]+)?$` so both forms are detected.
verboo-cli-update.yml: bump Node 20 → 22 LTS. Corepack resolves pnpm
11.x by default, which fails with ERR_UNKNOWN_BUILTIN_MODULE on Node
20.x. Bumping Node is a 1-line fix; pinning pnpm would require adding
`packageManager` to package.json (broader scope). Node 22 LTS is
maintained until 2027.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* chore(cli-bundle): prune unused packages from the bundled cli-package closure
copy-cli-resource.mjs: add an explicit EXCLUDED_PACKAGES set so the
bundled cli-package ships only what dist/cli.mjs actually imports at
runtime. Each excluded package has empirical proof (P1 research +
C2 battery of 22 routes, rc map identical to baseline):
- @opentelemetry/* (11 packages): the CLI calls an external
user-provided helper for OTEL headers; it does not import the
@opentelemetry/* SDK itself.
- @grpc/grpc-js, @grpc/proto-loader: transitively pulled by the OTEL
gRPC exporter; with OTEL removed, nothing references @grpc/*.
- type-fest, vscode-languageserver-protocol, code-excerpt, stack-utils:
type-only or unreferenced utilities.
Why an explicit list (not a heuristic): a heuristic that auto-detects
"unused" packages by grepping the bundle for the package name will
eventually prune something essential — bundlers inline code under
minified names, dynamic imports construct strings at runtime, and
rare-path packages look "unused" until they aren't. An explicit,
commented, evidence-backed list keeps the blast radius of a mistake
to exactly the packages a human signed off on.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* chore(version): update Cargo.lock for verboo-desktop 0.6.1-beta
Bump verboo-desktop in Cargo.lock to match the 0.6.1-beta version
bump in Cargo.toml.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* ci: cross-platform verify gate + pin packageManager
Two cooperating fixes for cross-platform verification.
1. .github/workflows/ci-verify.yml (new): a separate verify pipeline
runs cargo test --lib and the renderer build on macOS, Windows, and
Linux. Distinct from the existing release workflow so verify signal
stays decoupled from publish.
2. package.json: add packageManager: "npm@11.12.1". This is the actual
root cause fix for the verboo-cli-update.yml freeze — when neither
package-lock.json nor pnpm-lock.yaml pinned a manager, setup-node
resolved pnpm 11.x by default, which fails with ERR_UNKNOWN_BUILTIN_MODULE
on Node 20.x, so the CLI auto-update cron ran in silent failure and
the app stayed on @verboo/code 0.13.0 while the registry had 0.14.5.
With packageManager pinned, the dual-lockfile ambiguity is resolved
and pnpm is no longer implicit. pnpm-lock.yaml is preserved (it is
regenerated by the CLI auto-update cron via
scripts/verify/update-cli-dependency.mjs) so removing it was never
the correct fix.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* test(verify): run the full Linux suite and verify sidecars by hash
scripts/verify/browser-linux-check.sh: kill the 13 silent --skip flags
that hid real coverage gaps, replace them with a hard guard:
- Remove --skip on the 13 environment-dependent tests (git_service: 5
require real git; video::prepare: 4 require ffmpeg; video::probe: 4
require ffprobe). The container now ships git, ffmpeg, ffprobe, and
Node 22, so all 13 run for real.
- The "filtered out" count is now asserted to be exactly 0. Any new
silent skip becomes a gate failure (QA rule: a skip exists only when
explicitly declared with a one-line justification).
- Three previously green-by-accident tests now execute and pass on the
real Linux container.
Sidecar verification switches from executable-bit check to sha256
equality, before and after, with a fixed count of 5 binary fingerprints:
- macOS binaries were declared "OK" by bit de execução, which also
declared stale copies OK after a partial rebuild. sha256 comparison
catches drift across rebuilds and across developers' machines.
- The fixed-count 5 covers: ffmpeg, ffprobe (macOS), node-runtime,
whisper-helper, cli-helper.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* fix(video): host_target unified, maps Linux and Windows ARM
The function that maps a build triple to a sidecar target was
triplicated across probe.rs, prepare.rs, and router.rs. The non-macOS
arm64 branches returned the literal string "unsupported", which the
caller used to construct a sidecar path (e.g. verb-ffprobe-unsupported)
and proceeded to fail far from the cause.
This change:
- New module src-tauri/src/services/video/target.rs hosts a single
host_target() -> Option<&'static str> covering linux-x86_64,
linux-aarch64, macos-x86_64, macos-aarch64, windows-x86_64, and
windows-aarch64. None maps to "unsupported" — absent platforms are
represented by None at the type level, which forces the caller to
handle them explicitly.
- New VideoValidationError::UnsupportedPlatform variant replaces the
old string sentinel. The compiler now enforces the case.
- probe.rs, prepare.rs, router.rs all delegate to target::host_target().
Each call site is a single match — no fallback string ever reaches
the path builder.
Two guards against re-triplication:
1. A test in target.rs walks every other file under services/video at
test time and asserts none defines host_target or executable_suffix.
2. The signature is non-defaultable (Option, no String fallback). A
silent string return would still compile, but writing the same
triplication twice already failed once.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* fix(process): suppress Windows console windows on every CLI spawn
Without CREATE_NO_WINDOW, CREATE_NEW_PROCESS_GROUP alone still pops a
visible terminal window on Windows — every CLI spawn in the user's
session. Two outages this cycle were traced to it: a terminal window
stole focus mid-Goal cycle, and a git operation in the background
caused flakiness in user tests.
Chokepoint: cli_spawn.rs now exposes apply_creation_flags(&mut Command)
which sets CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW on Windows.
Every production spawn goes through it:
- cli_spawn.rs: new apply_creation_flags; Command builder applies both
flags on Windows, no-op on macOS and Linux.
- workspace_files_service.rs: 3 spawn calls updated to call
apply_creation_flags.
- chrome_integration/installer.rs: 1 spawn.
- goal_evaluator.rs: CliSpawn::new already calls it; comment added.
- plugins_service.rs: same; comment added.
- git_service.rs: spawn helper refactored to route through the
chokepoint.
- child_signal.rs: docs updated to explain why CREATE_NEW_PROCESS_GROUP
alone is insufficient.
A grep test in child_signal.rs walks all of services/ at test time and
asserts every spawn of a child command passes through cli_spawn
creation flags (directly or via apply_creation_flags). Any new spawn
that bypasses the chokepoint becomes a gate failure.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* fix(auth): non-blocking CLI login with incremental read and login:event
Fixes issue #59 — CLI login hung on Linux Fedora because the Tauri
command awaited stdout in one blocking read while the helper streamed
its output line-by-line. On Fedora's pipe semantics the read blocked
forever instead of consuming what was already buffered.
The fix moves login to a single-shot spawn in cli_service.rs that
streams stderr/stdout incrementally:
- cli_service.rs: new login_command()/spawn_login() — builds the CLI
login invocation, takes its stdin (so the user can paste credentials
during OTP), emits a login:event with structured { kind, payload }
for every meaningful line (auth/url/error/ready/done).
- auth_token.rs: helper that listens on the channel and persists the
token once a kind: "ready" arrives.
- models/types.rs + shared/types.ts: LoginEvent + LoginEventKind are
the canonical serde shape (camelCase, rename_all = "lowercase" on
the Rust enum).
- rustSerdeContract.test.ts: cross-module string contract pin — the
Rust enum declaration is read at test time and asserted to match
the TS union declaration. Catches silent rename drift at CI.
- lib.rs: Tauri channel "login:event" registered on the global
Channel<LoginEvent>.
- App.tsx: t() keys wired, LoginScreen receives the live stream, no
visible UI freeze (Tauri invoke returns immediately, events flow
in).
- LoginScreen.tsx + LoginScreen.test.tsx (new): the panel itself,
covered by 12 cases including progress lines, OTP prompt, error,
and ready. The two cases that previously failed on Linux Fedora
now run and pass inside the Linux container, so the bug has a
platform-correct regression test.
- login.css (new): styling extracted from the old inline layout.
- i18n.tsx: 16 keys for the login status progression (login.started,
login.waitingOtp, login.error, login.ready, etc.) so non-English
builds surface the same UX.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* chore(chrome-ext): parallel work committed by user order, NOT QA-gated
*** AVISO: ESTE COMMIT NAO PASSOU PELO GATE DO CADINHO. ***
O usuario trabalhou em paralelo na extensao do Chrome (PERISCÓPIO
fence, extensions/verboo-chrome/**) e no painel de integracao no
renderer (src/renderer/features/settings/ChromeIntegrationSettings.*).
O conteudo deste commit e de autoria do proprio usuario, commits
feitos por ordem expressa dele.
Escopo do commit:
- extensions/verboo-chrome/**: rotina store (routines/*), novos
contratos de painel (approvalActions, optionsContract, panelContract),
controller/tools/tabs.test.js, ajustes em loop/approvedExecute/
untrustedContent/hardBlocks/oauthConfig/policy e i18n, manifest,
docs (PERMISSIONS, PRIVACY, PUBLISHING, README, STORE_LISTING) e
assets (store-assets/README).
- src/renderer/features/settings/ChromeIntegrationSettings.tsx +
.test.tsx: ajustes no painel de integracao.
PENDENCIAS QUE NAO FORAM AUDITADAS (minha responsabilidade):
- diff vs meu plano de agrumamento — nao revisei arquivo por arquivo.
- seguranca do hardBlocks e untrustedContent — sem audit.
- serde/JS bridge na fronteira evaluateScript -> routerClient — sem
audit.
- manifest permissions vs usage real — sem audit.
- testes novos (tabs.test.js, approvalActions.test.js, panelContract,
optionsContract, routines/schema.test, slashCommands.test, store.test,
temporaryDraftStore) — sem review de cobertura ou de mocks.
- ChromeIntegrationSettings.test.tsx — sem review.
CADINHO precisa auditar este commit antes de qualquer merge para main.
Para auditoria futura: arquivo-por-arquivo. Nao faco outra coisa
neste turno — esta commit e um snapshot literal.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* feat(goal): T1 — per-task batch execution + action-evidence guard
Multi-task goals had a single end-state and a single turn counter.
The evaluator only saw "the goal", so each evaluation was a re-judgment
of the whole batch. There was no per-task progression, and the
completion predicate accepted a task that the agent finished in
prose without any whitelisted action on disk.
T1 introduces two cooperating changes, exclusively in the renderer
(no src-tauri touched):
1. Per-task batch execution. GoalState carries a GoalTask[] array
(objective + optional completion criteria) and a per-task
turnsRunThisTask counter alongside the global turnsRun.
GoalScheduler.runGoalCycle passes a SNAPSHOT to the Rust evaluator
with the CURRENT task's objective in `objective` and the per-task
counter in `turnsRun`. The evaluator now judges one task at a time
without any change on the Rust side — the same input/output
contract, the same 240s timeout, the same fingerprint ring.
2. D1 action-evidence guard. A task is not considered complete if the
owning conversation's transcript has zero whitelisted activityKind
entries (whitelist: tool_use, file_write, cli_invoke, eval_run).
Pure "thinking" / narration activity does NOT count as action.
The guard reads the OWNER conversation's live transcript via the
same resolution path as evaluateGoal, so batch goals do not
regress single-task behavior (acceptance 4: single-task key
turnsRunThisTask stays absent → falls back to the global
turnsRun).
PROVENANCE LIMIT (declared in both a comment and an executable test):
the guard proves PRESENCE of an action activity, not CORRECTNESS of
its outcome. A task that ran the wrong tool still satisfies the
guard. Correctness remains the evaluator's job; the guard's only
claim is "did the agent do something, not just talk".
Test suites:
- renderer: 777 tests in 72 files (759 baseline + 18 new, 0 deleted).
goalBatch.test.ts is the new file, covering 6 DISPARO→EFEITO
scenarios (whitelist enforcement, provenance limit, single-task
regression-free fallback, ring-feeding on prose-only, supervisor
handoff shape, retry-from-current-task).
- tsc --noEmit: zero errors.
- npm run build:renderer: builds clean.
- cargo test --lib: 1006 passed (untouched, T1 stayed in the renderer).
Files in this commit:
src/shared/types.ts (GoalTask, GoalBatchEvidence, ActivityKind)
src/renderer/features/goal/goalState.ts (GoalTask[] + turnsRunThisTask)
src/renderer/features/goal/goalState.contract.test.ts
src/renderer/features/goal/goalScheduler.ts (SNAPSHOT + guard)
src/renderer/features/goal/goalScheduler.test.ts
src/renderer/features/goal/goalBatch.test.ts (NEW)
src/renderer/App.tsx (per-task resume + counter wiring)
CADINHO: APROVADO. Push deferred to user authorization.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* feat(goal): T2 — batch state matrix + global stagnation guard
T1 introduced per-task progression and a per-task evidence guard. T2
plumbs the full batch into a state machine and adds a global guard so a
single broken task cannot silently poison the rest of the batch.
State matrix — 13 transitions, one test per row in
goalBatchMatrix.test.ts:
per task: pending → running → complete
→ running (loop, no-progress feed)
→ failed_action → skipped (user skip)
→ failed_error (hard error)
per batch: active → completed (all tasks complete)
→ paused (K guard trip)
→ aborted (user abort OR safety trip)
Decisions, with the reasons they are the decisions:
- Safety alert is WHOLE-BATCH, not per-task. Safety is a property of
the run, not of individual tasks (a destructive tool call in one
task contaminates the whole owner conversation).
- Loop detection kills the CURRENT task but ADVANCES to the next. A
task that loops forever must not take the rest of the batch with
it. The advance carries the loop fingerprint into the per-task
dead-letter slot, so the user can see what happened.
- Global K guard pauses the batch after 2 consecutive failures with
reason batchStagnation. The counter resets on ANY success
(including an unexpected one — proof the environment is alive) but
SURVIVES advances after a failure (so a sequence "fail, fail, advance"
still trips at the 3rd failure). This is the guard that detects
environment-broken-ness, not user choice.
- Skipping a blocked task is a USER decision and is a distinct status
(skipped), not a failure. It does NOT feed the K guard. Skipping is
the user's escape hatch; if it counted, the guard would punish the
user for using the only escape it provides.
PROVENANCE NOTE (declared in commit + docstring + test label):
the canonical repro test in goalBatch.test.ts changed its assertion
shape. This is strengthening, not regression. CADINHO audited the
diff line-by-line: the new assertion is strictly more specific
(witnesses the rust-stamped reason on the dead-letter, not just
"a reason exists"). The pre-T2 test would have passed silently
under the new code; the post-T2 test fails if the witness is dropped.
Test suites:
- renderer: 794 tests in 73 files (777 baseline + 17 new, 0 deleted).
goalBatchMatrix.test.ts is the new file, covering 13 matrix rows.
- tsc --noEmit: zero errors.
- npm run build:renderer: 6.03s clean.
- cargo test --lib: 1006 passed (untouched, T2 stayed in the renderer).
Files in this commit:
src/shared/types.ts (BatchStagnation, SkipReason, batchStatus)
src/renderer/features/goal/goalState.ts (K counter + batch state)
src/renderer/features/goal/goalState.contract.test.ts
src/renderer/features/goal/goalScheduler.ts (advance on loop, K guard)
src/renderer/features/goal/goalBatch.test.ts (canonical repro: assertion strengthened)
src/renderer/features/goal/goalBatchMatrix.test.ts (NEW)
CADINHO: APROVADO without ressalvas. Push deferred to user authorization.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* feat(goal): T3 — task frontier with /compact handoff
On task advance the batch emits the CLI's native /compact, awaits the
compaction turn to complete, and only then zeros the fingerprint ring,
no-progress counter, and per-task turn counter.
The compaction turn does not by itself satisfy the next task's
action-evidence guard, so the new task does not inherit a false
positive from the previous one.
Compaction tokens count exactly once (dedupe by turnId).
A failed /compact does not freeze the batch — the batch proceeds and
records that it did not compact.
Race between state writes eliminated by atomicity, not just ordering:
the index advance and counter zeroing now happen inside a single
updater.
Honesty note retained in code: reactive context-overflow recovery may
fire mid-task, outside the frontier's control, and disarms the loop
detector by construction. The global stagnation guard is the safety
net for that case.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* feat(goal): T3b — compact at every task frontier with coalescence
Compaction now fires at every frontier, not only on success advance. A
task that died from a loop produced the dirtiest context of the batch;
carrying that into the next task hands it the inherited dead zone. The
uniform rule is: every task starts with a clean context, regardless of
how the previous one ended.
Coalescence: skip compaction when the exiting task ran zero turns —
nothing was added to the context since the last compaction. The skip
is declared in the log, never silent.
A user-initiated skip happens outside the cycle: the frontier is
stamped as DUE and executed at the start of the next cycle with the
full protocol, idempotently. The DUE stamp is not applied when the
skipped task was the last one.
Cost cap: a stagnation-pause does NOT compact, because the batch halts
and there is no next task to compact for.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* feat(goal): T4 — final batch report with evidence, in-place progress, i18n
On batch close the report lists EACH task with the evidence that
sustained its conclusion — not just succeeded or failed. With N tasks
in a batch, a single lying task would contaminate the whole batch in
silence.
Evidence is stamped in state at the transition moment, captured
BEFORE the frontier reset; recomputing later would read zero because
of compaction.
A task marked no-tool gets its own line saying its evidence was
waived, instead of rendering zero actions and lying in the opposite
direction.
Progress renders as a discrete "task k of N" line updated in place,
with no badge and no box. The final report goes inline in the same
turn as the agent's message.
Nine new i18n keys across both locales, including batchStagnation
wired into the reason translator — paying off the T2 debt where the
stagnation pause rendered an unknown reason.
User product vetoes became negative test assertions: the test asserts
that the separate box and the second message do NOT exist. This
prevents experience regression the same way we prevent code
regression.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* feat(goal): T5 — batch input with tolerant parser
A batch is one task per line: two or more content lines form a batch
while a single line remains the normal goal of always — no behavior
change for the single-task path.
Numbering and bullet markers are accepted and stripped (up to two
stacked per line, always requiring a trailing space), so a fragment
like "1.5 corrigir versao" stays intact. Blank lines are ignored;
lines that are only a marker are discarded.
The [toolless] flag in any position of the line, case-insensitive,
marks the task as legitimately tool-less and waives the evidence
requirement; the final report declares this. Mentioning the word
WITHOUT brackets does not activate anything.
During a batch, objective editing stays disabled: the buttons stay
VISIBLE but inert, with the reason as tooltip — a clear warning, not
a mysterious disappearance. Skip-locked-task and cancel remain
available.
Known caveat (cheap fix planned): a line that is only a number with
no punctuation, or only punctuation, currently becomes a task and
will visibly fail in the report.
An empty /goal command teaches the format instead of just failing,
and does not interrupt a goal already running.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* fix(process): add missing CommandExt import at seven call sites
creation_flags is a method on the CommandExt trait. Without
`use std::os::windows::process::CommandExt` the compiler rejects the
call, so the previous console-window suppression did not actually
compile on Windows.
The import was missing at SEVEN call sites — the seven defects that
prior cross-platform testing missed.
Why this stayed hidden: 1006 tests passed on macOS and 1006 on Linux
because the compiler SKIPS cfg(windows) blocks on those targets.
The textual scan test that was supposed to guard this fix only
proves the FORM of the code (it greps for "creation_flags"); it does
not prove the code compiles.
Found by cross-compilation with `cargo xwin check
--target x86_64-pc-windows-msvc`, which runs in seconds on macOS.
child_signal.rs now carries a comment declaring its test as form-only
and pointing at the cross-compile gate as the real proof of
compilation.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* chore(verify): local Windows compile gate + Linux arch parametrization
The new scripts/verify/browser-windows-check.sh is the local mirror
of the Linux gate and runs `cargo xwin check
--target x86_64-pc-windows-msvc`. Its header declares the limit
explicitly: this proves COMPILATION, not behavior.
The script exists because cross-compilation surfaced seven real
defects in a commit that had already merged (the missing CommandExt
imports in fix(process)). Without a local compile gate, those defects
would have shipped because cfg(windows) code is invisible to the
macOS and Linux compilers.
scripts/verify/browser-linux-check.sh gained optional architecture
parametrization, with the host default unchanged. It also gained a
comment documenting a FAILED attempt to run the x86_64 gate under
QEMU emulation: gcc-13's collect2 segfaulted on proc-macro linking,
reducing parallelism did not help, and clang with lld broke at the
apt stage. The flags that had been added to disable apt signature
verification for the emulation attempt were removed in full. The
comment is there so the next person does not relive the night
thinking they had a new idea.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* fix(goal): evaluator downgraded markdown-wrapped decisions silently
Pre-existing defect found by field test on the packaged app: when
the CLI returned the evaluation with the JSON wrapped in a markdown
code fence, the envelope extractor could not read it and FABRICATED
a synthetic continue decision with confidence 0.5, dumping the real
evaluator response into the reason field as text.
In the field this caused a task that was correctly completed (file
created, content verified by reading) to be repeatedly downgraded to
continue until it died by loop detection and was marked failed. The
evaluator had actually decided complete with confidence 1.
The defect is INTERMITTENT — it depends on whether the model wraps
the response — and affects ANY goal, including single-task.
Fix: reuse the existing parse_first_json_object helper, which already
subsumes fence tolerance by scanning the string counting brace depth
and ignoring braces inside string literals. This avoids adding a
fourth local implementation of fence tolerance — three already exist
in the repo.
When no parseable JSON is present, the function now returns a parse
error carrying the TRUNCATED raw string, instead of a synthetic
continue. This honors the contract documented in goalScheduler.ts
lines 111-117: the caller must not swallow errors into a false
continue decision.
CONTRACT CHANGE (declared per CADINHO requirement):
The change in lib.rs makes all evaluator error classes — timeout,
spawn, exit, and parse — retry up to three times with growing
backoff before pausing, instead of pausing on the first failure.
This is an improvement but is a behavior change for callers that
previously observed a single failure as terminal.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* refactor(goal): minimalist goal panel at user request
The user found the panel visually heavy and asked for it to be more
minimalist, compact, and clean.
The card shed visual weight, the status became a discrete per-state
indicator instead of an UPPERCASE box label, the redundant objective
label was removed, and the buttons became icons with accessible
labels.
Product vetoes became negative test assertions: the tests require
that the old captions and the removed label do NOT come back, and
the block comment quotes the user's veto textually. Reintroducing
the loud panel breaks the test with a message explaining why.
Incidental cleanup: a pair of pre-existing dead CSS classes with no
remaining consumers was removed alongside the redesign.
Limitation declared by the author inside the test: jsdom does not
evaluate CSS, so visual quietness is pinned via class-and-attribute
contracts, not pixels. Final visual judgment is reserved for when
the app is open.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* fix(goal): restore batch final report with time+token, fix progress stamp
Field test on the packaged app with a direct user complaint: when a
batch finished, neither the per-task report, nor the elapsed time,
nor the token count was shown.
First cause: onComplete was not invoked when the last task reached
terminal state nor when the last task was skipped, so the batch
ended mute.
Second cause: the completion handler resolved the goal's deferred
BEFORE the summary item existed — item creation waits on an
asynchronous call. The stamp ran, found no target, returned
silently, and never retried for that turn.
Fix: the deferred is now resolved AFTER item creation. Stamp
failure is no longer silent: when the target is not found, this
now surfaces, with the declared distinction between "nothing to
stamp" and "stamp failed".
One test assertion changed by design: a prior pin asserted the
report was NOT emitted on that path, and its own comment said it
existed so that a conscious choice could be made later. The choice
was never made; the field complained; now it is made, with stronger
exactly-once, status, and timestamp assertions.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* fix(process): reserved slash commands pass through to CLI unwrapped
build_prompt was prefixing every message with the working directory,
including on resume. The prompt therefore never STARTED with a slash,
the CLI command interception layer never fired, and /compact received
the response "this is an internal command, don't invoke via Skill".
The batch was the first consumer that depended on this path.
The defect is pre-existing: /compact was born in commit 7fdd56c,
already on origin/dev, and the wrapping was introduced by c5dae57
during the Tauri migration. The command has never worked through the
app since it was created.
Fix: reserved slash commands now pass to the CLI without the
directory prefix. The normal (non-slash) path retains the prefix,
proved by counterfactual.
Boundary test added: a contract test reads the list of reserved
commands from the Rust source and compares it against the
TypeScript command family, failing if a command appears on only one
side — because a forgotten command would be wrapped and die silently,
exactly as /compact did. The test lives in the renderer fence as a
punctual exception authorized case-by-case, without changing the
project fence rules.
manifest_cache: the deadlock test that depended on real spawn was
moved from gate to an ignored integration test. It was measuring
ENVIRONMENT and calling the result a verdict, failing 1 in 5 under
load and threatening to leave CI intermittently red. The guarantee
of absence of deadlock was not lost — it was redirected to another
test promoted as witness, with signs on both sides.
LIMITATION: this proves the message exits raw; that the CLI
actually compacts through this path will only be proven with the
packaged app open.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* feat(goal): resumable pause-by-reply when task is impossible, plus two UI asks
The panel now shows the user's message LITERALLY instead of the
generic "Lote de N tarefas" label — the user said the generic was
not intuitive.
It gains genie-style entry and exit animation, rising from the
composer on start and descending on end, respecting
prefers-reduced-motion.
The taskImpossible reason now PAUSES the batch instead of falling
through. The task is stamped BLOCKED and never failed, because
resumability depends on this. The index does NOT advance while
blocked, so the batch does not skip the task to resolve.
Session re-hydration as its own important fix: the session
identifier lived only in a reference and was never recovered from
persisted state. Resuming after an app restart silently created a
NEW session — the user would answer thinking they were continuing
and the model would start from zero.
Replying in the composer now RESUMES the paused goal, detected by
the OWNING conversation, not by the active one.
Visible contract message: the pause tells the user, on screen and
in both locales, that replying there resumes the SAME task and that
to change task they must cancel and relaunch. Predictable behavior
by visible declaration, because someone will reply "forget this, do
Y" and the evaluator would judge against the old text.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* fix(evaluator): pause when output is a symbolic artifact, never conclude
In a field test on the packaged app, a batch with two IMPOSSIBLE
tasks reported all four as completed. The agent honestly explained
it could not, but it still created copia.txt with ZERO bytes and
config.json with empty keys — and the evaluator accepted.
The observable-action guard does not catch this because creating an
empty file IS an action: it proves PRESENCE, not CORRECTNESS. That
limit is declared in the code itself.
New rule: a symbolic artifact is not delivery — empty, placeholder,
or stub outputs do not satisfy the objective. The output is PAUSE
with the new reason taskImpossible and a human-readable explanation,
never conclusion.
Counterfactual: the rule still lets REAL delivery conclude. A
rejection rule that rejects too much is worse than no rule.
This commit lands after the TS-side commit (2f7460e) so the new
reason exists on the renderer side first. With TS first, no point
in the repo history ever holds the dangerous state where Rust
emits taskImpossible and the renderer falls through to a silent
continue.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* feat(process): propagate TodoWrite contents instead of discarding them
The bundled CLI already has TodoWrite and the agent already plans
steps and checks them off as it goes. The app received the event
but flattened it to the generic label "Updated tasks" with a
generic type, and the items and status of each were DISCARDED
before crossing the bridge — the data was never even stored.
Now TodoItem carries content, status, and activeForm, and
RuntimeActivity propagates the list.
TodoWrite emitted by a SUBAGENT is filtered, so it does not
overwrite the main turn's list.
SECOND CHANGE (declared per CADINHO): TodoWrite now has its own
planning type and ceases to count as an observable action for the
goal guard. The guard exists to prove something WAS DONE, and
writing the list of what you intend to do is the opposite.
Before this change, planning satisfied the guard, which would
allow an agent to close a task by merely announcing the plan.
A test BREAKS if planning is reintroduced to the action list,
with the expected answer documented as NO.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* feat(checklist): agent task list with floating card and docked mode
The list appears whenever a checklist exists — it is not exclusive
to goal. Any multi-step request makes the agent plan and the list
spawns by itself, checking items off as the agent completes them.
Two display modes with user preference persisted: a compact floating
card in the upper-right corner, sized to content, draggable with
magnetic snap; or a docked row above the goal or composer, three
lines with no header or border, collapsing to one line when all
items are done. Goal stays closest to the composer; the list rises.
When the right side is occupied by terminal, web, review, or a
sub-agent panel, the list MIGRATES by flight animation to docked
mode, and returns when the panel closes.
The card follows the pointer over the conversation during drag but
has no resting position outside the right strip — releasing snaps
it back.
Positioning is a PURE FUNCTION with an exhaustive test matrix,
because the user demanded the UI not break under any combination.
The floating card is mounted OUTSIDE the fixed footer via a portal
to the body: a fixed-position element inside an ancestor with
transform has its containing block hijacked and stops orienting by
the window — exactly the class of bug that already dropped the
panel in this project. A test locks the portal so nobody simplifies
it back.
Multi-platform mitigations by construction: scrollbar width
measured at runtime, heights in line-height units with a test that
breaks if pixels return, pointer events with capture, and position
reclamping on both restore and resize.
LIMITATION: the real pixel on Windows and Linux remains unproven
until a push; Windows tests are geometry SIMULATION.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* fix(checklist): stop card colliding with other floating elements, cut less text
Field test on the packaged app: the card spawned correctly and
checked items off by itself, but the text rendered truncated with
ellipsis eating almost everything, and the sub-agent indicator
OVERLAID the card — both disputing the same upper-right corner.
The fix is NOT to move the card to another corner, which would only
push the collision elsewhere. The positioning pure function now
carries a notion of free space at the top, so ANY floating element
in that region pushes the card BY RULE.
Property verified by CADINHO across the 256 combinations of the
matrix: the indicator is FORM-INVARIANT — it never decides whether
the card floats or docks, it only shifts geometry.
The step now breaks across two lines with the full text available
in a tooltip. A CSS pin locks the font property and breaks if
someone reverts the line wrap. Heights stay in relative units, not
pixels, preserving the multi-platform protection.
LIMITATIONS: jsdom does not lay out, so the real pixel of the
two-line wrap and the live geometry measured for the indicator are
only proven with the app open. Windows and Linux pixels remain
simulated in tests, unproven until a push.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* feat(extension): rotinas, extracao estruturada e sessao de opcoes
Trabalho majoritariamente do usuario, feito em paralelo ao ciclo do
app. Descricao factual e neutra.
O controlador ganha browserTools.js com extracao estruturada de
pagina e uma leitura de pagina mais robusta, acompanhados de testes.
O painel de opcoes ganha uma sessao propria de configuracao
(optionsSession.js) com testes, separando o estado das opcoes da
logica de renderizacao.
Rotinas (routines) amadurecem: runner, runQueue, runStore e schema
completam o ciclo de execucao de rotinas, todos com testes.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* feat(browser): ciclo de vida, visibilidade e User-Agent das abas, mais anotacoes e checklist
Fronte por fronte:
- navegador embutido: navegar a partir do estado vazio; audio para
ao fechar aba (about:blank + sondagem ate confirmar, orcamento
500ms); minimizar esconde de verdade e pausa midia; reabrir volta
na mesma aba; teto de 8 abas vivas com despejo LRU declarado na UI;
barra de URL derivada da aba ativa
- visibilidade virou ESTADO IRREPRESENTAVEL: campo privado em modulo
privado, so alteravel por funcao que exige a transicao nativa;
ativar aba com painel escondido nao mostra a webview
- User-Agent das abas: sufixo Safari que a WKWebView omite (o Google
servia layout antigo); versao lida em runtime via NSBundle, com
fallback declarado e registrado; Linux coberto de forma DEFENSIVA
(nao verificado em maquina Linux); Windows dispensa (motor
Chromium)
- anotacoes: selecionar trecho da resposta, comentar, e enviar como
mensagem propria na ordem do transcript
- checklist: saida animada ao completar, dois sons, e fim da
sobreposicao com o composer
- idioma dos passos do TodoWrite e limpeza de vazamento de </think>
na exibicao
- varredura de fronteira renderer/Rust: teste que le o renderer
atras de invoke() e falha se o comando nao existir do outro lado
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* chore: prototipos de rascunho do checklist
Mockups de rascunho do checklist, autocontidos em HTML. Foram usados
para o usuario escolher o desenho e ja cumpriram o papel; ficam no
historico para quem achar depois saber o que sao.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* fix(ci): CARGO_TARGET_DIR fora do env de job, que invalidava o workflow
O contexto 'runner' nao e valido em env: de job — por isso o GitHub
recusava o arquivo inteiro na validacao (0 segundo, nenhum job
criado). O workflow tinha sido escrito na maquina e nunca havia
rodado.
A isolacao por job foi preservada via $RUNNER_TEMP escrito em
GITHUB_ENV num passo dedicado, o que propaga o valor para todos os
passos seguintes.
'shell: bash' e necessario porque o runner Windows usa PowerShell
por padrao, e o GITHUB_ENV via echo funciona em bash.
Limite honesto: isto prova que o workflow CARREGA, nao que ele
PASSA — a primeira execucao real de verdade nos tres sistemas ainda
vai acontecer.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* fix(ci): hash do ffmpeg, recurso cli-package e shell do passo Windows
Tres defeitos reais que a primeira corrida de verdade expos, todos do
arreio de CI, nenhum do app:
1. Hash SHA-256 do ffmpeg no Windows com dois caracteres trocados
(a0b em vez de b0a). O hash foi conferido no checksum publicado
pelo fornecedor (gyan.dev) — nao copiado do valor observado na
falha, que seria transformar a verificacao em teatro. O download
era legitimo; o valor chumbado e que estava errado.
2. macOS e Linux nem chegavam a testar: build.rs abortava com
"resource path 'resources/cli-package' doesn't exist".
src-tauri/resources/ e gitignored e a CI nao rodava o passo que
cria o recurso. O passo novo (copy-cli-resource.mjs) roda depois
do npm ci e antes do build do renderer, no job core-tests.
3. Renderer no Windows: sintaxe POSIX rodando sob PowerShell
(ParserError), faltava 'shell: bash' no passo de scripts .mjs.
Limite: isto corrige o que a primeira corrida real expos; se os
testes passam nos tres sistemas, so a proxima corrida diz. Nunca
rodamos a suite Rust no Windows nem no Linux.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* fix(test): CLI falso descarregava stdout tarde demais no Windows
O teste a1_url_extracted_before_process_exits levava 26,6s no
Windows (limite 2s). O CLI falso fazia process.stdout.write(...) e
caia num laco ocupado de 30s; em pipe a escrita do Node e
assincrona e o laco bloqueava o laco de eventos, impedindo a
descarga. No macOS passava por acidente.
Correcao: usar o callback do write como barreira de descarga.
Nenhum leitor de producao foi alterado. A leitura incremental do
produto foi conferida no codigo: laco de read + extract a cada
pedaco, wait so depois.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* fix(test): teste do Safari fixava a versao da maquina de desenvolvimento
O teste afirmava versao "27.0", que e a do Safari da maquina onde
foi escrito; falhava em qualquer outra, inclusive no runner
macos-15.
Agora afirma RELACAO (a leitura devolve valor, comeca com digito, e
atravessa ate o UA montado) em vez de numero.
A protecao original continua: a suite fica vermelha se alguem fizer
o resolvedor ignorar o valor de runtime.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* fix(ci): ffmpeg moderno no Linux em vez do apt do Ubuntu 22.04
Dois testes de video falhavam so no Linux com "Unrecognized option
'fps_mode'" — opcao que existe do ffmpeg 5.1 em diante, e o apt do
Ubuntu 22.04 entrega 4.4.
Trocado por build estatico 8.1.2 (mesma linha que o app empacotado
embute), de release imutavel, com SHA-256 conferido contra o
arquivo de checksums PUBLICADO.
A verificacao de hash torna irrelevante a tag ser teoricamente
reescrivel: se o artefato mudar, a CI fica vermelha e barulhenta,
nunca silenciosamente diferente.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* fix(scripts): caminho interno de bundle .app e sempre POSIX
resolveLaunch montava o caminho com o separador do HOST; como recebe
a plataforma por parametro, ela pode ser chamada com darwin de
qualquer maquina, e no Windows produzia barra invertida. Agora usa
POSIX.
Limite honesto: a prova contrafactual e CEGA no macOS (join e
posix.join produzem o mesmo resultado no darwin) — a falha so
aparece em host Windows, e a confirmacao vem da proxima corrida da
CI.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* fix(test): CLI falso nao prende mais a CPU, e vivacidade do filho virou assercao
Dois pontos:
(a) o laco ocupado 'while (Date.now() - start < 30000) {}' prendia
um nucleo. No runner Windows, com poucos nucleos, isso atrasava a
thread leitora: o teste foi de 26,6s para 12,98s com a correcao
anterior e continuava acima do limite de 2s. Trocado por setTimeout,
que mantem o processo vivo os 30s sem monopolizar processador —
temporizador pendente segura o laco de eventos do Node.
(b) achado do QA: a premissa 'o filho ainda estava vivo quando a URL
apareceu' NUNCA foi afirmada, nem antes. Um pipe retem os bytes ja
escritos mesmo depois do processo sair, entao um falso futuro que
saisse cedo faria o teste passar pelo motivo errado. Agora ha
assercao de child.try_wait().is_none() no instante em que a URL e
encontrada.
Limite: a hipotese da CPU so se confirma na proxima corrida Windows.
Se o tempo continuar perto de 13s, a causa e outra.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* fix(scripts): normalizar fim de linha ao ler arquivo em teste
Checkout do git no Windows traz CRLF; os testes liam arquivo e
comparavam/fatiavam com \n, entao quebravam so no Windows. Helper
readWorkflowText normaliza na leitura, aplicado em 7 pontos, mais a
leitura de fonte .rs no smoke do navegador. Mesmo padrao ja usado em
browser_panel.rs ao ler o wry vendorizado.
O QA varreu o repositorio e nao existe teste cujo proposito seja
detectar fim de linha — a normalizacao remove ruido de checkout,
nao sinal.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* fix(test): teto de tempo derivado da vida do filho, em vez de numero absoluto
O '<2s' era proxy fraco e dependente de maquina: media partida do
Node junto com a leitura. Falhou por 2,32s no runner carregado do
macOS e do Linux.
Agora o teto sai da vida do filho: uma constante FAKE_CHILD_LIFETIME
de 30s, e o teto e um terco dela. A fracao tem justificativa
numerica, nao estetica: a regressao que importa (o leitor voltar a
esperar o processo sair) vale 30s, e 10s da 3x de separacao; o ruido
observado e ~2,3s, e 10s da 4x sobre ele.
As quatro literais soltas foram unificadas: o valor do setTimeout dos
DOIS scripts falsos e interpolado da mesma constante, e o prazo do
laco e o teto da assercao sao calculados dela. Sem isso, 'derivado'
seria so nome bonito — bastaria alguem mudar a vida do filho para o
teto virar palpite de novo.
A assercao de vivacidade (child.try_wait().is_none() no instante em
que a URL e encontrada) continua: ela prova a leitura de processo
VIVO; o tempo e teto de sanidade. As duas sao necessarias —
vivacidade sozinha nao pega leitor lento, porque o filho vive 30s.
PENDENCIA (achado do QA, nao bloqueante): o prazo do laco e o teto
da assercao usam o mesmo valor, entao uma URL que chegue logo acima
do teto produz 'nao achou' em vez de 'achou tarde' — mensagem de
falha enganosa. Correcao futura: laco em 2/3, assercao em 1/3.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* docs: o app nao e self-contained — exige Node do sistema
A documentacao prometia "self-contained" em 9 lugares; era falso. O
bundle traz o cli-package (@verboo/code 0.14.5) mas NAO traz o runtime
Node. node_runtime.rs resolve na ordem: env var -> Homebrew/nvm/fnm/
Volta -> PATH -> sidecar '(Future)' que nunca foi implementado.
Distincao preservada: npm nao e necessario, CLI global nao e necessario.
Node runtime >=22.0.0 exigido.
README citava 4 docs inexistentes (release-github-actions,
updater-signing, feedback-supabase, open-source-review) — referencias
removidas.
requirements/macos-arm64.json: cli-package 0.10.6 -> 0.14.5.
Nova secao cobre: navegador embutido, anotacoes no transcript, sidecars
midia, extensao Chrome.
User-Agent do navegador embutido: limite declarado honestamente —
override macOS+Linux, Windows default Chromium, Linux defensivo nao
verificado em runtime.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* docs(extension): politica de privacidade em conformidade com o manifest
Versao da documentacao 0.1.2 -> 0.2.1 para acompanhar o manifest 0.2.1.
Tres de 13 permissoes nao eram divulgadas:
- activeTab: screenshot.js captureVisibleTab fallback
- nativeMessaging: bridge.js host name
- <all_urls>: necessario para captureVisibleTab
file:// e chrome:// sao rejeitados pelo planner (navigate.js), nao
pela permisso <all_urls>.
Ponte nativa deixou de ser "planejada" — limites do protocolo
refletindo o codigo atual.
NENHUM codigo da extensao foi alterado, apenas documentacao.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* feat(transcript): ferramentas do Chrome ganham icone e rotulo proprios
As ferramentas da extensao chegam como
'mcp__verboo-in-chrome__<tool>' e caiam no caso generico,
aparecendo como 'Usou ferramenta'. Agora cada uma tem rotulo
especifico ('Navegou no Chrome', 'Leu pagina no Chrome',
'Capturou tela no Chrome'...) e o tipo 'browser'.
Os nomes foram confirmados por TRES fontes independentes antes de
escrever o braco: o MCP_TOOL_PREFIX lido do cli.mjs empacotado, o
tools/list do proprio servidor MCP, e um turno real no app onde o
agente citou mcp__verboo-in-chrome__navigate.
MARCA: o icone e um globo generico, NAO o logo do Google Chrome.
A marca vive no rotulo em texto. Decisao deliberada — o app e
distribuido publicamente e assinado.
Canario novo (chrome_tools_canary.rs) le o browserTools.json REAL
da extensao e exige correspondencia NAS DUAS DIRECOES. Sem ele, um
rename do lado da extensao rebaixaria o passo a generico em
silencio. Provado por mutacao dupla.
O fallback 'Usou o Chrome' cobre a janela em que a extensao
INSTALADA diverge do manifest do repo (usuario com versao mais
nova da Store) — rotulo generico honesto e melhor que quebrar.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* feat(chrome): avisar que o painel lateral precisa estar aberto
Descoberto em teste de campo: sem o painel lateral da extensao
aberto no Chrome, NENHUMA ferramenta executa — a chamada falha
com approval_ui_unavailable e o usuario so descobria lendo o
texto do agente.
Agora o cartao de Ajustes/Plugins avisa, e ha orientacao apos a
falha.
LIMITE DECLARADO, no relatorio e no codigo (models.rs): nao ha
deteccao PREVIA. Saber se o painel esta aberto antes de tentar
exigiria mexer no background.js da extensao, que e trabalho do
dono em paralelo. A limitacao esta escrita para o proximo leitor
saber que foi decisao, nao esquecimento.
A instrucao de como abrir o painel foi corrigida: dizia para
clicar no icone 'e escolher Abrir painel lateral', mas
background.js:181-190 mostra que o clique abre direto, sem menu.
O texto errado mandaria o usuario procurar opcao inexistente.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* fix(transcript): cada passo do Chrome diz a propria acao
- a feature entregue mostrava Usou o Chrome em todo passo, apesar da tabela de 8 rotulos especificos. O dono pediu, com print de referencia, que cada passo DISSESSE A ACAO — treze linhas identicas nao dizem acao nenhuma.
- a causa: StepFlow.tsx:96 monta o rotulo com summarizeActions (turnBlocks.ts:114), que agrupa por TIPO usando PLURAL_KEYS. A primeira tentativa de correcao mexeu em activityDisplayLabel (App.tsx), que NAO e o caminho do transcript — o teste ficou verde numa funcao que a tela nao usa.
- agora summarizeActions usa o rotulo proprio da acao para kind browser. Os outros tipos (read/edit/command) continuam achatando com contador, de proposito.
- o teste passou a exercitar o CAMINHO REAL: chama summarizeActions com duas acoes de ferramentas diferentes e exige rotulos distintos. RED capturado antes: esperado Navegou no Chrome, Leu pagina no Chrome, recebido Usou o Chrome (2).
- endurecido tambem o teste do Rust: assert!(label.contains("Chrome")) passava tanto para o rotulo especifico quanto para o fallback, ou seja nao distinguia nada. Agora afirma o rotulo especifico por ferramenta.
PENDENCIA nao bloqueante (achado do QA): com muitas acoes de Chrome consecutivas num mesmo bloco, o rotulo vira uma tira longa. Refinamento futuro: deduplicar com contador e/ou limitar a ~4 rotulos com e mais N no Chrome.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* feat(transcript): cada acao do Chrome ganha sua propria linha
- varias acoes de Chrome no mesmo turno colapsavam numa linha so ('Usou ferramentas (2)'). O dono mandou a referencia do produto concorrente, onde cada acao e uma linha com icone e rotulo proprio, e pediu esse estilo.
- reusado o mecanismo que ja existia: 'isPerFileAction' (edicoes com caminho ganham linha propria em vez de 'Editou arquivos (2)') virou 'isOwnRowAction' e passou a incluir o kind browser. O comentario passou a explicar os DOIS motivos.
- decisao registrada: browser NAO exige 'detail' para ganhar linha propria. Se exigisse, a maioria das acoes do Chrome continuaria colapsando e o pedido nao seria atendido.
- agrupamento dos outros tipos preservado de proposito: dois 'read' continuam juntos, duas edicoes de arquivos diferentes continuam separadas.
- os testes novos exercitam o caminho de RENDER (render do StepFlow + consulta ao DOM real), nao so a montagem de blocos.
OBSERVACAO DE CAMPO (achado do QA): uma acao nao-browser imediatamente apos uma sequencia de browser pode se anexar ao ultimo bloco; se aparecer no uso, o ajuste e a linha propria tambem fechar o bloco.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* fix(chrome): apagar a moldura de presenca ao fim do turno via MCP
- a moldura roxa de presenca continuava acesa depois que a tarefa do Chrome terminava. Causa: navigate.js acende em toda navegacao, mas a limpeza so existia no ciclo de vida do turno PROPRIO da extensao — o caminho dirigido por MCP nao tinha nenhuma chamada de presenca.
- a primeira ancora cogitada (Drop do SessionGuard) foi DESCARTADA por evidencia: o host nativo e de vida longa, entao o Drop nao marca fim de turno.
- ancora escolhida: EOF do stdio do servidor MCP, que e spawnado por cada execucao do CLI. complete_turn() so e chamado depois de service.waiting(), nunca entre chamadas de ferramenta — a luz nao apaga no meio do trabalho.
- protocolo v2 com turnComplete/turnCompleteAck. Divergencia de versao falha limpo: extensao antiga responde protocol_version_mismatch com mensagem acionavel e o host segue; host antigo simplesmente nao manda o sinal, degradando ao comportamento anterior em vez de quebrar.
- PROTOCOL.md atualizado — os tres lados (protocol.rs, bridge.js, documento) agora concordam na versao 2 e nos seis tipos.
LIMITES DECLARADOS (achados do QA, nenhum bloqueante):
- cancelamento abrupto pode deixar residuo de moldura; e auto-curavel, porque o proximo turno de Chrome termina em EOF e limpa tudo.
- dois turnos concorrentes dirigindo o Chrome: o que terminar primeiro apaga a moldura que o outro ainda usa; ele re-acende na proxima navegacao. Cosmetico e raro.
- nao ha timeout de ACK no host: se a extensao travar sem responder nem desconectar, o relay fica pendente. Na pratica a porta desconecta e destrava.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* feat: ship side chats, settings redesign, and Chrome context
Add ephemeral side chats and selected-text Ask Verboo context, consolidate settings navigation, and render Chrome actions as individual transcript rows. Include protocol and presence cleanup, clearer interruption and error states, corrected translations, docs, tests, and the existing 0.6.2-beta metadata.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* docs: design interactive iOS simulator
* docs: plan interactive iOS simulator
* feat(chrome): intent classifier with serialized reclassification fallback and find/extract tools
- classify each turn by intent in the agent loop; when a conversation-classified
turn emits a browser tool call, re-run it with browser tools forced, serialized
through the browserControlQueue so concurrent turns never interleave actions
(loop.js, background.js, routerClient.js)
- add find.js and extractPageContent.js tools: find returns real clickable
references (text/tag/href/derived selector) for named targets; extract returns
the full page text beyond read_page's truncation, chunked by the loop
- bump extension to 0.3.1 (manifest version/version_name, package.json)
- update PRIVACY.md and privacy.html to match the feature
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* feat: ship iOS simulator, signed CLI updates, and Chrome automation (#67)
Integrate the accumulated authorized desktop work: macOS iOS simulator tooling, independently signed CLI installation and updates, cross-platform authentication/provider support, and durable background Chrome automation with native MCP integration.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* feat(providers): add multi-account usage to desktop
* feat(context): add memory context with localOnly filtering
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* fix(providers): harden account preflight with capability fallback, model cache, and CLI update notice
Addresses QA findings M1, M2, and M5: capability fallback, model cache, and CLI update notice.
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* fix(feedback): fallback opens a GitHub issue with stable supabase codes
Co-Authored-By: Verboo Code <noreply@code.verboo.ai>
* fix(providers): serde default for turn account, spawn canary, and dead wrapper removal
Co-Authored-By: Verboo…
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.
Scope
This PR intentionally carries the accumulated desktop work already present on the feature branch. It is not limited to the CLI updater.
The repository work is authorized by the Verboo owner. This PR does not present the desktop app as an official Verboo product.
Update and ownership boundaries
cli/directorycurrent.json, controls first-install gating and retryFirst-install behavior
iOS simulator behavior
Cross-platform authentication and credentials
Unprotectsecret-toollookup contract and retains the plaintext compatibility fallbackVerification
cargo xwin checkMobileCal: FrontBoard watchdog0x8BADF00Dafter a 30-second startup stall indyld_sim; Verboo remained alive and the stable iOS 26.5 runtime did not reproduce itThe local release script cannot sign the Tauri updater tarball without
TAURI_SIGNING_PRIVATE_KEY; that private key remains only in protected CI. The locally produced.appand DMG are Developer ID signed, but the local tarball is intentionally not a publishable updater artifact.Upstream readiness
Chrome background automation and MCP
Behavioral validation kept X in the foreground throughout a 56-step, approximately 175-second browser task, resumed and completed a stopped task after reopening the side panel, reused exactly one background window after extension reload, and completed form navigation, typing, clicking, reading, and screenshot capture under one task-scoped approval. The extension suite passes 410/410 tests; the live CLI doctor connected to
verboo-in-chromewith no findings.