Commit Graph

57 Commits

Author SHA1 Message Date
Ashish Huddar f330a1ea69
feat(cli): filter terminated sessions from ao session ls / ao status by default (#1340)
* feat(cli): filter terminated sessions from ao session ls / ao status by default

Closes #1310. Terminated sessions (killed/terminated/done/merged/errored/cleanup,
plus lifecycle-driven terminal states) are now hidden from `ao session ls` and
`ao status` by default. A dim footer reports how many were hidden and how to
surface them. Pass `--include-terminated` to restore the full list.

JSON output wraps into `{ data: [...], meta: { hiddenTerminatedCount } }` on
both commands so text and machine-readable views tell the same story. This is a
breaking change for script consumers of `--json`; `--include-terminated` is the
escape hatch.

Orthogonal to `-a, --all` (orchestrator visibility, unchanged). Restore of
terminated sessions by id is unaffected — that path goes through `sm.get`, not
`sm.list`.

Docs (`SETUP.md`, `docs/CLI.md`) updated to match. Tests cover both the legacy
status branch and the canonical lifecycle branch of `isTerminalSession`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(cli): drop unused `lc` param in lifecycle-alive test case

ESLint's no-unused-vars rejects unprefixed unused args. The "alive — should
remain visible" branch of the new lifecycle-driven filter test in
`session.test.ts` took `lc` but never touched it. Switch to `()`. Matches the
equivalent case in `status.test.ts`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(core): preserve pr.state=merged when legacy metadata lacks pr= URL

Review blocker on PR #1340: a metadata file with `status=merged` but no `pr=`
URL was still showing as active in `ao session ls` / `ao status` by default.

Root cause: `synthesizePRState()` in lifecycle-state.ts short-circuited to
`{ state: "none" }` whenever no PR URL was present, ignoring the fact that the
legacy `status` column already encodes terminal truth. Once lifecycle was
synthesized as `session.state="idle"` + `pr.state="none"`, `deriveLegacyStatus`
returned `"idle"` and `isTerminalSession()` (lifecycle branch) returned false.
The new CLI filter then let the session through.

Fix: when legacy `status === "merged"` and no URL is available, synthesize
`pr.state="merged", reason="merged"` with `number: null, url: null`. The
terminal signal survives the flat-metadata → canonical-lifecycle round trip.

Also:
- Export `sessionFromMetadata` from the core barrel. CLI tests and external
  consumers need it to round-trip metadata through the canonical lifecycle.
- Update CLI `buildSessionsFromDir` helpers to route through `sessionFromMetadata`
  so mocked `sm.list()` reflects production reconstruction (the old shortcut
  bypassed synthesis entirely and was the reason the bug slipped past the
  original test suite).
- Add regression tests: one at the core level (`parseCanonicalLifecycle` for
  merged-without-URL) and one integration-style test per CLI command asserting
  the reviewer's exact repro produces the expected filtered output.
- One pre-existing test expectation updated: when metadata has `status=working`
  and `pr=<url>`, the reconstructed status is `pr_open`, not `working`. That's
  what production `sm.list()` has always returned; the test was previously
  hiding behind the reconstruction shortcut.

Changeset bumped to include ao-core (patch).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(cli): route review-check helper through sessionFromMetadata

