Commit Graph

1274 Commits

Author SHA1 Message Date
whoisasx 0b0b266bb4 Merge origin/main and address review feedback 2026-05-19 14:17:39 +05:30
Harshit Singh Bhandari ee3fb5d33a
fix(web): make Done/Terminated dashboard section scrollable (#1923) (#1925)
* fix(web): make Done/Terminated dashboard section scrollable (#1923)

The collapsible Done/Terminated cards grid had no max-height or
overflow handling, so it grew unbounded and was clipped by the
ancestor's overflow:hidden once enough terminated sessions
accumulated. The regression came from the Warm Terminal design
refresh (#927) which added `flex: 1` to `.kanban-board-wrap`,
defeating the body-level `overflow-y: auto` that previously
allowed natural page scroll past the done section.

Mirror the existing `.kanban-column-body` pattern by giving the
cards grid its own internal scroll container and matching thin
scrollbar styling. Each scrolling region owns its own scroll;
the Warm Terminal flex layout is preserved.


* style(web): document done-bar__cards max-height magic number

Addresses greptile P2 review feedback on PR #1925. The 320px in
calc(100vh - 320px) is the assumed chrome above the cards
(dashboard header + subhead + body padding + done-bar toggle and
margins). Adding a comment makes the implicit ancestor-height
contract explicit, and points to the kanban-board rule that
uses the same viewport-anchored pattern.
2026-05-19 12:08:35 +05:30
Dhruv Sharma f3e45959e6
Add orchestrator-driven code review board (#1871)
* feat: add orchestrator-driven code review board

* feat: wire review findings back to workers

* feat(web): send review feedback to workers

* fix(core): mark stale review runs outdated

* fix: restore reviewer flow after main merge

* Fix review lock lint failure

* Guard concurrent review executions

---------

Co-authored-by: Madhav Kumar <lakshy1523@gmail.com>
2026-05-19 11:47:57 +05:30
whoisasx 67a030e7c0 fix: address notifier logging ci failures 2026-05-19 04:40:36 +05:30
whoisasx 509ece02b6 Merge remote-tracking branch 'origin/main' into feat/issue-1579
# Conflicts:
#	packages/cli/__tests__/commands/setup.test.ts
#	packages/cli/src/commands/setup.ts
#	packages/core/src/activity-events.ts
#	packages/core/src/plugin-registry.ts
#	packages/plugins/notifier-composio/src/index.ts
#	packages/plugins/notifier-discord/src/index.ts
#	packages/web/server/__tests__/mux-websocket.test.ts
#	packages/web/server/mux-websocket.ts
2026-05-19 04:24:12 +05:30
whoisasx 9223afe907 chore: add notifier logging screenshot 2026-05-19 04:10:26 +05:30
whoisasx 2f7e133179 Add notification delivery observability 2026-05-19 04:04:00 +05:30
Harshit Singh Bhandari 07c90996d2
fix(web): show Restore button for every exited session, including pr_merged (#1909)
* fix(web): show Restore button for every exited session, including pr_merged

The Restore button was hidden for sessions exited with `pr_merged` reason
(legacy status `cleanup`) on the dashboard kanban and absent altogether
from the session-detail "Terminal ended" panel. The core `isRestorable()`
helper already allowed restoring these sessions; the dashboard helpers
were out of sync, compensating for a non-existent core constraint.

- `isDashboardSessionRestorable` now gates on `NON_RESTORABLE_STATUSES`
  only, matching core's `isRestorable`. Merged-but-running sessions
  (runtime still alive) remain non-restorable.
- `DoneCard` no longer hides Restore for merged sessions.
- `SessionEndedSummary` now exposes a prominent `Restore session` button
  alongside `Open PR` / `Back to dashboard`, so users don't have to find
  the small icon button in the header.

Closes #1907

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

* fix(web): add :focus-visible styles to ended-summary action buttons

Address PR #1909 review feedback. The new Restore session button (and
existing Open PR / Back to dashboard pills) lacked a visible focus ring,
making keyboard navigation invisible. Apply the same accent outline used
elsewhere in the dashboard.

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-05-19 02:50:34 +05:30
Dhruv Sharma 6d48022c87
feat(cli): wire activity events into CLI commands and supervisor lifecycle (#1698)
* feat(cli): wire activity events into CLI commands and supervisor lifecycle (#1654)

Adds "cli" to ActivityEventSource and emits ~30 activity events across the CLI
surface so `ao events list --source cli` can answer RCA questions like:

- Did AO start cleanly? When? (cli.start_invoked / cli.start_failed)
- Was AO shut down gracefully or did it crash? (cli.shutdown_signal /
  cli.shutdown_completed / cli.shutdown_force_exit / cli.stale_running_pruned)
- Did ao spawn / ao update / ao stop / ao migrate-storage fail and why?
- Did the auto-restore prompt actually restore sessions?

Instrumented files:
- packages/core/src/activity-events.ts (cli source)
- packages/cli/src/lib/shutdown.ts (signal/completed/failed/force_exit/session_kill_failed)
- packages/cli/src/commands/start.ts (start_invoked/start_failed/restore_*/stop_*/daemon_*/last_stop_* /config_migrated)
- packages/cli/src/commands/spawn.ts (spawn_invoked/spawn_failed)
- packages/cli/src/commands/update.ts (update_invoked/update_failed)
- packages/cli/src/commands/setup.ts (setup_failed)
- packages/cli/src/commands/migrate-storage.ts (migration_completed/migration_failed)
- packages/cli/src/commands/project.ts (project_register_failed)
- packages/cli/src/lib/resolve-project.ts (project_resolve_failed/config_recovered/config_recovery_failed)
- packages/cli/src/lib/running-state.ts (lock_timeout/stale_running_pruned)
- packages/cli/src/lib/credential-resolver.ts (credential_load_failed)

All emits are sync, never wrapped in try/catch (recordActivityEvent never
throws), and put raw error text in data.errorMessage (not summary, which is
FTS-indexed and not credential-sanitized).

Tests:
- 16 new instrumentation tests across shutdown, migrate-storage, update,
  resolve-project, and start/stop action paths covering MUST emits.

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

* fix: correct CLI activity event semantics

* fix(cli): emit migrate-storage invocation event

* fix(cli): record last-stop write failures

* fix(linear): retry transient API failures

* fix(cli): stabilize update instrumentation test

* fix(cli): stub process probes in stop instrumentation tests

* chore(ci): retrigger checks

* Fix CLI failure event review issues

* fix: bound linear integration cleanup

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: whoisasx <adil.business4064@gmail.com>
Co-authored-by: Adil Shaikh <106678504+whoisasx@users.noreply.github.com>
2026-05-18 21:04:25 +05:30
Dhruv Sharma c1e43f61dc
feat(core): round out session-manager activity events (#1657) (#1697)
* feat(core): round out session-manager activity event instrumentation (#1657)

PR #1620 wired activity events for the worker-spawn happy path
(spawn_started/spawned/spawn_failed/killed). This extends the same
pattern across the rest of session-manager's failure surfaces so RCA
can answer questions today's logs can't:

- Did the orchestrator spawn fail? (orchestrator path had no AE
  envelope at all; now wrapped like worker spawn)
- Did the agent's startup prompt deliver, or did all 3 retries fail?
- Did `runtime.destroy` / `workspace.destroy` succeed during kill, or
  silently leak?
- Did `runtime_lost` reconciliation fire for this session?
- Did `ao send` actually deliver?

New event kinds (added to ActivityEventKind union):
  session.kill_started, session.prompt_delivery_failed, session.send_failed,
  session.restore_failed, session.restore_fallback, session.rollback_started,
  session.rollback_step_failed, session.workspace_hooks_failed,
  session.cleanup_error, session.orchestrator_conflict,
  runtime.lost_detected, runtime.lost_persist_failed, runtime.destroy_failed,
  workspace.destroy_failed, agent.opencode_purge_failed,
  tracker.issue_fetch_failed, tracker.generate_prompt_failed,
  metadata.corrupt_detected

Coverage (per #1657 spec):
- All 8 MUST emits implemented and have a regression test in
  session-manager-instrumentation.test.ts
- 13 SHOULD emits implemented (cleanup loop, orchestrator subpaths,
  kill silent catches, send/restore failure paths, etc.)
- 4 COULD emits implemented
- Out-of-scope per-list enrichment paths intentionally NOT instrumented

Invariants preserved (extends #1620's B1-B16):
- B1: state mutation BEFORE event emission (runtime_lost persist
  happens before the AE fires; verified by test)
- B2: never wrap recordActivityEvent in try/catch
- B11: prompt content excluded from prompt_delivery_failed data
  (verified by test that asserts the prompt string is absent)
- B16: failure-only — emit on final retry exhaustion only, not per
  attempt
- B25 (new): cleanup-stack rollbacks emit per-step via the onError
  callback hook, not in aggregate

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

* fix: address session activity event review

* fix: address session activity event review

* fix(core): avoid duplicate restore failure events

* chore(ci): retrigger checks

* Fix orchestrator activity event instrumentation

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: whoisasx <adil.business4064@gmail.com>
2026-05-18 19:53:55 +05:30
neversettle 73ffd4ab13
fix(cli): fall back to local config when global config is missing in project supervisor (#1809)
* chore(npm): suppress prebuild-install deprecation warning

The prebuild-install package is no longer maintained, but it's a transitive
dependency of better-sqlite3 (optional) and node-pty (optional). Both packages
continue to use it reliably for downloading prebuilt binaries across platforms.

Suppressing this warning avoids noise in npm install output while we await
upstream changes. See https://github.com/ComposioHQ/agent-orchestrator/issues/1752

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

* fix(cli): warn users when spawning in permissionless mode

When agentConfig.permissions is unset, the schema defaults to
"permissionless", which passes --dangerously-skip-permissions to Claude
Code. Claude then shows a one-time confirmation prompt inside the tmux
session; if dismissed, the session exits silently with no user-facing
error — making it very hard to debug.

Add a post-spawn warning in ao spawn output explaining the mode and
how to either accept the prompt or switch to interactive mode. Also
improve the config-instruction docs to describe each permissions value
and the silent-exit risk.

Fixes #1754

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

* fix(cli): direct users to dashboard terminal for permissionless prompt

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

* removed unwanted changes

* fix(cli): fall back to local config when global config is missing in project supervisor

There are two separate things reading config:
- Dashboard (Next.js web server): loadDashboardConfig() — tries global config, falls back to local.
- Project supervisor (CLI process): loadConfig(getGlobalConfigPath()) only — no fallback.

When a user runs ao start with a local agent-orchestrator.yaml that has never been
registered in ~/.agent-orchestrator/config.yaml, the supervisor silently returned
with 0 lifecycle workers, leaving sessions frozen in their last known state and
PR status never updating.

loadSupervisorConfig() mirrors the dashboard fallback: try global config first,
fall back to loadConfig() (local discovery) on ENOENT. ConfigNotFoundError (no
config anywhere) is handled by isMissingGlobalConfigError so the supervisor still
exits cleanly when AO has never been configured.

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

* fix(cli): address review feedback and fix failing supervisor tests

Review feedback from greptile:
- Remove unreachable ConfigNotFoundError branch from loadSupervisorConfig's
  catch — loadConfig(globalConfigPath) cannot throw it with a non-null path.
  The error still propagates from the fallback loadConfig() (no args) and is
  caught by isMissingConfigError at the outer scope.
- Rename isMissingGlobalConfigError → isMissingConfigError. After extending it
  to catch ConfigNotFoundError (the "no config anywhere" case), the old name
  was misleading.

Test fix (CI was failing):
- Add ConfigNotFoundError to the vi.mock factory so tests that exercise the
  fallback path compile.
- Add coverage for the new fallback paths:
  - ENOENT on global config → falls back to loadConfig() (local discovery)
  - Non-missing-config errors (e.g. invalid yaml) propagate up
  - No config anywhere → supervisor exits cleanly, no workers attached

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

* chore(cli): drop unused config-instruction module

The getConfigInstruction() helper and its config-help subcommand were the
module's only consumers, so remove both. The schema URL referenced via
CONFIG_SCHEMA_URL still lives in core and is reachable from the yaml's
$schema field for editor-side completion.

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

* chore(cli): tighten loadSupervisorConfig comment

Replace the rationale paragraph with a one-line description of what the
function does. The "why" lives in the PR description and commit history.

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

* fix(cli): scope loadSupervisorConfig ENOENT guard to the global config path

Review feedback from greptile: the ENOENT catch was broader than the matching
guard in isMissingConfigError. If the global config exists but references a
missing nested file, the supervisor would silently fall back to the local
config instead of surfacing the configuration error.

Mirror the path check from isMissingConfigError so only ENOENT for the global
config path itself triggers the fallback. Add a test covering the nested-file
case.

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

* chore: restore trailing newline in .npmrc

Review feedback from greptile. The trailing newline was accidentally dropped
by an earlier "removed unwanted changes" commit on this branch and is
unrelated to the supervisor fix.

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

* Revert "chore(cli): drop unused config-instruction module"

This reverts commit cb58e15d02.

* revert the unwanted comments

* Addressing comments to make the resolved path as a fallback for supervisor

* test(cli): assert local fallback configPath propagates distinct from global

Reviewer nit: the ENOENT-fallback test was returning makeConfig with the
default `/tmp/global-config.yaml` path, so the assertion on
ensureLifecycleWorker could pass even if the supervisor accidentally
propagated the global path. Use a distinct local path in the fallback
fixture so the regression coverage is honest.

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

---------

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-18 19:19:39 +05:30
Dhruv Sharma be9afb36a4
feat(core): record activity events for config, plugin-registry, and storage migration (#1696)
* feat(core): record activity events for config, plugin-registry, and storage migration

Closes #1658.

Surfaces failures that previously left no trace in `ao events list`:

- Config: project_resolve_failed (degraded project), project_malformed,
  project_invalid, migrated. Local-config errors carry the project's
  projectId so `ao events list --project <id>` finds them.
- Plugin registry: load_failed (built-in and external), validation_failed,
  specifier_failed. External plugin failures are now queryable instead of
  living only in stderr.
- Migration: blocked (active sessions), project_failed (per-project),
  rename_failed (.migrated rename), completed (one summary with totals),
  rollback_skipped (post-migration sessions present).
- Agent report: api.agent_report.transition_rejected and apply_failed for
  observability into rejected reports.

Migration emits at most one event per milestone (per project failure +
single completion summary) so event volume scales with errors, not input
size. Config events redact sensitive keys via the existing sanitizer.

Adds regression tests for all four MUST emits.

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

* fix(core): avoid duplicate config activity events

* test(core): cover blocked storage migration event

* test(core): satisfy lint in migration event mock

* fix(core): emit migration completed for no-op runs

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: whoisasx <adil.business4064@gmail.com>
2026-05-18 19:16:34 +05:30
Dhruv Sharma 73bed33c2e
feat(web): activity events for webhooks and mux WebSocket (#1656) (#1693)
* feat(web): activity events for webhooks and mux WebSocket (#1656)

Closes #1656 — adds the 10 activity events called out in the issue, covering
webhook ingress (4) and the mux WebSocket terminal server (6). Builds on the
ActivityEvent infrastructure landed in #1620.

Webhook events (api source):
- api.webhook_unverified (warn) — 401 signature verification failure
- api.webhook_rejected   (warn) — 413 payload exceeds maxBodyBytes
- api.webhook_received   (info|warn) — 202 success, with parse/lifecycle error counts
- api.webhook_failed     (error) — 500 outer catch / pipeline crash

Mux WS events (ui source — Node-side server only):
- ui.terminal_connected     — one per mux WS connection
- ui.terminal_disconnected  — one per close
- ui.terminal_heartbeat_lost (warn) — once on 3 missed pongs (was console-only)
- ui.terminal_pty_lost       (warn) — fires only when subscribers are still attached,
  distinguishing "PTY actually died" from "user closed browser"
- ui.terminal_protocol_error (warn) — invalid mux client message
- ui.session_broadcast_failed (warn) — emitted on the healthy→failing transition
  only; re-arms after a successful poll so a long outage yields one event, not 20/min

Invariants honored: no raw payloads or signatures in `data`, no client IPs in
`summary` (kept in `data` only), no per-keystroke / per-pong fan-out — only on
state transitions. `api.webhook_unverified` is the security-audit event; data
captures `slug` and `remoteAddr` but never the failed signature.

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

* fix(web): harden webhook and PTY activity events

* chore(ci): retrigger checks

* fix(web): record PTY loss across mux exit paths

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: whoisasx <adil.business4064@gmail.com>
2026-05-18 18:54:00 +05:30
Dhruv Sharma ff0c3b741d
feat(web): record activity events for API mutation routes (#1695)
* feat(web): record activity events for API mutation routes

Wire recordActivityEvent calls into all POST/PATCH/DELETE handlers under
packages/web/src/app/api/ so RCA can answer "did the user click X?" beyond
just success/failure status codes. Source: "api" (added in #1620).

Coverage:
- session mutations: spawn, kill, send, message, restore, remap (with
  failure variants for SessionNotRestorable/WorkspaceMissing/etc.)
- orchestrator + PR: orchestrator spawn, PR merge (with rejected/failed)
- project + config: add, update, remove, repair, reload
- issue + verify + labels: issue create, verify, label setup

Sanitization rules preserved:
- never include request/response bodies
- *_message_* events include messageLength only, never the message text
- project_updated records changedKeys, never values (config can carry tokens)

Tests: regression coverage for the 12 MUST emits across two new test
files, plus negative-path sanitization assertions.

Closes #1655

* fix(web): refine api activity failure events

* fix(web): align project activity event tests

* fix(web): avoid orchestrator spawn failure double emit

---------

Co-authored-by: whoisasx <adil.business4064@gmail.com>
2026-05-18 17:28:10 +05:30
Dhruv Sharma fcedb25031
feat(core): activity events for recovery, metadata corruption, agent-report (#1692)
* feat(core): activity events for recovery, metadata corruption, agent-report

Wires activity events into three forensic-critical paths so RCA can
reconstruct what happened after the fact:

1. recovery subsystem
   - recovery.session_failed (MUST) per failed session in runRecovery
   - recovery.action_failed (SHOULD) on recoverSession outer catch
   Adds "recovery" to ActivityEventSource so `ao events list --source
   recovery` reconstructs an `ao recover` invocation timeline.

2. metadata corruption
   - metadata.corrupt_detected (MUST) when mutateMetadata renames a
     corrupt session-metadata file to .corrupt-{ts}. Includes
     data.renamedTo and a 200-char data.contentSample (B11) for forensic
     recovery. Previously only console.warn — silent overwrites had no
     queryable signal.

3. agent-report apply path
   - api.agent_report.transition_rejected (SHOULD)
   - api.agent_report.session_not_found (COULD)

Per B22, recovery events fire per session, not per probe step. Per B11,
metadata.corrupt_detected truncates contentSample to 200 chars (full
file would exceed the 16KB sanitizer cap).

Closes #1660

* fix(core): align forensic activity event metadata

* fix(core): cover single-session recovery failures

* fix(core): improve corrupt metadata event attribution

* fix(cli): expose activity event source filter

---------

Co-authored-by: whoisasx <adil.business4064@gmail.com>
2026-05-18 17:27:36 +05:30
Dhruv Sharma befd910eeb
feat: emit plugin-internal activity events for distinct failure shapes (#1699)
* feat: emit plugin-internal activity events for distinct failure shapes

Plugins (scm/tracker/workspace/notifier) call recordActivityEvent directly
to surface failure shapes the lifecycle layer can't distinguish from
generic "plugin call failed":

- scm-github: scm.gh_unavailable (gh CLI missing, deduped per-process),
  scm.batch_enrich_pr_failed (one PR fails inside an OK batch),
  scm.ci_summary_failclosed (getCIChecks threw, fell closed to "failing")
- scm-gitlab: scm.ci_summary_failclosed, scm.review_fetch_failed
- workspace-worktree: workspace.post_create_failed (which command failed),
  workspace.branch_collision (concurrent session on same branch),
  workspace.destroy_fell_back (rmSync after git failure → stale metadata)
- workspace-clone: workspace.branch_collision, workspace.corrupt_clone_skipped
- tracker-linear: tracker.dep_missing (Composio SDK not installed,
  deduped per-process), tracker.api_timeout (30s abort)
- notifier-openclaw: notifier.auth_failed (401/403 distinct from 5xx),
  notifier.unreachable (ECONNREFUSED)
- notifier-discord: notifier.rate_limited (429 budget exhausted)
- notifier-composio: notifier.dep_missing (deduped per-process)

Adds "tracker", "workspace", "notifier" to ActivityEventSource and the
13 new kinds to ActivityEventKind. Includes regression tests for the 5
MUST emits.

Closes #1659

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

* fix: refine plugin activity event classification

* fix(openclaw): retry transient unreachable errors before logging

* Deduplicate corrupt clone activity events

* Deduplicate poll-path activity events

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: whoisasx <adil.business4064@gmail.com>
2026-05-18 17:25:23 +05:30
i-trytoohard 33789445d2
fix: remove 3 orphaned submodule refs leaked by PR #1819 (#1913)
PR #1819 accidentally introduced ao-pr1483, hermes-agent, and libkrunfw
as submodule entries (mode 160000) with no .gitmodules. These are
orphaned refs that serve no purpose and should not be on main.

Co-authored-by: AO Bot <ao-bot@composio.dev>
2026-05-18 04:43:09 +05:30
i-trytoohard d5d0f077ad
fix(cli): rebuild better-sqlite3 on install + quieter ABI-mismatch warning (closes #1822) (#1824)
* fix(cli): rebuild better-sqlite3 when binding is missing

* fix(cli): support Windows postinstall rebuild shims

---------

Co-authored-by: i-trytoohard <193449657+i-trytoohard@users.noreply.github.com>
2026-05-18 04:02:26 +05:30
i-trytoohard 87c6a3d9f4
fix(web): hide unenriched PR diff stats (#1912)
Co-authored-by: i-trytoohard <193449657+i-trytoohard@users.noreply.github.com>
2026-05-18 03:48:28 +05:30
whoisasx 2ce27ebdc4 Merge remote-tracking branch 'origin/main' into feat/issue-1579
# Conflicts:
#	packages/web/src/components/Dashboard.tsx
2026-05-18 01:43:02 +05:30
Priyanshu Choudhary eac581768d
fix: recover native session restore fallback (#1797)
* fix: recover native session restore fallback

Persist native agent restore metadata from dead runtimes and fall back to a fresh launch when native restore context is missing, so stuck sessions do not permanently block startup.

* fix: avoid redundant dead-session metadata discovery

* test: harden Linear list issue polling
2026-05-18 01:37:39 +05:30
Pritom Mazumdar 406b26e837
feat(web): sidebar and dashboard header UI/UX polish (#1846)
* perf(web): eliminate sidebar re-renders on every SSE tick

Resolves #1844

The SSE hook delivers a new sessions array reference every 5 seconds
even when content is identical, causing sessionsByProject to recompute
and all session rows to re-render on every tick.

Three fixes in ProjectSidebar.tsx:
* sessionsKey + sessionsRef: replace unstable array reference in memo
  deps with a content-derived string; memo only fires on real changes
* SessionRow memoized component: rows skip re-renders when props are
  unchanged; navigate and startRename stabilised with useCallback
* SessionDot memoized: status indicator skips re-renders when level
  prop is unchanged

A quiet SSE tick now touches zero React components in the sidebar.

* fix(web): add displayName, displayNameUserSet, branch to sessionsKey hash

Without these fields, a session rename delivered via SSE did not
trigger sessionsByProject to recompute. The stale session object
held the old displayName, and once the optimistic pendingRename
was cleared the sidebar silently reverted to the pre-rename title.

Addresses review feedback on PR #1846.

* feat(web): sidebar and dashboard header UI/UX polish

Removes state text labels from sidebar session rows so the colored dot
is the sole status indicator, matching the intended design. Fixes the
sidebar compact header height to align with the 48px main header.
Adds session count summary pills to the dashboard project header.
Converts CopyDebugBundleButton to an icon-only compact form so the
actions row stays vertically centered. Fixes the project page wrapper
missing flex-1 which caused a right-side viewport gap in the horizontal
shell layout.

* fix(web): resolve lint errors from UI/UX polish

Remove LEVEL_LABELS, _isLoading prefix for unused loading var, and dead
title variable from ProjectSidebar. Remove unused isDashboardSessionStatus
and isActivityStateValue from the project session page. Remove
react-hooks/exhaustive-deps eslint-disable comments for a rule not in the
ESLint config. Stabilize startRename via pendingRenamesRef so the callback
does not recreate on every rename state change, preventing unnecessary
SessionRow re-renders. Remove non-null assertion in Dashboard.tsx
handleToggleSidebar with a null guard.

* fix(web): replace native Node 25 localStorage stub with full in-memory mock

Node.js 25 exposes a native localStorage via --localstorage-file that lacks
.clear() and .key(), causing all UpdateBanner tests to throw TypeError.
Replace the global with a complete in-memory implementation so test suites
work across all Node versions.

* fix(web): seed sidebar with all sessions on hard refresh and eliminate per-project layout re-render

- Hoist sidebar layout from projects/[projectId]/layout.tsx to projects/layout.tsx so it renders
  once for the entire /projects/* subtree and never re-mounts when switching between projects
- Pass getDashboardPageData("all") so initial sessions cover every project, not just the primary
- Extract ProjectLayoutClient from the old client layout for clean server/client split
- Simplify per-project empty state to "No active sessions" only, removing the second hint line
- Restore accidentally deleted Dashboard empty-state test for zero-projects install
- Fix Reflect.deleteProperty lint error in localStorage mock; drop unused AttentionLevel import
  and dead effectiveDisplayName/pending variables from ProjectSidebar editing block

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

* fix(web): address PR review — orchestrators prop, sessionsKey, badge count, mobile overlay, tests

* fix(web): remove duplicate skeleton sidebar from project loading state

* fix(web): restore orchestrator button and eliminate Session unavailable flash

Re-add the orchestrator icon + menu item that were removed during the merge
conflict resolution. Convert the icon from a <Link> to an <a> that calls
navigate() with the full session object so ProjectSessionPage gets an
instant sessionStorage cache hit instead of starting with session=null
and briefly showing the "Session unavailable" error card.

* fix(web): eliminate Session unavailable flash on orchestrator navigation

React Strict Mode aborts the first fetchSession() during its unmount/remount
cycle. The aborted finally reset fetchingSessionRef but set loading=false,
briefly showing the error card before the retry completed. Fix: keep loading=true
on abort (no session yet), and immediately retry via fetchSession() once the
ref is clear. mountedRef guards the retry so it only fires on Strict Mode
remounts — not on genuine navigation-away unmounts, which would leak requests.

* fix(web): fix working pill count and layout of error states in project session page

- Dashboard topbar "working" pill now counts only actively working sessions,
  not working + pending (which inflated the number incorrectly)
- Error/loading/missing states in ProjectSessionPage wrapped in
  dashboard-main--desktop so they fill the flex shell beside the sidebar
  instead of shrinking to content width

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 23:45:35 +05:30
whoisasx ced430a291 Fix notifier bot follow-up comments 2026-05-17 22:18:49 +05:30
whoisasx 70245c3a81 Address notifier PR review comments 2026-05-17 22:06:24 +05:30
Harshit Singh Bhandari 94981dc0fd
feat(web,core): "Launch Orchestrator (clean context)" button (#1904)
* feat(web,core): "Launch Orchestrator (clean context)" button

Adds a dashboard action that replaces the project's canonical
orchestrator with a fresh one — killing any existing orchestrator,
deleting its metadata, and spawning a new session with no carryover.

Why: users had no way to start an orchestrator with a clean slate from
the dashboard. Previous orchestrator context (conversation history,
stale state) silently carried over via the existing "Open Orchestrator"
flow, which only worked for first-time spawn anyway.

- core: new SessionManager.relaunchOrchestrator(config) that kills +
  deletes existing metadata then calls spawnOrchestrator. Ignores
  project.orchestratorSessionStrategy — replacement is the whole point.
  Coalesces concurrent calls via a dedicated relaunchOrchestratorPromises
  map (separate from ensureOrchestratorPromises since the semantics
  differ — a relaunch behind an ensure must not return the existing
  session).
- web: POST /api/orchestrators accepts { clean: true } to route to the
  new method. OrchestratorSelector renders a "Launch Orchestrator
  (clean context)" button that uses window.confirm() before discarding
  an existing orchestrator; no confirm when none exists.

Closes #1900, closes #1080.


* fix(core,web): address PR #1904 review

- core: cross-map race between ensureOrchestrator and relaunchOrchestrator.
  Each now awaits the other's in-flight promise (keyed by sessionId) before
  proceeding. Prevents (a) relaunch skipping the kill while ensure's
  spawnOrchestrator is mid-reservation, and (b) ensure returning a session
  that relaunch is about to kill. Adds two race regression tests.

- web: align handleSpawnNew with handleRelaunchClean via the void expression
  form; add "Launching..." in-progress label to the clean-context button and
  a test that asserts it renders during POST.


* refactor(web): rip out Orchestrator Selector page; relocate clean-launch action

There is only ever one orchestrator per project, so the /orchestrators
selector page is meaningless. Delete it along with its component, tests,
and the unused mapSessionsToOrchestrators util. Drop GET /api/orchestrators
(only consumer was the deleted page). Remove /orchestrators from project
revalidate lists.

The "Launch Orchestrator (clean context)" action that previously lived on
the deleted page now appears in two places:

- Dashboard header: a "Relaunch (clean)" button renders alongside the
  Orchestrator link whenever a project orchestrator exists. Uses
  window.confirm before discarding state.
- Orchestrator session page: a "Relaunch (clean)" button in the
  SessionDetailHeader for live orchestrator sessions, calling
  POST /api/orchestrators with clean:true and reloading the session view.

* refactor(web): remove Relaunch (clean) action from the Dashboard

Keep the clean-launch action only on the orchestrator session page —
that's where the user has the context to decide on a destructive
restart. The Dashboard header just links to the orchestrator (or shows
the existing Spawn Orchestrator button when none exists).

* fix(web): surface relaunch failures with an inline error banner

After confirm + POST /api/orchestrators with clean:true, the previous
implementation only logged failures to console.error — leaving the user
on a stale page with no signal that the destructive action partially
executed. relaunchOrchestrator kills before respawning, so a failed
respawn means the server has no orchestrator while the client still
renders the old session view.

Add local relaunchError state, set it on catch (parsed from the JSON
error response when available), and render a dismissible error banner
above the terminal area. The banner explicitly warns the user that the
previous orchestrator may already be terminated and points them at the
project dashboard to retry.

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

* fix(web,core): address PR #1904 review from @i-trytoohard

- web: navigate to the new orchestrator's session path (from POST
  response) instead of window.location.reload(). Orchestrator session
  IDs are fixed per project so the path is the same in practice, but
  reading from the response is the right contract and a hard nav forces
  the terminal WebSocket to reconnect cleanly against the new tmux.

- web: remove the `!terminalEnded` gate on the Relaunch (clean) button
  in SessionDetailHeader. Terminated orchestrators are exactly when the
  user wants to relaunch — hiding the button there was wrong.

- core: log a warning instead of silently swallowing when an in-flight
  cross-map promise (ensure waiting on relaunch, or relaunch waiting on
  ensure) rejects before its caller proceeds. The catch-and-continue
  semantics are correct (the caller will re-check state anyway) but
  invisible failures were a debugging hazard.

Adds a regression test that the button stays visible on terminated
orchestrator sessions and that successful relaunch navigates via
window.location.href.
2026-05-17 21:16:25 +05:30
Harshit Singh Bhandari ce6b47adf8
fix(core): keep actionable activity sticky (#1902)
`waiting_input`/`blocked` no longer decay on wallclock; clears only on process death or newer entry.

Close #1894.
Reference #1899.
2026-05-17 20:03:56 +05:30
Harshit Singh Bhandari cb7b83d223
fix(agent-claude-code): add AO-JSONL safety net cascade (closes #1897) (#1903)
* fix(agent-claude-code): add AO-JSONL safety net cascade

claude-code now has the same AO-JSONL fallback cascade as codex; protects against ~/.claude/projects slug drift (cf. #1611, commit 582c5373).

Closes #1897. References #1899.

* fix(agent-claude-code): let stale native activity use AO fallback

When Claude's latest native JSONL entry predates the AO session, fall through to the AO activity JSONL cascade before preserving the prior idle default. This keeps terminal-derived waiting_input from being hidden by an old native file.

Addresses review on #1903. References #1899 and closes #1897.
2026-05-17 20:03:07 +05:30
Adil Shaikh 667d1dedfc
fix(core): sm.list() no longer writes terminated state to disk (#1737)
* fix(core): sm.list() no longer writes terminated state to disk (#1735)

sm.list() was bypassing the lifecycle manager's probe decision matrix by
persisting terminated state immediately on a single isAlive() failure.
The dashboard's 3s poll via /api/sessions/patches called sm.list() ~10x
more often than the lifecycle manager, so a transient runtime failure
would permanently kill the session before the lifecycle manager could
evaluate all three probes (runtime, process, activity).

Changes:
- sm.list() now persists "detecting" instead of "terminated" when it
  detects a dead runtime, so the lifecycle manager's resolveProbeDecision
  pipeline remains the single authority on terminal decisions.
- /api/sessions/patches now calls listCached() instead of list(),
  preventing the dashboard's 3s poll from probing runtimes directly.
  The cache TTL (35s) aligns with the lifecycle manager's 30s poll.
- Updated CLAUDE.md invariants to reflect the new behavior.

* fix(core): skip re-persisting detecting state on subsequent list() calls

Check the on-disk lifecycle state (raw metadata) instead of the
in-memory state when deciding whether to persist. Enrichment already
sets detecting in-memory, so the previous guard always skipped the
persist block. Using the on-disk state ensures:
- First detection: persists detecting + lastTransitionAt
- Subsequent calls: skips re-write, preserving the original timestamp
2026-05-16 19:05:49 +05:30
whoisasx 7a511463bf Merge remote-tracking branch 'origin/main' into feat/issue-1579
# Conflicts:
#	packages/core/src/lifecycle-manager.ts
2026-05-15 21:57:54 +05:30
i-trytoohard e6ad078d7a
fix(web): kill RSC prefetch storm + dedupe in-flight fetches + retry on transient timeout (closes #1855) (#1856)
* fix: reduce session page fetch starvation

* fix: address fetch dedupe review feedback

* fix: preserve body-read timeouts in client fetch

---------

Co-authored-by: i-trytoohard <193449657+i-trytoohard@users.noreply.github.com>
2026-05-15 12:00:56 +05:30
i-trytoohard 3fb23cfb48
feat(lifecycle): inject failed-job/step + log tail into CI-failure agent messages (closes #1807) (#1810)
* feat(core): add CI failure summary SCM contract

Add an optional SCM getCIFailureSummary hook and shared CIFailureSummary shape so providers can return failed jobs, failed steps, run URLs, and bounded log tails without changing existing SCM implementations.

* feat(scm-github): summarize failed CI logs

Use failed GitHub check URLs to locate Actions run/job IDs, fetch gh run view --log-failed output, parse the step column from the gh log format, and cap each failed-job log tail at 120 lines. Return null when no failed run logs can be fetched so lifecycle can fall back cleanly.

* feat(lifecycle): send actionable CI failure details

Prefer SCM CI failure summaries when composing ci-failed agent messages, including failed job/step, run URL, and log tail. Fall back to check names/statuses for SCM plugins without getCIFailureSummary, and remove the generic default ci-failed text so default handling relies on the lifecycle-composed payload.

State invariants preserved: this only changes ci-failed reaction payload composition and dispatch hashing. It does not change lifecycle status decisions, state-machine transitions, ci-failed persistence, retry/escalation thresholds, or stable-passing tracker reset semantics.

* fix(lifecycle): harden CI failure summaries

Address review feedback: escape log-tail lines that could close markdown fences, report the last failed-step column from gh failed logs, and pass already-known failed checks into getCIFailureSummary to avoid duplicate CI check fetches.

State invariants preserved: lifecycle transition decisions, retry/escalation budgets, persistent ci-failed tracker behavior, and stable CI passing reset semantics remain unchanged.

* refactor(lifecycle): centralize CI failure check lookup

Deduplicate CI failure check lookup, filtering, and fingerprint generation across transition-time enrichment and follow-up CI-failure dispatch while preserving the existing fetch/no-fetch split.

State invariants preserved: lifecycle transition decisions, reaction retry/escalation behavior, ci-failed tracker persistence, and stable-passing reset semantics are unchanged.

* fix(scm-github): fetch in-progress failed job logs

* fix(lifecycle): label CI failure URLs explicitly

---------

Co-authored-by: i-trytoohard <193449657+i-trytoohard@users.noreply.github.com>
2026-05-15 05:48:45 +05:30
i-trytoohard d44e708525
fix(core): clear terminatedAt/completedAt when restoring a session out of terminal state (closes #1831) (#1834)
* fix(core): clear terminal markers on non-terminal lifecycle transitions

Clear stale completedAt/terminatedAt whenever lifecycle helpers settle on a non-terminal session state, including restore-to-working paths.

Preserved invariants: terminal-state idempotence remains intact because done/terminated states keep their terminal markers; session state transitions still flow through existing code that updates lastTransitionAt before marker hygiene runs.

* fix(core): share terminal marker hygiene

Move non-terminal marker clearing into lifecycle-state so restore and lifecycle-transition paths use one terminal-state source of truth.

Preserved invariants: terminal-state idempotence still keeps done/terminated markers; existing transition code continues to update lastTransitionAt before marker hygiene runs.

---------

Co-authored-by: i-trytoohard <193449657+i-trytoohard@users.noreply.github.com>
2026-05-15 04:46:35 +05:30
i-trytoohard 6fb18cb47e
fix(web): authoritative session.state for terminated UI rendering (closes #1832) (#1833)
* fix(web): honor lifecycle state for terminal session UI

* fix(web): address terminal state review feedback

* chore: retrigger integration ci

* fix(web): trim terminal state change scope

* fix(web): preserve legacy terminated helper behavior

---------

Co-authored-by: i-trytoohard <193449657+i-trytoohard@users.noreply.github.com>
2026-05-15 04:30:44 +05:30
i-trytoohard 7d324b537d
fix(cli): reap daemon children on stop+SIGINT, sweep orphans on start (closes #1848) (#1849)
* feat(core): add managed daemon child registry

* fix(cli): reap daemon children on stop and shutdown

* test(cli): cover daemon child reaping

* chore: version packages for 0.9.0

* fix(core): avoid regex in orphan process scan

* test(web): expect managed child spawn helper

* fix(core): let daemon shutdown own signal exit

* fix(web): mark shutdown ownership before spawning children

* fix(core): wait for managed children before fallback exit

* docs: document daemon process management architecture

* fix(core): scope daemon child sweeps by owner pid

* docs: link process architecture to PR changes

* docs: clarify legacy messaging watcher status

* chore(core): remove unused messaging watcher orphan pattern

* chore: remove version bump from orphan reaping PR

* docs: remove process management design draft

---------

Co-authored-by: i-trytoohard <193449657+i-trytoohard@users.noreply.github.com>
2026-05-15 03:38:09 +05:30
i-trytoohard 89ad185195
fix(agent-plugins,lifecycle): distinguish indeterminate probe from "not found" + bump ps timeout (closes #1838) (#1839)
* fix(agent-plugins): preserve sessions on indeterminate probes

* fix(core): gate recovery activity on successful process probe

* refactor(core): centralize process probe indeterminate handling

---------

Co-authored-by: i-trytoohard <193449657+i-trytoohard@users.noreply.github.com>
2026-05-14 21:50:39 +05:30
whoisasx b9d81184bf Fix notifier test fallout after merge 2026-05-14 15:03:36 +05:30
whoisasx 39b64e7bc4 Update notifier integration assertions after merge 2026-05-14 14:53:05 +05:30
whoisasx 2394cb7137 Merge origin/main into feat/issue-1579 2026-05-14 14:44:53 +05:30
whoisasx 78b6466fd7 Address remaining notifier bot reviews 2026-05-14 14:32:05 +05:30
whoisasx faf2d018ba Address notifier bot review comments 2026-05-14 13:38:39 +05:30
whoisasx 8e750abb06 Fix code scanning notifier alerts 2026-05-14 13:23:13 +05:30
Adil Shaikh 200bf4dfee
Potential fix for pull request finding 'CodeQL / Client-side cross-site scripting'
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-05-14 12:41:17 +05:30
whoisasx 84bae872fa Remove redundant dashboard notification actions 2026-05-14 11:57:41 +05:30
whoisasx 9d802b5555 Update AO notifier app icon 2026-05-14 11:42:00 +05:30
whoisasx bd1fa42c61 Improve desktop notification UX 2026-05-14 03:47:07 +05:30
whoisasx 66b89e2acf Improve notifier setup and rich notifications 2026-05-14 03:03:18 +05:30
whoisasx c665da9183 chore: use ao-agent as composio default user 2026-05-13 21:49:01 +05:30
whoisasx 2401abf6e9 Make notifier payloads semantic v3 2026-05-13 21:24:11 +05:30
whoisasx 360c302f0a feat: add dashboard notifier 2026-05-13 20:57:07 +05:30
whoisasx ed176861b3 Fix notifier setup flows 2026-05-13 17:35:39 +05:30