Real Codex sessions emit records like
`{"type":"event_msg","payload":{"type":"error",...}}` and
`{"type":"event_msg","payload":{"type":"approval_request",...}}`.
readLastJsonlEntry only exposed the top-level `type`, so the codex
plugin's activity switch matched `event_msg` and decayed to ready/idle,
never surfacing `blocked` or `waiting_input`. The approval_request/error
branches were dead code for payload-wrapped sessions, which is the exact
format this PR series is migrating to.
- readLastJsonlEntry now returns payloadType alongside lastType.
- Codex getActivityState prefers payloadType when present and classifies
task_started/agent_reasoning as active, task_complete as ready, and
the approval/error variants as waiting_input/blocked.
- New tests cover the payload-wrapped approval_request, exec_approval_request,
error, task_started, and task_complete cases end-to-end.
- Core utils gains coverage for payloadType extraction and null fallbacks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Merge main to pick up @composio/ao-core → @aoagents/ao-core rename
and decomposer removal (#1104)
- Fix @composio/ao-core import in prompt-spawn.test.ts → @aoagents/ao-core
- Remove unreachable dead code in CLI spawn (empty-string guard after || undefined)
- Update format.ts JSDoc to document 8-item fallback chain including userPrompt
- Add clarifying comment on validation/sanitization separation in spawn route
- Integrate userPrompt display into redesigned SessionCard footer
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style(design): FINDING-001 — add prefers-reduced-motion support
All animations and transitions are disabled when the user's system
requests reduced motion, per DESIGN.md accessibility requirements.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style(design): FINDING-003 — remove concurrent breathe animations
Status pills had two animations: a breathe animation on the pill
and a dot-pulse on the child dot. DESIGN.md says "one animation per
element, one purpose" and "keep dot pulse, remove border heartbeat."
Removed all three breathe keyframes, kept dot-pulse only.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style(design): FINDING-004 — fix dashboard title weight and tracking
DESIGN.md specifies display headings at weight 680 and letter-spacing
-0.035em. The dashboard title was using 600 / -0.05em.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style(design): FINDING-005 — fix detail-card text to blue-tinted graphite
Detail cards overrode text-secondary and text-tertiary with neutral
grays (#9898a0, #5c5c66). DESIGN.md specifies blue-tinted graphite
palette (#a5afc4, #6f7c94) for dark mode text.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style(design): FINDING-008 — add text-wrap: balance on headings
Dashboard title and kanban column titles now use text-wrap: balance
for more even line breaks on narrow viewports.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style(design): FINDING-006/009 — fix section label semantics and spacing
Changed "Attention Board" from <h2> to <div role="heading"> since it's
styled as a 12px uppercase label, not a heading. Also fixed letter-spacing
from 0.16em to 0.06em per DESIGN.md UI/Labels spec.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style(design): FINDING-007 — contextual empty state messages
Empty kanban columns now show context-specific messages instead of
generic "No sessions" text. Each column's empty state reflects its
purpose: "No agents need your input" (Respond), "No code waiting
for review" (Review), etc.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(design): fresh design system — Warm Terminal
Complete redesign from Industrial Precision (blue-tinted) to Warm Terminal
(brown-tinted). Key changes:
- Warm charcoal surfaces (#121110, #1a1918, #222120) replace blue-gray
- Cream text (#f0ece8) replaces blue-white (#eef3ff)
- Warm periwinkle accent (#8b9cf7) replaces cool blue (#5B7EF8)
- Berkeley Mono for display headlines (mono cohesion)
- Added: Accessibility section (44px touch targets, WCAG AA, focus-visible)
- Added: Component anatomy (button states, card structure, input fields)
- Added: Light mode design rationale (warm parchment, not clinical white)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(design): swap Berkeley Mono for JetBrains Mono (free)
Berkeley Mono is a paid font ($75). JetBrains Mono is free, open source,
already loaded in the project, and the mono-for-headlines concept works
the same way with it.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(design): fix light mode contrast failures
Light mode text-tertiary #a8a29e failed WCAG AA at 2.5:1 on white.
Darkened to #736e6b (5.0:1). Light mode accent #6b73c4 was borderline
at 4.3:1, darkened to #5c64b5 (5.3:1). All pairs now pass AA.
Added verified contrast ratios for both modes to accessibility section.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: gitignore .gstack/ directory
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(design): add design audit report and screenshots
Design review audit report with before/after screenshots for all
dashboard pages (kanban, session detail, PRs) across desktop, tablet,
and mobile viewports in both light and dark mode.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(design): address PR review comments
- Fix mobile test expecting removed "No sessions" text. The merge zone
emptyMessage is now "Nothing cleared to land yet." (Bugbot comment #1)
- Remove no-op .dark .detail-card override that duplicated global dark
values after FINDING-005 fix aligned them (Bugbot comment #2)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: gitignore .gstack-report/ and remove from tracking
The .gstack-report/ directory contains local audit artifacts with
filesystem paths. Should not be tracked in the repository.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(design): align dashboard title CSS to new DESIGN.md spec
Dashboard title was using old Geist Sans values (weight 680, -0.035em).
New spec is JetBrains Mono, weight 500, letter-spacing -0.02em.
Added font-family: var(--font-mono) to match.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use native h2 element for Attention Board section heading
Replace ARIA role="heading" div with semantic h2 per ARIA first rule — native elements are preferred over ARIA roles for actual headings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: pre-landing review fixes — a11y, dead code, test coverage
- Add aria-controls + id to accordion button/body pair in AttentionZone
- Wrap empty-state messages in aria-live="polite" regions for AT announcements
- Remove dead message prop and isDefault from EmptyState (Skeleton.tsx)
- Add parameterized test covering all 6 zone-specific empty messages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: revert aria-live on empty states — causes false AT announcements
Codex review identified that role="status" aria-live on static empty-state
text causes burst announcements on page load (all empty columns fire) and
announces in collapsed mobile sections that aren't visible. Empty states are
static text, not dynamic transitions. The aria-controls fix is kept.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: bump version and changelog (v0.0.1.0)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: remove .gstack-report/ from .gitignore
* chore: remove VERSION and CHANGELOG (not used in this project)
* style(design): warm terminal color migration + inline style removal
Migrate all CSS tokens from cool blue-tinted graphite to warm
brown-tinted terminal aesthetic per DESIGN.md spec. Replace inline
style color mappings in ActivityDot, AttentionZone, Dashboard,
ProjectSidebar, and SessionCard with data-attribute CSS selectors.
Fix duplicate className bug on SessionCard done-title element.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: pre-landing review fixes — activity dot fallback + review stat color
Add base CSS fallback for activity-dot, activity-pill, and
activity-pill__text so null/unknown activity states render visibly
(gray) instead of invisible. Fix review stat card to use accent-orange
(matching kanban/sidebar/mobile review indicators) instead of cyan.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: checkpoint current design branch state
* design changes
* feat(web): redesign session detail page — compact PR card, identity strip, layout reorder
- Redesign SessionTopStrip with simplified breadcrumbs, action buttons (Message/Kill)
- Replace stacked PR card with compact inline layout: title row + blocker/CI chips + collapsible comments
- Move PR card above terminal for better information hierarchy
- Replace vertical IssuesList with inline buildBlockerChips helper
- Add ~200 lines of new CSS classes for compact PR card design system
- Add changedFiles field to DashboardPR type
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(web): design system tokens, sidebar redesign, and component primitives
- Align all color tokens (status, bg, border, text) across three HTML mockups
- Rewrite ProjectSidebar to match finalized.html: rotation chevron, session status text, border-bottom project separators, 224px width
- Add packages/web/DESIGN.md: agent-readable reference for tokens, typography, component patterns, anti-patterns
- Add Badge.tsx: generic badge/chip/pill primitive with status/outline/default variants
- Add Button.tsx: ghost/primary/danger button primitive
- Update CLAUDE.md to reference DESIGN.md as required pre-read for web UI work
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* style(design): FINDING-001 — card border-radius 0 → 6px to match mockup
* style(design): FINDING-002 — column border-radius 0 → 7px, border subtle to match mockup
* style(design): FINDING-003 — column header mono font, 500 weight, muted color to match mockup
* style(design): FINDING-004/005/006 — fix accent-blue/yellow/purple tokens to match mockup
* style(design): FINDING-007 — add --color-bg-card token (light #fff, dark #1c1b19)
* Refine dashboard design system and remove fixture flow
* Fix respond status colors in dashboard indicators
* Align tests with updated dashboard and metadata behavior
* fix(core): register notifier aliases consistently
* chore(web): drop uncovered showcase routes
* Add desktop PullRequestsPage coverage tests
* Remove generated coverage artifact
* Consolidate web design guidance into the root design system
* Fix working and ready status color tokens
* Fix sidebar collapse and inline kill confirmation
* fix(qa): ISSUE-001 - show all mobile filter chips
* Restore full title contrast in session cards
* Fix review feedback in dashboard state styling
* style: implement mobile responsive designs (feed, terminal-first, dense PRs)
Dashboard: replace accordion with urgency-sorted priority feed, horizontal scroll filter pills.
Session Detail: terminal-first layout with floating header, status pill, PR bottom sheet.
PRs: dense rows with CI dots, grouped sections, muted merged/closed rows.
Update tests to match new mobile layouts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix mobile terminal padding with PR sheet layout
* Polish mobile feed and session detail styling
* Align mobile dashboard layouts with gstack designs
* Fix mobile terminal actions and PR review labels
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
node-pty leaves libuv handles alive after kill() (PTY master fd, internal
Socket, timers). Without process.exit(0) the probe process idles until the
10s execSync timeout fires, causing a spurious node-gyp rebuild on every
install even when the prebuilt binary is perfectly compatible.
The require('node-pty') check only verifies the module loads, but the
actual posix_spawnp failure only surfaces when the helper binary is
executed via pty.spawn(). This means the probe could pass while the
terminal remains broken — exactly the scenario issue #987 describes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The postinstall script only fixed spawn-helper permissions but didn't handle
ABI mismatches when the prebuilt binary doesn't match the user's Node.js
version (common with nvm/fnm/volta). Now tries to load node-pty after the
chmod fix and falls back to npx node-gyp rebuild if it fails.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Orchestrator sessions are control-plane sessions that clutter the ls
output alongside worker sessions. Filter them out using the existing
`isOrchestratorSessionName()` helper. Add `--all` / `-a` flag to
include them when needed.
Closes#1088
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The test file imported from @composio/ao-core but main renamed packages
to @aoagents/ao-core. Also added Session type annotations to all find()
callbacks to satisfy strict typecheck.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
writeMetadata() used a hard-coded field whitelist that silently dropped
userPrompt. The in-memory Session.metadata also never included it, so
the spawn response always returned null. Additionally, prompts containing
newlines could inject arbitrary key=value pairs into the flat-file
metadata format.
Fixes:
- Add userPrompt to writeMetadata() whitelist and readMetadata() return
- Populate session.metadata with userPrompt during spawn
- Strip newlines from prompts in both web API and CLI (defense-in-depth)
- Harden serializeMetadata() to replace newlines in all values
- Add prompt length validation (4096 max) in CLI to match web API
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously, sessions with terminal statuses (merged, done, killed, etc.)
had their activity unconditionally set to "exited" without checking if
the agent process was actually still running. This caused the dashboard
to show incorrect activity state for sessions where the PR was merged
but the agent was still alive in tmux.
Now the runtime liveness check runs first for all sessions, and activity
is derived from the actual process state via getActivityState(). Terminal
status no longer forces activity to "exited".
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Two notifier alias tests in plugin-registry.test.ts still referenced
@composio/ao-plugin-notifier-slack after the @composio→@aoagents
rename in #983. The mock fallback hit the throw branch, the slack
plugin never got registered, and the assertions failed with
"undefined" / "called 0 times". CI on main has been red on these
two tests since #967 merged.
Other tests in the same file (webhook line 231, openclaw line 333)
already use @aoagents — these two were missed.
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
Fix shell injection vulnerability when combining systemPromptFile with
prompt. The prompt could contain shell metacharacters ($(), backticks)
that would be executed inside the double-quoted string.
Now uses the exact same pattern as OpenCode:
"$(cat 'file'; printf '\n\n'; printf %s 'prompt')"
The shellEscape wraps prompt in single quotes (no shell expansion),
and printf %s outputs it literally without interpretation.
Co-Authored-By: Claude <noreply@anthropic.com>
Use $(cat file) shell substitution instead of inlining file content
to avoid tmux truncation for large system prompt files (2000+ chars).
This matches the pattern used by Claude Code, Aider, and OpenCode.
- Replace readFileSync with $(cat) in getLaunchCommand
- Remove unused readFileSync import
- Update tests to verify shell substitution behavior
Co-Authored-By: Claude <noreply@anthropic.com>
1. Add symlink check for .cursor directory in extractCursorSummary
to match getCursorSessionMtime behavior (prevents path traversal)
2. Add vitest alias for @aoagents/ao-plugin-agent-cursor in CLI tests
(fixes missing module resolution in tests)
3. Add lstatSync check before readFileSync in getLaunchCommand
to reject symlinked systemPromptFile paths (security hardening)
4. Add test coverage for symlink rejection behavior
Co-Authored-By: Claude <noreply@anthropic.com>
Fixes broken lockfile caused by merge conflict resolution.
The lockfile had duplicate keys for proxy-from-env@2.1.0 in the
packages section, which violated YAML spec and broke pnpm.
Co-Authored-By: Claude <noreply@anthropic.com>
- Resolve @composio → @aoagents package renaming conflicts
- Add cursor agent to BUILTIN_PLUGINS in plugin-registry.ts
- Add cursor agent to AGENT_PLUGINS in detect-agent.ts
- Add cursor agent import and registration in plugins.ts
- Add cursor agent dependency and import in web services.ts
- Update cursor plugin package naming to @aoagents/ao-plugin-agent-cursor
- Add cursor agent to changeset linked group
- Fix test imports to use new @aoagents package naming
Co-Authored-By: Claude <noreply@anthropic.com>
Fixes all issues identified in PR review from illegalcall:
1. 🔴 detect() false positives - Now checks for multiple Cursor-specific
markers: "Cursor Agent" text OR (--approve-mcps AND --sandbox flags).
Provides redundancy if Cursor changes one indicator.
2. 🔴 systemPromptFile/systemPrompt ignored - Properly reads file content
synchronously using readFileSync and prepends to prompt. Clean approach
without shell command substitution. Gracefully handles missing files.
3. 🟡 Process regex too generic - Fixed regex from /\\.?/ to /\.?/ for
optional dot prefix. Now correctly matches "agent" or ".agent" binaries.
4. 🟡 Idle check before waiting_input - Reordered detectActivity checks so
waiting_input patterns (permission prompts) are tested BEFORE idle prompt
detection. Fixes false negatives when prompts end with input cursor.
5. 🟡 Symlink/path traversal protection - Added lstat() checks in
extractCursorSummary and getCursorSessionMtime to reject symlinks and
verify paths stay under workspacePath.
6. 🟡 hasRecentCommits false actives - Added comment acknowledging the
limitation (same pattern as Aider plugin). Better than missing activity.
7. 🟡 Missing test coverage - Added 11 new tests:
- 6 tests for detect() covering text match, flag fallback, edge cases
- 5 tests for systemPromptFile/systemPrompt handling including errors
Total: 62/62 tests passing
Co-Authored-By: Claude <noreply@anthropic.com>
Bounds the override to axios 1.x to prevent pulling in a hypothetical
axios v2 that could break @hey-api/client-axios peer dependency or
introduce supply chain risk.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds pnpm.overrides to force axios >=1.15.0 across all transitive
dependencies. This resolves the critical SSRF vulnerability
(GHSA-3p68-rc4w-qgx5) where axios <1.15.0 had a NO_PROXY hostname
normalization bypass.
The vulnerable axios@1.13.5 was pulled in transitively through
composio-core@0.5.39 > axios and composio-core > @hey-api/client-axios
> axios.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Activity Detection (2 related issues):
- Fix getCursorSessionMtime to stat .cursor/chat.md file instead of directory
- Directory mtime only updates on entry changes, not file modifications
- Now checks chat.md file first (tracks actual writes), falls back to directory
- Prevents directory mtime from blocking JSONL fallback in getActivityState
- Allows tier 4 (getActivityFallbackState) to run when needed
Prompt Safety:
- Add -- separator before positional prompt argument in getLaunchCommand
- Prevents prompts starting with - from being misinterpreted as flags
- Matches pattern used in Codex agent plugin
- Update test expectations to include -- separator
Process Detection:
- Update comment to accurately describe "agent" binary matching
- Removed misleading reference to "cursor and .cursor" process names
Plugin Detection:
- Improve detect() to check --version output for Cursor-specific text
- Reduces false positive risk from generic "agent" command name
- Validates output contains "cursor" or "agent" keywords
All tests passing (51/51).
Fixes issues identified in PR #637 review.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add @composio/ao-plugin-agent-cursor to linked version group in .changeset/config.json
- Update README.md plugin architecture table to include cursor
- Update config.md reference to include cursor in agent options
This ensures the cursor plugin versions in lockstep with other built-in plugins
and is properly documented for users.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The --trust flag only works in headless mode (with --print), so it doesn't
prevent the workspace trust prompt in interactive mode. Changed to --sandbox
disabled which skips workspace trust prompts entirely.
This fixes the issue where Cursor agent would block on startup waiting for
user to approve the workspace trust prompt.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>