Last remaining test-fidelity shortcut flagged by codex on PR #1340. The
`buildSessionsFromDir` helper in review-check.test.ts fabricated Session
objects by hand, bypassing the canonical lifecycle reconstruction that
production `sm.list()` runs. Doesn't affect review-check's actual behavior
(which reads `session.metadata["pr"]` directly), but aligns this test with
the equivalent helpers in session.test.ts and status.test.ts so future
lifecycle changes don't silently skip this surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 13:05:34 +05:30
Ashish Huddar faaddb15df
feat(core): auto-terminate sessions on PR merge (#1309) (#1311)
* feat(core): auto-terminate sessions on PR merge (#1309)

When a session's PR was detected as merged, the session transitioned
to status "merged" but its tmux runtime, worktree, and metadata were
never cleaned up — leaving zombie tmux sessions and stale entries in
`ao status` / `ao session ls`. Users worked around this with an
external watchdog. Close the loop in AO itself.

Changes:
- `kill()` gains an optional `reason` and returns `KillResult`
  (`cleaned` / `alreadyTerminated`), with short-circuit paths so
  repeated calls on archived sessions are safe no-ops instead of
  throwing `SessionNotFoundError`.
- New `LifecycleConfig` (`autoCleanupOnMerge: true` default,
  `mergeCleanupIdleGraceMs: 5 min`) so operators can opt out when
  they need merged worktrees preserved for inspection.
- `lifecycle-manager` runs `maybeAutoCleanupOnMerge` at the end of
  each `checkSession`. Reactions and notifications observe the live
  session first; cleanup runs last. If the agent is still `active` /
  `waiting_input` / `blocked`, cleanup is deferred and retried on
  the next poll until the agent idles or the grace window elapses
  (prevents killing an agent mid-task).
- New `CanonicalSessionReason` / `CanonicalRuntimeReason` variants
  (`pr_merged`, `auto_cleanup`) so observability distinguishes
  automated teardown from manual kills.

Scope is deliberately narrow to `merged`: `done` / `errored` often
need the worktree preserved for debugging; `killed` would self-recurse.

Follows Codex review feedback (conditional pass): scope narrowed,
reactions-before-cleanup ordering, idleness safety gate, real
idempotency guards, config opt-in.

6 new unit tests cover: idle agent cleanup, active agent deferral,
grace-window force-cleanup, config opt-out, terminated/killed no
self-recursion, kill() failure retry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(core): clean up lifecycle config access per review

Address review comment on PR #1311. The `config.lifecycle` field is
typed optional but always populated by Zod — the old guard chain
(`if (lifecycleConfig && lifecycleConfig.autoCleanupOnMerge === false)`)
obscured that duality. Destructure with defaults at the call site so
the contract is visible in one place, and document why the field stays
optional (hand-constructed test configs) on the interface.

Matches the existing `power?: PowerConfig` pattern — keeps churn to
zero across 60 test config literals while removing the ambiguous guard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(core): surface auto-cleanup-on-merge in config, docs, and UI

Followup to DX audit on #1311. The lifecycle cleanup behavior was
operational but invisible — config key only in TS types, no changeset
for downstream consumers, missing observability spec, and a dashboard
summary that actively contradicted the new default-on behavior.

- agent-orchestrator.yaml.example: add commented `lifecycle:` block
  with both keys so operators discover the knob in the primary
  config reference.
- .changeset/auto-cleanup-on-merge.md: minor bump for @aoagents/ao-core
  with migration note (default-on, opt-out via config).
- docs/observability.md: document the three new lifecycle_poll
  operations (merge_cleanup.completed / deferred / failed) so
  dashboard/alert authors have a spec.
- packages/web/src/lib/serialize.ts: replace stale summary
  "PR merged; worker is still available for a keep-or-kill decision"
  with "PR merged; worker session will be cleaned up automatically".
  The old copy is wrong under default-on auto-cleanup.

Deferred to follow-up issues:
- Health surface degradation on repeated cleanup failure (the
  operator-facing gap Codex flagged — failures emit a metric but
  don't downgrade /api/observability health).
- Dashboard "cleaning up in Nm" indicator for the deferred state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(core,web): address PR review feedback for auto-cleanup on merge

- serialize.ts: only claim "will be cleaned up automatically" when
  mergedPendingCleanupSince marker is present; otherwise show neutral
  "PR merged". Avoids lying when autoCleanupOnMerge is opted out.
- lifecycle-manager.ts: use ACTIVITY_STATE constants instead of
  hardcoded strings, matching the existing SESSION_STATUS.MERGED usage.
- config.ts: keep mergeCleanupIdleGraceMs=0 as a valid escape hatch
  (immediate cleanup), but reject 1..9999 with a units-mistake error
  so users typing `5` (intending seconds) get a clear message.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 12:56:07 +05:30
harshitsinghbhandari 0d03c46ca5 Merge upstream/main into session/aa-22-lifecycle-fixes
Resolved conflicts in:
- packages/web/src/lib/types.ts (merged canonical type imports with DashboardAttentionZoneMode)
- packages/web/src/lib/dashboard-page-data.ts (kept upstream imports)
- packages/web/src/components/Dashboard.tsx (kept upstream mobileKanbanOrder)

Updated getAttentionLevel to support mode parameter from upstream
while preserving lifecycle-aware helper functions.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-18 00:53:06 +05:30
harshitsinghbhandari 93fa946ac8 docs: add working principles to contributor docs
Document the Karpathy-style working principles in CLAUDE.md and add condensed references in contributor-facing docs so humans and agents follow the same guidance.
2026-04-18 00:32:10 +05:30
Harshit Singh Bhandari 2ea4385a61
Merge pull request #115 from harshitsinghbhandari/docs/stage2-evidence-recovery-plan
docs: add Stage 2 evidence and recovery plan
2026-04-16 22:23:32 +05:30
harshitsinghbhandari 4c2dcbf506 docs: add stage 2 evidence and recovery plan (#95) 2026-04-16 22:22:23 +05:30
harshitsinghbhandari 81d88c4412 fix(core): address lifecycle review feedback 2026-04-15 12:41:06 +05:30
harshitsinghbhandari 4e18198868 docs: add stage 1 lifecycle redesign plan 2026-04-15 10:30:19 +05:30
Dhruv Sharma ba899f9c30
Merge pull request #1104 from ComposioHQ/feat/issue-1045
refactor: remove ao spawn --decompose feature
2026-04-11 10:22:22 +05:30
Harsh Batheja 3fb2ddf06d refactor: remove --decompose feature entirely
The --decompose flag and supporting decomposer module had two
unfixable bugs (#1045): it crashed when ANTHROPIC_API_KEY was unset,
and it created multiple branches/PRs per issue, fragmenting history
and complicating review/merge. Removing the feature instead of
patching either bug.

- Delete packages/core/src/decomposer.ts and all re-exports
- Drop @anthropic-ai/sdk dependency from @composio/ao-core
- Remove --decompose / --max-depth options from ao spawn
- Remove decomposer field from ProjectConfig (zod default-strip
  silently ignores existing decomposer: blocks in user yaml)
- Remove lineage / siblings from SessionSpawnConfig and the
  prompt-builder Layer 4 block (only used by decomposer)
- Gut the matching backlog reactor branch in web/services.ts
  so the polling path no longer hits the same bugs
- Remove decompose parameter from openclaw-plugin ao_spawn tool
- Drop decomposer mocks from web services.test.ts
2026-04-10 13:02:52 +05:30
ChiragArora31 98c5e1518c Merge upstream/main into feat/session-ls-json 2026-04-10 06:43:25 +05:30
ChiragArora31 ea3da99c51 Merge upstream/main into feat/session-ls-json 2026-04-09 22:24:37 +05:30
Prateek 27712442f8 fix: restore GitHub repo URLs to ComposioHQ/agent-orchestrator — only npm scope changed 2026-04-09 16:00:32 +00:00
Prateek 967e864f5a chore: rename @composio scope to @aoagents across all packages
Renames all npm package scopes from @composio/* to @aoagents/* and
updates GitHub repo references from ComposioHQ/agent-orchestrator
to aoagents/ao throughout the codebase.

- All package.json names and dependencies
- README badges, links, and install instructions
- Documentation references
- Changeset config
- Source code imports and test files
2026-04-09 15:59:33 +00:00
Gaurav Bhola 4684b75def feat(web): single-socket — multiplex terminals + sessions over one WebSocket
Replace three separate real-time channels (per-terminal WS, SSE, HTTP poll)
with a single persistent multiplexed WebSocket at /mux.

Architecture:
- Browser ↔ MuxProvider owns one WS connection per tab (/mux)
- Terminal I/O, resize, open/close all flow over mux channels
- Session status patches delivered via a shared SSE relay:
  mux server subscribes once to Next.js /api/events (SSE) and
  broadcasts to all connected browser clients — no per-client polling
- Manual WS upgrade routing fixes ws library limitation with multiple
  WebSocketServer instances on the same HTTP server

Remove:
- terminal-websocket.ts (legacy ttyd-based per-session server, port 14800)
- Per-terminal WebSocket connections from DirectTerminal
- Per-client 5 s HTTP polling for session patches

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 22:46:29 -07:00
ChiragArora31 f19f46b30e feat(cli): add json output to session ls 2026-04-07 08:41:10 +05:30
Dhruv Sharma 2febded969
Merge pull request #836 from ChiragArora31/feat/status-watch-mode
feat(cli): add watch mode to ao status
2026-04-01 12:28:32 +05:30
ChiragArora31 2d6bc7671d feat: add watch mode to status command 2026-04-01 09:26:59 +05:30
ChiragArora31 4420ab1773 feat(cli): add plugin resolution checks to ao doctor 2026-04-01 06:39:14 +05:30
Dhruv Sharma 846ddc4135 refactor: enhance OpenClaw plugin interfaces and improve setup options 2026-03-31 04:56:03 +05:30
Dhruv Sharma 7445e134c4 add plugin spec and update docs 2026-03-31 04:02:25 +05:30
Deepak Veluvolu 852f1f93c8
fix: added GraphQL batch PRfeat: add GraphQL batch PR enrichment for orchestrator polling enrichment for orchestrator polling (fixes #608) (#637)
## Approach

  The orchestrator polling loop previously made individual API calls for each PR's
  state, CI status, and review decision - 3 separate calls per PR per poll.
  With multiple PRs being monitored, this quickly exhausted GitHub's 5,000-point
  hourly rate limit.

  This PR implements GraphQL batching using aliases, which allows fetching data
  for up to 25 PRs in a single GraphQL query. Additionally, a 2-Guard ETag
  strategy is used to skip queries entirely when nothing has changed.

  ## Implementation

  ### GraphQL Batching
  - `generateBatchQuery()` creates a single GraphQL query with unique aliases (pr0, pr1, pr2...)
  - Each PR gets the same set of fields: state, CI status, review decision, mergeability
  - Uses inline fragments for union types (CheckRun/StatusContext)
  - Variable types: String! for owner/repo, Int! for PR numbers

  ### 2-Guard ETag Strategy
  Before running expensive GraphQL queries, two lightweight REST ETag checks detect if
  anything changed:

  **Guard 1 (PR List ETag):**
  - Checks `/repos/{owner}/{repo}/pulls` with If-None-Match header
  - Returns 304 if no changes → skips GraphQL (0 points)
  - Detects: New commits, title/body edits, labels, reviews, state changes

  **Guard 2 (Commit Status ETag):**
  - Checks `/repos/{owner}/{repo}/commits/{sha}/status` per cached PR
  - Returns 304 if no changes → skips GraphQL (0 points)
  - Detects: CI status transitions (failing → passing, passing → failing, etc.)

  ### Caching
  - LRU caches for PR metadata (max 200 entries), ETags (100/500 entries)
  - Cache misses trigger individual API fallback via lifecycle-manager
  - No placeholder caching on errors - allows proper fallback behavior

  ## Impact

  - **API reduction:** ~88% fewer REST calls (216 vs 1,800 calls/hour for 5 PRs)
  - **GraphQL efficiency:** Batch query fetches 25 PRs for ~40 points vs ~400 for individual calls
  - **Polling interval:** Still 30s, but most polls return cached data (0 cost)
  - **Fallback:** Individual SCM calls still work for edge cases (permissions, cache misses)

  ## Testing

  - Unit tests for query generation and parsing helpers
  - Integration tests for real GraphQL API calls (skipped by default)
  - Covers batch failures, partial success, empty arrays, edge cases
2026-03-30 00:31:04 +05:30
suraj-markup 5315e4e0ab fix(web): runtime terminal port resolution + project-id hardening 2026-03-28 19:23:15 +05:30
Dhruv Sharma 5933991252
Merge pull request #631 from illegalcall/feat/openclaw-integration
feat: OpenClaw plugin, AO skill, Discord notifier, and setup wizard
2026-03-27 09:48:25 +05:30
Dhruv Sharma 570f3b588f fix: address CodeRabbit review comments
- Use consistent regex for OPENCLAW_HOOKS_TOKEN detection and replacement
  in shell profile (prevents silent no-ops for non-exported lines)
- Broaden token detection regex to match lines with/without export prefix
  and leading whitespace
- Fix misleading --non-interactive help text (token is auto-generated)
- Fix doctor.ts catch block to say "Notifier checks failed" not "load config"
- Fix 204 mock in Discord notifier test (ok: true, not ok: false)
- Fix weak no-duplicate assertion in setup.test.ts (actually count list items)
- Add discord to notifier options comment in config-instruction.ts
- URL-encode threadId in Discord webhook URL construction
- Add aoCwd to required[] in openclaw.plugin.json configSchema
- Add HTTPS recommendation comment to agent-orchestrator.yaml.example
- Add rimraf for cross-platform clean script in notifier-discord
- Rename "Recommended Settings" to "Required: Disable Conflicting Built-in Skills"
  with explicit warning in docs
- Add /ao setup post-setup reminder to manually disable coding-agent skill
- Fix misleading README non-interactive example wording

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 21:58:19 +05:30
suraj-markup cbdf2d2da0 docs: sync install-flow design doc with current start/preflight behavior 2026-03-26 16:05:59 +05:30
suraj-markup fea320e92a docs: add prerequisite matrix and install behavior details
Expand the Interactive Prerequisite Prompts section with:
- Full prerequisite matrix table (tmux, git, agent runtime, gh, docker)
  showing required status, prompts, install methods per platform, and
  failure behavior
- Key behaviors list (consent-first, logging, blocking, non-TTY fallback)
- Example terminal output for fresh machine install flow

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 18:09:00 +05:30
suraj-markup fafe76c45a docs: upgrade flow diagrams to visual CSS flowcharts
Replace ASCII art flow diagrams with styled HTML/CSS flowcharts using
color-coded nodes (blue=command, yellow=decision, green=success,
red=failure) and proper branching layout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 17:53:43 +05:30
suraj-markup eee9d773f4 fix: interactive tmux install with user consent, log before auto-install
- Refactor ensureTmux() to use askYesNo() + tryInstallWithAttempts()
  matching the existing ensureGit() pattern — no silent sudo
- Add log line before each auto-install attempt in preflight.checkTmux()
- Update config warning: "will prompt to install at startup"
- Update design doc to reflect interactive consent flow
- Include prior uncommitted changes: interactive install prompts for
  git/gh/agent-runtime, ensureGit(), canPromptForInstall(), askYesNo()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 17:46:56 +05:30
suraj-markup 9d15ce5d16 fix(start): enforce tmux preflight across all start paths 2026-03-24 15:24:26 +05:30
suraj-markup f54630021c fix: block with clear tmux install instructions instead of process fallback
A degraded runtime gives a bad first impression. Instead of silently
falling back to runtime: "process", ao start now:
1. Tries auto-install (brew/apt/dnf)
2. On failure, exits with clear platform-specific install command
3. User runs one command, then re-runs ao start

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 03:40:15 +05:30
suraj-markup 761173456d fix: true two-step setup — tmux auto-install, process fallback, restart bookkeeping
- checkTmux() now attempts auto-install (brew/apt/dnf) before erroring
- Config generation uses runtime: "process" when tmux unavailable
- start.ts tries auto-install during config creation, falls back gracefully
- Fix restart bookkeeping in start-all.ts: slot-based tracking prevents
  duplicate children entries that broke cleanup countdown
- Updated design doc reflecting all fixes and review feedback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 03:31:06 +05:30
Dhruv Sharma 36d354e0c2 feat: OpenClaw plugin, AO skill, Discord notifier, and setup wizard
Adds bidirectional integration between Agent Orchestrator and OpenClaw,
enabling AI bots on Discord/Telegram/WhatsApp to manage coding agent
fleets through natural conversation.

OpenClaw Plugin (openclaw-plugin/):
- 14 AI tools: ao_spawn, ao_issues, ao_sessions, ao_status, ao_batch_spawn,
  ao_send, ao_kill, ao_session_restore, ao_session_cleanup,
  ao_session_claim_pr, ao_review_check, ao_verify, ao_doctor, ao_session_list
- Hooks: message_received + before_prompt_build for live data injection
- /ao slash command with subcommands (sessions, spawn, issues, doctor, setup)
- Background services: health monitor + issue board scanner
- Security: execFileSync with arg arrays (no shell injection)

AO Skill (skills/agent-orchestrator/):
- Natural language intent → AO tool mapping
- Decision heuristic: quick fix → direct, multi-issue → AO
- Designed for ClawHub publishing (blocked on ClawHub server bug)

CLI: ao setup openclaw (packages/cli/src/commands/setup.ts):
- Interactive wizard + non-interactive mode
- Auto-detects OpenClaw gateway on localhost
- Auto-generates secure token
- Writes both configs (agent-orchestrator.yaml + openclaw.json)
- Appends OPENCLAW_HOOKS_TOKEN to shell profile
- Validates connection end-to-end

CLI: ao doctor notifier checks:
- Probes OpenClaw gateway reachability + token validity
- --test-notify flag sends test event through all configured notifiers
- Failure count propagated to exit code

Discord Notifier (packages/plugins/notifier-discord/):
- Rich webhook embeds with colors, fields, action links
- Retry with exponential backoff + Discord Retry-After header
- Request timeouts, embed truncation, webhook URL validation
- thread_id via URL query string (Discord API requirement)

OpenClaw Notifier improvements:
- Added request timeouts via AbortController
- Improved README

Reviewed through 5 rounds of Codex code review.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 21:52:28 +05:30
suraj-markup bce49a1981 docs: move CLI reference out of README, focus on user experience
The README should sell the experience, not list CLI flags. Humans
interact with `ao start` and the dashboard — the CLI is primarily
used by the orchestrator agent internally.

- Move full CLI reference to docs/CLI.md with clear sections:
  "commands humans use" vs "commands the orchestrator agent uses"
- Rewrite README to focus on install → start → done flow
- Remove CLI and Maintenance sections from README
- Simplify "How It Works" to describe the system, not CLI commands
- Add CLI Reference to Documentation table
- Collapse source install into <details> to reduce noise

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 07:23:32 +05:30
suraj-markup 61bb9ea108 Update design doc: spawn no longer accepts project arg
- Section 5: spawn/batch-spawn descriptions updated
- Section 9: mermaid diagram simplified (no two-arg branch)
- Section 10: scenarios #8, #11, #14 updated
- Section 12: ID collision finding updated (no ambiguity possible)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 14:34:11 +05:30
suraj-markup 0dfc75f592 Expand design doc section on ao-web npm publishing
Add detailed coverage of the npm dashboard publishing story:
- Root cause table (5 blockers that prevented publishing)
- Before/after with actual error output
- Package size bar chart (67 MB → 1 MB) with files field breakdown
- Production architecture flowchart (start-all.js process tree)
- End-to-end npm user journey flowchart
- Impact grid (crash → works, 67 MB → 1 MB, 1 → 2 startup modes)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 19:04:57 +05:30
suraj-markup 14fa726551 Publish ao-web on npm, harden error handling, deduplicate code
- Publish @composio/ao-web dashboard as npm package (removed private flag,
  added files field, production entry point, node-pty made optional)
- CLI auto-detects dev vs production mode for dashboard startup
- Use local next binary instead of npx in production start-all.ts
- findWebDir() throws with install-specific guidance instead of returning
  broken path
- Fix CI-silent failure: setup.sh and ao-update.sh exit 1 on non-interactive
  npm link failure
- Deduplicate detectDefaultBranch into shared cli/lib/git-utils.ts
- Add EACCES permission guidance to README.md and SETUP.md
- Move resolveProjectIdForSessionId to @composio/ao-core
- Update design doc with problems #8-13, changes #9-12, known limitations
  section, and expanded test plan

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 19:01:49 +05:30
suraj-markup 90fe0e09f8 Add contextual next-step hints with directory guidance
Every command now prints what to run next and from where:
- setup.sh → tells user to cd to project dir and run ao init
- ao init → tells user to run ao start (with directory context)
- ao add-project → tells user to run ao start and ao spawn
- ao start → tells user to run ao spawn <project> <issue>

Eliminates confusion about running commands in the wrong directory
(e.g., ao init inside agent-orchestrator instead of the project repo).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 22:07:58 +05:30
suraj-markup 59e2c74bd3 Add flowcharts and bar charts to design doc
- npm link retry logic decision tree
- ao init auto vs interactive decision flow
- ao add-project 8-step pipeline diagram
- ao start port resolution flowchart
- Before/after horizontal bar chart for UX metrics

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 21:33:20 +05:30
suraj-markup 2df2d2f45e Replace design doc with HTML version
Swap markdown design doc for a styled HTML version with dark theme,
before/after comparisons, impact metrics, and flow diagrams.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 21:20:25 +05:30
suraj-markup fa6c475d48 docs: add design doc for onboarding improvements, update README
- Add design doc covering all changes, UX impact, and new flow
- Update README: add ao init, ao add-project, ao start to Quick Start and CLI sections
- Remove --auto flag from README examples (now the default)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 20:55:50 +05:30
Andrés Kaminker b064925621
docs: add CONTRIBUTING.md, expand development guide, fix broken CLAUDE.md links (#448)
* docs: add CONTRIBUTING.md, expand development guide, fix broken CLAUDE.md links

- Add CONTRIBUTING.md covering bug reports, dev setup, plugin development, PR process
- Expand docs/DEVELOPMENT.md into a comprehensive architecture + conventions reference
  (architecture overview, plugin pattern with full example, spawn flow, TypeScript and
  shell command conventions, key design decisions, common dev tasks)
- Update README.md docs table to reference docs/DEVELOPMENT.md instead of gitignored CLAUDE.md
- Fix broken CLAUDE.md links in SETUP.md to point to docs/DEVELOPMENT.md

CLAUDE.md is gitignored (personal agent config) so links to it were always broken.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: document ao update workflow and fix guide links

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Harsh <harshb012@gmail.com>
2026-03-12 22:32:33 +05:30
Harsh Batheja b04d3e21de
feat: add end-to-end observability across core, web, and terminal (#436)
* feat: add end-to-end observability across core, web, and terminal

Instrument lifecycle/API/websocket flows with correlation-aware metrics and operator health surfaces so the system can self-diagnose and escalate failures.

* fix: realign session restore set and unblock claim PR typecheck

* fix(web): restore project-filtered sessions route after main merge

* fix(core): remove unused orchestrator import to unblock lint

* fix(core): always record lifecycle poll failures and remove dead review branches

* fix(web): harden websocket metrics and reuse SSE observers

* fix(web): preserve primary session API errors when services bootstrap fails

* fix(web): use full orchestrator config type in SSE observer helper

* fix(web): restore project-scoped SSE and guard observability error paths

* fix(web): address remaining Bugbot review gaps

* fix(web): align SSE project attribution and share session project resolver

* fix(web): require request arg for SSE route and align tests

* fix(web): record websocket error disconnects as failures

* fix(web): use path alias for session project resolver import

* fix(web): include active connection count in disconnect metrics
2026-03-12 16:57:40 +05:30
Harsh Batheja c7c04c14df
feat(web): Project-scoped dashboard with sidebar navigation (#381)
* feat(web): add project-based dashboard architecture

- Add project query parameter to API routes
- Filter sessions by project in both SSR and SSE
- Update useSessionEvents hook to accept project param
- Update Dashboard and pass project to hook
- Add unit tests for project filtering
- Add architecture spec document

Implements project-based architecture as defined in docs/specs/project-based-dashboard-architecture.md:
- GET /api/sessions?project=X returns sessions for project X
- GET /api/events?project=X streams only sessions for project X
- Dashboard uses project filter from config
- SSE URL includes project param when provided
- Project matching uses projectId and sessionPrefix
- Full backward compatibility maintained (no param = all sessions)

* fix: remove duplicate export default in page.tsx

* fix(web): clean project-scoped dashboard verification

* fix(web): scope dashboard using project id

* fix(web): address Bugbot findings in project-scoped dashboard

- Consolidate triplicated matchesProject into shared lib/project-utils.ts
- Fix Dashboard to receive both projectId (for SSE filtering) and projectName (for display)
- Remove inline matchesProject definitions from page.tsx, sessions/route.ts, events/route.ts

* fix(web): resolve Bugbot issues in project-scoped dashboard

- Add ?project=all query param support in SSR page to show all sessions
  (previously getPrimaryProjectId() always returned non-empty, making
  else branches unreachable)
- Exclude orchestrator sessions from SSE stream to match SSR/API behavior
  (orchestrator sessions get their own button, not a card)
- Add tests for SSE stream orchestrator exclusion and project filtering

Addresses Bugbot issues:
- #2903595986: Dead else branches when project filter always applied
- #2903595995: SSE stream includes orchestrator sessions unlike SSR/API

* feat(web): add project navigation sidebar

Add visible left sidebar with project navigation for multi-project setups:
- ProjectSidebar component with active state styling
- /api/projects endpoint to fetch configured projects
- getAllProjects() helper in project-name.ts
- Sidebar appears only when 2+ projects configured
- Click navigation updates ?project= query param
- Active project highlighted with accent color

Tests:
- ProjectSidebar component tests (8 tests)
- API routes tests with proper mocking
- All 386 web tests passing

Manual test steps:
1. Configure 2+ projects in agent-orchestrator.yaml
2. Start dashboard - sidebar should appear on left
3. Click different projects - URL updates, sessions filter
4. Click "All Projects" - shows all sessions across projects
5. Active project highlighted in sidebar

* fix(web): remove duplicate import in test file

* fix(web): consolidate ProjectInfo type to shared source

Remove duplicated ProjectInfo interface definitions from Dashboard.tsx and
ProjectSidebar.tsx. Both now import the type from @/lib/project-name where it is exported as the single source of truth.

This addresses Bugbot issue #2906835895: Triplicated ProjectInfo type instead of shared import.

* fix(web): integrate globalPause state from main

* fix(web): consolidate duplicate @/lib/types import

* feat(web): add project-based dashboard architecture

- Add project query parameter to API routes
- Filter sessions by project in both SSR and SSE
- Update useSessionEvents hook to accept project param
- Update Dashboard and pass project to hook
- Add unit tests for project filtering
- Add architecture spec document

Implements project-based architecture as defined in docs/specs/project-based-dashboard-architecture.md:
- GET /api/sessions?project=X returns sessions for project X
- GET /api/events?project=X streams only sessions for project X
- Dashboard uses project filter from config
- SSE URL includes project param when provided
- Project matching uses projectId and sessionPrefix
- Full backward compatibility maintained (no param = all sessions)

* fix: remove duplicate export default in page.tsx

* fix(web): clean project-scoped dashboard verification

* fix(web): scope dashboard using project id

* fix(web): address Bugbot findings in project-scoped dashboard

- Consolidate triplicated matchesProject into shared lib/project-utils.ts
- Fix Dashboard to receive both projectId (for SSE filtering) and projectName (for display)
- Remove inline matchesProject definitions from page.tsx, sessions/route.ts, events/route.ts

* fix(web): resolve Bugbot issues in project-scoped dashboard

- Add ?project=all query param support in SSR page to show all sessions
  (previously getPrimaryProjectId() always returned non-empty, making
  else branches unreachable)
- Exclude orchestrator sessions from SSE stream to match SSR/API behavior
  (orchestrator sessions get their own button, not a card)
- Add tests for SSE stream orchestrator exclusion and project filtering

Addresses Bugbot issues:
- #2903595986: Dead else branches when project filter always applied
- #2903595995: SSE stream includes orchestrator sessions unlike SSR/API

* feat(web): add project navigation sidebar

Add visible left sidebar with project navigation for multi-project setups:
- ProjectSidebar component with active state styling
- /api/projects endpoint to fetch configured projects
- getAllProjects() helper in project-name.ts
- Sidebar appears only when 2+ projects configured
- Click navigation updates ?project= query param
- Active project highlighted with accent color

Tests:
- ProjectSidebar component tests (8 tests)
- API routes tests with proper mocking
- All 386 web tests passing

Manual test steps:
1. Configure 2+ projects in agent-orchestrator.yaml
2. Start dashboard - sidebar should appear on left
3. Click different projects - URL updates, sessions filter
4. Click "All Projects" - shows all sessions across projects
5. Active project highlighted in sidebar

* fix(web): remove duplicate import in test file

* fix(web): consolidate ProjectInfo type to shared source

Remove duplicated ProjectInfo interface definitions from Dashboard.tsx and
ProjectSidebar.tsx. Both now import the type from @/lib/project-name where it is exported as the single source of truth.

This addresses Bugbot issue #2906835895: Triplicated ProjectInfo type instead of shared import.

* fix(web): integrate globalPause state from main

* fix(web): consolidate duplicate @/lib/types import

* fix(web): restore global pause state and membership refresh

* fix(web): satisfy lint in project-scoped page defaults

* fix(web): remove dead global pause reducer action

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix(web): restore global pause resume time

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* chore(web): retrigger bugbot after thread reset

* refactor(web): centralize project session filtering helpers

* fix(web): reset pause banner dismissal on new pause

* fix(web): remove useless orchestrator assignment

* fix(web): derive header stats from live session state

* fix(web): restore project name in dashboard header

* fix(web): restore backlog poller startup in events stream

* refactor(web): keep project-utils helpers internal

* chore: retrigger bugbot evaluation

* chore: retrigger stuck bugbot check

* fix: address latest bugbot findings for dashboard events

* test(web): add dashboard bugbot regression coverage

* refactor(web): simplify projectId selection in dashboard props

* fix(web): derive selected project name from project filter

* refactor(web): initialize dashboard defaults before service load

* fix(web): satisfy lint in dashboard page error path
2026-03-11 09:22:37 +05:30
prateek 80d7132476
docs: remove remaining PR-specific review artifact 2026-03-10 22:40:32 +05:30
prateek 8e8cab771f
feat(core): add feedback tools contracts, validation, storage, and dedupe
* feat(core): add feedback report tool contracts and storage

* fix(core): stabilize feedback dedupe and clear lint blocker

* fix(core): harden feedback report parsing and resilience

* docs: add feedback routing architecture and PR explainer

* fix(core): remove dedupe collisions and share atomic writes

* docs: add final PR403 bugbot resolution status

* docs: formalize feedback pipeline and fork-aware execution design

* docs: remove PR-specific review artifacts

* docs: add durable feedback pipeline explainer

* docs: add consent gates and journal semantics to feedback design

* docs: add pr403 confidence checklist with openclaw dogfood evidence

* docs: add work openclaw validation prompt helper page

* docs: record work openclaw validation evidence

* docs: remove pr-specific review artifacts from repo

* fix(core): ignore confidence in feedback dedupe key
2026-03-10 22:31:39 +05:30
Harsh Batheja 4e2144d99e
feat: OpenCode session lifecycle and CLI controls (#315)
* feat: refine OpenCode session reuse strategy and cleanup

* fix: harden OpenCode session selection and lint errors

* refactor: centralize OpenCode reuse resolution flow

* fix: return 404 for missing session in message route

* feat: replace force remap with terminal reload control

* fix: protect project path from session kill cleanup

* fix: preserve fullscreen alignment without reload action

* fix: harden OpenCode session id handling and title reuse selection

* fix: show OpenCode reload control and remap before restart

* fix: preserve title-only OpenCode reuse with fallback mapping persistence

* fix: resolve remaining PR315 Bugbot findings

* fix: keep OpenCode discovery title-based without timestamp sorting

* fix: avoid enrichment race fallout in session listing

* fix: guard OpenCode discovery parse with array check

* fix: stabilize Linear comment integration check

* fix: harden OpenCode discovery and prompt option flow

* ux: clarify OpenCode terminal restart action

* fix: remap OpenCode session fresh on each restart

* fix: validate remap session ids before reuse

* fix: clean archived metadata only after purge

* docs: align OpenCode remap selection with title-based behavior

* fix: harden opencode cleanup and ignore local sisyphus state

* test: add timeout cleanup coverage for session enrichment

* fix: harden Linear integration helper against transient non-JSON errors

* fix: make linear integration assertions resilient to eventual consistency

* fix: remove unused fs import after rebase

* fix: address remaining Bugbot blockers for opencode session handling

* fix: avoid stale metadata overwrite during restore post-launch

* fix: forward subagent in orchestrator flows and defer reuse lookup

* fix: apply configured subagent fallback for session spawn

* fix: scope archived cleanup by project and delay archive restore write

* fix: normalize orchestrator strategy aliases in start display logic

* fix: centralize orchestrator strategy normalization in core

* fix: derive orchestrator reuse display from spawn result

* fix: keep cleanup results consistent across project-id collisions

* fix: namespace cleanup results when session IDs collide

* fix: harden GitHub issue stateReason fallback

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix: avoid false failing CI state mapping

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix: add tmux command timeouts

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix: bound session API enrichment latency

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix: repair scm-github merge resolution

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix: repair lifecycle-manager test merge

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix: delay archive metadata recreation until restore passes

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* test: restore claim-pr session mocks

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix: address review findings for OpenCode lifecycle PR

- Fix stripControlChars to preserve newlines for reload commands
- Add SessionNotFoundError and use instanceof checks in API routes
- Document orchestratorSessionStrategy in YAML example
- Validate existingSessionId with asValidOpenCodeSessionId()
- Extract inline Node script to buildSessionLookupScript helper
- Create OpenCodeSessionManager interface for remap capability
- Create OpenCodeAgentConfig type for agent-specific config
- Change default orchestratorSessionStrategy from delete to reuse

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: guard reused session display without metadata

* chore: add agent config files to .gitignore

Agent configuration files (CLAUDE.md, AGENTS.md, IMPROVEMENTS.md, etc.) are personal and project-specific. They should not be committed to the repository.

Changes:
- Remove CLAUDE.md from git tracking
- Add agent config files to .gitignore
- Create .gitignore-template for reference

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore: update gitignore for agent config folder structure

Reorganized agent configuration files:
- CLAUDE.md and AGENTS.md stay in root (agents read them there)
- Tracking files move to .opencode/ (IMPROVEMENTS.md, etc.)
- Optional Claude files in .claude/

Updated .gitignore to ignore folders instead of individual files.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: tighten opencode remap and session discovery safeguards

* fix: address Bugbot findings in session manager

* fix: scope opencode discovery to opencode sessions

* fix: restore concurrent listing and strict permission literals

* fix: throw SessionNotFoundError, parallelize list enrichment, fix permissions type

- Session manager now throws SessionNotFoundError instead of plain Error
  for missing sessions, so web API routes correctly return 404 (not 500)
- Parallelize session enrichment in list() — was sequential, causing O(N)
  latency for N sessions with subprocess enrichment
- Fix AgentLaunchConfig.permissions type to accept legacy "skip" value
  (AgentPermissionInput instead of AgentPermissionMode)
- Add happy-path and validation tests for /api/sessions/:id/message route
- Update all test mocks to use SessionNotFoundError

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: route ao send through session manager

* feat: add purge option to orchestrator stop

* fix: register opencode agent in web services

* test: align web API missing-session coverage

* fix: match notifier config by plugin name

* refactor: dedupe session lookup and tmux buffer send flow

* test: update send lifecycle wait expectation

* fix: harden send routing and cleanup purge controls

---------

Co-authored-by: Harsh <harsh@Ubuntu-24-Forrest.lan>
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Prateek <karnalprateek@gmail.com>
2026-03-08 09:55:44 +05:30
Jayesh Sharma 8801502871
feat: add PR claim flow for agent sessions (#326)
* feat: add PR claim flow for agent sessions

* feat: support PR claim during spawn

* fix: address PR review feedback

* feat: add lifecycle worker automation

* fix: prevent duplicate messages on unconfirmed delivery and deduplicate review prompt

sendWithConfirmation now treats unconfirmed delivery as a soft success
since the message was already sent, preventing the dispatch hash from
staying stale and causing duplicate sends on the next poll cycle.

review-check command now sources its prompt from the lifecycle reaction
config instead of hardcoding it, keeping both paths aligned.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: pass AO_CONFIG_PATH to spawned lifecycle worker and log duplicate-worker early exit

The spawned lifecycle worker now receives AO_CONFIG_PATH in its
environment so it finds the correct config regardless of CWD. Also
added a log message when exiting early due to an existing worker.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: prevent lifecycle worker from clearing metadata on SCM fetch failures

SCM methods (getPendingComments, getAutomatedComments) silently returned []
on error, causing maybeDispatchReviewBacklog to treat fetch failures as
"no comments" and clear all tracking metadata every poll cycle. The worker
appeared to do nothing because it kept resetting its own state.

- SCM methods now propagate errors instead of returning []
- maybeDispatchReviewBacklog distinguishes null (fetch failed, skip) from
  [] (confirmed empty, safe to clear)
- pollAll() logs errors instead of silently swallowing them
- Added heartbeat logging and stdout flush before process.exit

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add debug breadcrumbs when review comment fetch fails

Logs a console.debug message when getPendingComments or
getAutomatedComments fails, making it clear the lifecycle loop is
preserving metadata due to a fetch failure rather than genuinely
having no comments.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: scope lifecycle worker polling to its project and add missing test

pollAll() now passes the worker's projectId to sessionManager.list(),
so each per-project lifecycle worker only polls its own sessions. This
prevents duplicate SCM API calls and race conditions in multi-project
configs.

Also fixed misleading test name and added a test verifying that
--no-dashboard alone still starts the lifecycle worker.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove dead confirmation check and respect project-level reaction overrides

Removed the unreachable "could not be confirmed" guard from the retry
logic since sendWithConfirmation now returns silently on unconfirmed
delivery.

review-check now resolves the prompt per-session by checking
project-level reaction overrides before falling back to the global
config, matching lifecycle worker behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: close TOCTOU race in lifecycle worker spawn and unexport getRegistry

Write the child PID from the parent immediately after spawn, closing
the window where a concurrent ensureLifecycleWorker could pass the
"not running" check and spawn a duplicate worker.

Also removed the unnecessary export on getRegistry since it has no
external consumers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: always include ao base prompt on spawn

* fix: clear heartbeat on shutdown and add initial delay to confirmation loop

Clear the heartbeat interval in the shutdown handler to prevent it
firing during the stream-flush window after lifecycle.stop().

Move the sleep in sendWithConfirmation to before each check (including
the first), so the runtime has time to reflect the message before
the first confirmation attempt.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve lint errors in lifecycle and session manager

- Replace dynamic delete with object reconstruction in lifecycle-manager
- Add { cause } to re-thrown errors in session-manager for preserve-caught-error rule

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 03:54:19 +05:30
Sujay Choubey 17c79b21a0 Add Composio banner to README 2026-03-03 22:43:23 +05:30
prateek 001d411219
Vibrant 3D gradient CTA buttons for README (#209)
* docs: add demo video and article links to README

Add tweet screenshot for the video demo prominently after the intro,
and link to the full article thread. Replaces the TODO placeholder.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: use article screenshot for README article link

Replace text-only article link with clickable screenshot showing
the article title, preview image, and engagement stats.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: replace demo video screenshot with higher quality version

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: vibrant 3D gradient CTA buttons for README

- Replace flat black shields.io badges with custom SVG buttons
- Purple gradient for "Watch the Demo" with play icon
- Coral gradient for "Read the Full Article" with book icon
- Both have 3D shine effect (top highlight, bottom shadow)
- High contrast on both GitHub light and dark mode
- Update article screenshot with cleaner version
- Images remain clickable (already wrapped in <a> tags)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: remove white borders from CTA buttons

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: use manually captured PNG buttons instead of SVGs

Playwright screenshots introduce 1-2px border artifacts on SVG img
elements (microsoft/playwright#35014). Use clean manually-captured
PNG screenshots instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 03:11:44 +05:30