Commit Graph

149 Commits

Author SHA1 Message Date
Dhruv Sharma 87fb598d87
Merge pull request #1126 from gautamtayal1/fix/spawn-ao-for-opencode
fix (opencode): use local AGENTS.md for OpenCode orchestrator instructions
2026-04-16 18:21:12 +05:30
Adil Shaikh ba77929c93
Merge pull request #1158 from ComposioHQ/feat/issue-1154
fix(cli): stop writing broken yaml when `ao start` runs in a new folder
2026-04-15 15:10:10 +05:30
Dhruv Sharma 9691e6e6c5
Merge pull request #1215 from illegalcall/fix/codex-session-tracking-1099
Fix Codex session activity parsing for payload JSONL
2026-04-15 01:50:22 +05:30
Dhruv Sharma 49954403ef docs(codex): clarify token-accounting precedence in session parser
Token sources in streamCodexSessionData are precedence-ordered via `continue`.
`total_token_usage` is a cumulative snapshot (overwrite) while the others are
per-turn deltas (accumulate) — document this so a future reader doesn't "fix"
the asymmetry and break cumulative totals.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 01:45:16 +05:30
adil 2cf9128101 refactor: clean up redundant requireRepo calls and nullish coalescing
- Use ?? instead of || for ownerRepo fallback (semantically correct for
  null-to-undefined conversion)
- Extract requireRepo() result into a local variable in tracker-gitlab's
  updateIssue and issueUrl to avoid redundant validation calls

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 22:13:02 +05:30
adil 17ea240f68 fix(cli): stop writing broken yaml when `ao start` runs in a new folder
- Make `repo` field optional in ProjectConfig and Zod schema so projects
  without a detected GitHub remote can still load and run
- Remove placeholder `repo: "owner/repo"` from autoCreateConfig() and
  addProjectToConfig() — omit the field entirely when no remote is found
- Always use actual workingDir for `path` instead of unreliable `~/<projectId>`
  fallback for non-git directories
- Add null guards for `project.repo` across SCM plugins, tracker plugins,
  lifecycle manager, webhooks, and prompt builders to prevent crashes when
  repo is not configured

Closes #1154

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 22:13:01 +05:30
Dhruv Sharma ab1e4fb069
Merge pull request #1180 from yyovil/add/type-resolution
Add node type resolution for non-web packages
2026-04-13 19:56:36 +05:30
Dhruv Sharma 64b6a2ec72 fix(codex): address cursor bugbot comments on readJsonlPrefixLines
Use a single StringDecoder across reads so multi-byte UTF-8 sequences
that straddle the 8KB chunk boundary buffer correctly instead of
producing U+FFFD replacement characters that break JSON.parse.

Also fix the test mock: makeFakeFileHandle now advances an internal
cursor and returns bytesRead: 0 at EOF. The prior mock copied from
offset 0 every call, which would infinite-loop readJsonlPrefixLines
for any line larger than the 8192-byte chunk size.

Add a regression test using 3,000 CJK characters (9,000 bytes of
payload) to exercise the chunk boundary path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 19:23:29 +05:30
Dhruv Sharma df9f3c8b5d fix(codex): classify activity by payload.type for wrapped event_msg
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>
2026-04-13 18:53:50 +05:30
yyovil 9bed49d453 fix: scope node types to node packages 2026-04-13 18:25:21 +05:30
Dhruv Sharma 4ab844e47f Fix Codex session activity parsing 2026-04-13 16:45:57 +05:30
i-trytoohard 36a64c98b7
chore: bump all package versions to 0.2.5 (#1190)
* chore: release 0.2.5

Realign main with npm registry after off-branch publish of 0.2.3/0.2.4.
Bump all 21 linked packages to 0.2.5 and cherry-pick the startup-grace-period
fix for #989 (was in 5e4244a8 but never merged to main).

Also sync non-linked plugin versions (notifier-discord, notifier-openclaw,
scm-gitlab, tracker-gitlab) to their current npm versions.

* Revert "chore: release 0.2.5"

This reverts commit eb17f32834.

* chore: bump all package versions to 0.2.5, remove release workflow

- Bump all 25 packages to 0.2.5 to realign with npm registry
- Update package-version test to expect 0.2.5
- Remove stale .changeset/linear-spawn-branch-name.md
- Delete .github/workflows/release.yml (changesets-based NPM publish)

---------

Co-authored-by: Prateek <karnalprateek@gmail.com>
Co-authored-by: AO Bot <ao-bot@composio.dev>
2026-04-13 05:47:08 +05:30
Gautam Tayal 372c1be631 refactor(opencode): simplify AGENTS.md handling and remove unused code 2026-04-11 18:35:18 +05:30
Gautam Tayal 054e601167 refactor(opencode): replace OpenCode config with agents markdown file 2026-04-11 18:20:33 +05:30
Gautam Tayal fdf46653fc chore: add tests 2026-04-11 15:54:16 +05:30
Gautam Tayal f83f45eacb feat (opencode): add config file instead of injecting system prompt manually 2026-04-11 15:54:07 +05:30
Dhruv Sharma d908500791
Merge pull request #882 from DNX/feat/linear-spawn-branch-name
Honor Linear branchName as single source of truth for spawn worktree branches
2026-04-11 14:45:57 +05:30
harshitsinghbhandari 8f43006472 fix(agent-cursor): use printf %s pattern to prevent shell injection
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>
2026-04-10 12:34:07 +05:30
harshitsinghbhandari 843ae2b897 fix(agent-cursor): use shell substitution for systemPromptFile
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>
2026-04-10 12:23:47 +05:30
harshitsinghbhandari e47b03807c fix(agent-cursor): address Cursor Bugbot security findings
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>
2026-04-10 12:16:37 +05:30
harshitsinghbhandari f154f87add fix: merge upstream main and resolve conflicts for cursor agent
- 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>
2026-04-10 11:48:27 +05:30
harshitsinghbhandari 63faae784c fix(agent-cursor): address 7 critical PR review findings
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>
2026-04-10 11:02:33 +05:30
harshitsinghbhandari d97177bced fix(cursor): address PR review comments from Copilot AI and Cursor bot
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>
2026-04-09 22:17:13 +05:30
harshitsinghbhandari e210feb850 fix(agent-cursor): use --sandbox disabled instead of --trust to skip workspace prompts
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>
2026-04-09 21:46:22 +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 2c862f3f29 fix: restore @composio/core references — third-party SDK, not our scope 2026-04-09 16:00:31 +00:00
Prateek 4f2616674f fix: address bugbot comments — missed renames in preflight, tests, and shell scripts 2026-04-09 16:00:31 +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
harshitsinghbhandari f8e535f605 fix(cursor): correct binary name from 'cursor' to 'agent'
CRITICAL: The Cursor CLI binary is actually called 'agent', not 'cursor'.
This was a fundamental assumption error that made the plugin completely non-functional.

## Changes

### Binary and Process Names
- **manifest.description**: "Cursor CLI" → "Cursor Agent CLI"
- **processName**: "cursor" → "agent" (line 119)
- **detect()**: Check for "agent" binary instead of "cursor" (line 335)
- **isProcessRunning**: Look for "agent" process, not "cursor" (line 263)
  - Updated regex to `/(?:^|\/)\bagent\b(?:\s|$)/` with word boundaries
  - Prevents false matches on "agent-orchestrator" etc.

### Command Generation
- **Base command**: "cursor" → "agent" (line 122)
- **Permission flags**: Changed from assumed --auto-approve to actual Cursor flags:
  - --force (Run Everything / YOLO mode)
  - --trust (trust workspace without prompting)
  - --approve-mcps (auto-approve MCP servers)
- **Prompt handling**: Prompt is passed as positional argument, not --prompt flag
- **Removed**: --system flag support (Cursor agent doesn't have this)

### Activity Detection
- Updated prompt patterns: "cursor>" → "agent>", "[cursor]" → "[agent]"
- All terminal output detection patterns updated

### Test Updates (51/51 passing)
- processName expectation: "cursor" → "agent"
- Command assertions: "cursor" → "agent", --auto-approve → --force
- Combined options test: updated to reflect actual flag names
- Process detection tests: "cursor" → "agent", ".cursor" → "/path/to/agent"
- Activity detection tests: all "cursor" references → "agent"
- Manifest description test: updated to match new description

## Verification

**Before (broken):**
```bash
$ detectAvailableAgents()
Available agents: claude-code, aider, codex, opencode  #  cursor missing
```

**After (working):**
```bash
$ detectAvailableAgents()
Available agents: claude-code, aider, codex, cursor, opencode  #  cursor detected

$ agent.getLaunchCommand({permissions: 'permissionless', model: 'claude-sonnet-4', prompt: 'Fix bug'})
"agent --force --trust --approve-mcps --model 'claude-sonnet-4' 'Fix bug'"  #  correct

$ agent.processName
"agent"  #  correct
```

## Impact

This fix makes the plugin actually functional. The previous implementation would:
-  Never detect Cursor CLI (detect() checked wrong binary)
-  Never find running processes (isProcessRunning looked for wrong name)
-  Generate invalid commands (wrong binary name and flags)

Now it:
-  Detects Cursor CLI when `agent` binary is installed
-  Correctly identifies running agent processes
-  Generates valid commands with correct flags

## Testing
-  51/51 tests passing
-  TypeScript typecheck clean
-  Agent detection working (verified with actual agent binary)
-  Command generation produces valid agent commands

Addresses: ComposioHQ/agent-orchestrator#1076

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-04-09 21:11:07 +05:30
harshitsinghbhandari f3224e9193 fix(cursor): correct getRestoreCommand signature and update documentation
Address critical PR review findings from #1076.

## Changes

### Critical Fix
- **cursor/index.ts:308**: Add required parameters to getRestoreCommand signature
  - Was: `async getRestoreCommand(): Promise<string | null>`
  - Now: `async getRestoreCommand(_session: Session, _project: ProjectConfig): Promise<string | null>`
  - Fixes interface contract violation that would cause silent runtime failures
  - Parameters prefixed with _ since method always returns null (Cursor doesn't support resume)

### Documentation Improvements
- **cursor/index.ts:125-127**: Add comment explaining --auto-approve flag
  - Clarifies that --auto-approve is equivalent to Aider's --yes flag
  - Maps to same permission semantics across agents
- **cli/config-instruction.ts:24,144**: Add cursor to available agents list
  - Update defaults example to include cursor
  - Update "Available plugins" reference to include cursor

### Type Imports
- **cursor/index.ts:19**: Import ProjectConfig type for getRestoreCommand signature

## Testing
-  cursor plugin tests: 51/51 passing
-  cursor plugin typecheck: clean
-  No regressions introduced

## Why These Changes Are Needed

**Before:**
- getRestoreCommand signature mismatch would cause silent failures when core system calls it with parameters
- Config documentation didn't mention cursor as available agent option
- No explanation why --auto-approve differs from Aider's --yes naming

**After:**
- Interface contract properly satisfied
- Documentation complete and helpful
- Clear code comments explaining design decisions

Addresses: ComposioHQ/agent-orchestrator#1076

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-04-09 19:29:26 +05:30
harshitsinghbhandari 04f3a7c27f fix(cursor): register plugin in all discovery/resolution layers
Address PR review feedback from #1076 to make Cursor agent fully functional.

## Changes

### Plugin Registration (High Severity Fixes)
- **core/plugin-registry.ts**: Add cursor to BUILTIN_PLUGINS array for config-based resolution
- **cli/plugins.ts**: Import and register cursor in agentPlugins map for getAgent/getAgentByName
- **cli/detect-agent.ts**: Add cursor to AGENT_PLUGINS array for detectAvailableAgents()
- **web/services.ts**: Import and register cursor plugin for dashboard SessionManager/LifecycleManager
- **web/package.json**: Add @composio/ao-plugin-agent-cursor dependency

### Activity Detection Fix (Medium Severity)
- **cursor/index.ts**: Remove overly broad blocked detection patterns (error:/failed:)
  - Compiler errors, test failures, and linter output are normal tool output
  - Terminal-based detection can't distinguish between actionable agent errors and normal output
  - Follow Aider/OpenCode pattern: only Claude Code detects blocked (has native JSONL)
  - Added comment explaining why blocked detection is removed
- **cursor/index.test.ts**: Remove blocked detection test cases

## Why These Changes Are Needed

Before these fixes:
- `defaults.agent: cursor` in config would throw "Unknown agent plugin: cursor"
- `ao spawn --agent cursor` would fail
- Dashboard couldn't resolve cursor agent (SessionManager/LifecycleManager failures)
- `detectAvailableAgents()` never discovered Cursor
- False "blocked" states when agent encountered normal compiler errors

After these fixes:
- Cursor agent fully discoverable and usable via config and CLI
- Dashboard can spawn and manage Cursor sessions
- Activity detection matches other terminal-based agents (Aider, OpenCode)
- No false positives from normal tool output

## Testing
-  cursor plugin tests: 51/51 passing (reduced from 52 due to removed blocked tests)
-  core typecheck: clean
-  web typecheck: clean
-  CLI can import cursor plugin without errors

Addresses: ComposioHQ/agent-orchestrator#1076

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-04-09 19:17:41 +05:30
harshitsinghbhandari 464f771a76 feat: add Cursor agent CLI support (#1060)
Implement a new agent plugin for Cursor CLI following the existing agent plugin pattern.

## Changes
- Create `packages/plugins/agent-cursor/` with full Agent interface implementation
- Add Cursor plugin to CLI dependencies
- Implement all required methods:
  - getLaunchCommand - command to start Cursor CLI with flags
  - getEnvironment - env vars including ~/.ao/bin in PATH
  - detectActivity - terminal output classification for Cursor prompts
  - getActivityState - JSONL/activity-based state detection with fallbacks
  - isProcessRunning - process liveness check (tmux TTY + PID)
  - setupWorkspaceHooks - PATH wrappers for git/gh metadata capture
  - getSessionInfo - extract summary from .cursor directory if available
  - recordActivity - delegate to shared recordTerminalActivity
  - postLaunchSetup - ensure hooks are installed post-launch
  - getRestoreCommand - returns null (Cursor doesn't support session resume)

## Testing
- Comprehensive test suite with 52 passing tests covering:
  - Manifest validation
  - Command generation with permission modes
  - Process detection (tmux + process runtime)
  - Activity detection from terminal output
  - Activity state cascade (JSONL → git commits → session mtime → fallback)
  - All required getActivityState contract states (exited, waiting_input, blocked, active, idle)
  - Session info extraction
  - Environment setup

## Pattern
Follows the Aider agent pattern using:
- PATH wrappers (`~/.ao/bin/gh`, `~/.ao/bin/git`) for metadata capture
- AO Activity JSONL for terminal-derived activity detection
- Age-based decay for active/ready/idle state transitions
- Process name regex matching both `cursor` and `.cursor` (dot-prefixed)

Closes #1060

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-04-09 18:51:33 +05:30
Harsh Batheja e25e9b4967
fix(lifecycle): reduce GitHub API rate limiting from batch enrichment bypass (#906)
* fix(lifecycle): reduce GitHub API rate limiting from batch enrichment bypass

Three optimizations to prevent API storms in the lifecycle manager poll cycle:

1. **CRITICAL - maybeDispatchMergeConflicts**: Gate the getMergeability()
   fallback to only run when batch enrichment didn't run at all. Previously
   it called getMergeability() (3 REST calls) whenever hasConflicts was
   undefined, even when the batch had already fetched PR data. Now uses
   cachedData.hasConflicts ?? false when the batch ran.

2. **HIGH - maybeDispatchCIFailureDetails**: Use batch enrichment ciChecks
   when available instead of calling getCIChecks() (separate REST call)
   on every poll. The GraphQL batch query now fetches statusCheckRollup
   contexts (individual check names, statuses, URLs) alongside the rollup
   state. Falls back to getCIChecks() only when batch didn't run.

3. **MEDIUM - maybeDispatchReviewBacklog**: Throttle getPendingComments +
   getAutomatedComments API calls to at most once per 2 minutes per session.
   These were called every 30s even when nothing had changed.

Impact: ~8-10 API calls/PR/poll reduced to ~2-4, enabling 3-4x more
concurrent sessions before hitting GitHub's 5,000/hr REST limit.

Also extends PREnrichmentData with ciChecks?: CICheck[] and adds
parseCheckContexts() helper to graphql-batch.ts for parsing CheckRun
and StatusContext nodes from the GraphQL statusCheckRollup.contexts field.

* fix(scm-github): fall back to getCIChecks() when contexts list is truncated

When a PR has >20 CI checks, contexts(first: 20) silently truncates the
list. Setting ciChecks to undefined when pageInfo.hasNextPage is true
ensures maybeDispatchCIFailureDetails falls back to the getCIChecks()
REST call, which returns all checks without truncation.

Also adds pageInfo { hasNextPage } to the contexts GraphQL query so
truncation can be detected.

* fix(lifecycle): prune lastReviewBacklogCheckAt in pollAll cleanup loop

Add the new throttle map to the existing pruning loop that removes stale
entries for sessions no longer in the session list. Previously the map
was only cleared on terminal status transitions, leaving orphaned entries
for sessions removed externally (killed + cleaned up without transition).

* fix(lifecycle): bypass throttle on review transition; fix StatusContext conclusion

Two fixes for automated review findings:

1. Bypass review backlog throttle when a transition reaction just fired for
   humanReactionKey or automatedReactionKey. The transitionReaction branch
   needs to read the current fingerprint via the API to record
   lastPendingReviewDispatchHash. Without bypassing, the throttle prevents
   this write and the next unthrottled poll sees a stale (empty) hash,
   clears the reaction tracker, and fires a duplicate dispatch.

2. Set conclusion on StatusContext nodes in parseCheckContexts() to match
   the REST getCIChecksFromStatusRollup() format (rawState.toUpperCase()).
   The CI failure fingerprint includes c.conclusion ?? '', so inconsistent
   conclusion values between GraphQL and REST paths caused phantom fingerprint
   changes when switching sources, triggering duplicate dispatches.

* fix(scm-github): normalize CheckRun conclusion and map NEUTRAL to skipped

Two consistency fixes in parseCheckContexts() vs the REST path:

1. NEUTRAL conclusion: was mapped to 'passed' (with SUCCESS), but
   mapRawCheckStateToStatus() in the REST path maps NEUTRAL to 'skipped'.
   Changed to treat NEUTRAL the same as SKIPPED.

2. CheckRun conclusion: was stored as the raw GraphQL string (may be
   lowercase). REST getCIChecks/getCIChecksFromStatusRollup always store
   conclusion as rawState.toUpperCase(). Now stores rawConclusion which
   is already uppercased during the status branching logic.

Both fixes prevent phantom fingerprint changes when maybeDispatchCIFailureDetails
switches between GraphQL batch and REST fallback across poll cycles.

* fix(scm-github): map STALE/NOT_REQUIRED/NONE conclusions to skipped

parseCheckContexts() was mapping these conclusions to 'failed' via the
else fallback, while mapRawCheckStateToStatus() in the REST path
explicitly maps all of them to 'skipped'. Added them to the skipped
branch alongside SKIPPED and NEUTRAL to fully mirror the REST mapping.

* fix(scm-github): map QUEUED/WAITING to pending not running

parseCheckContexts() mapped QUEUED and WAITING CheckRun statuses to
'running', but mapRawCheckStateToStatus() in the REST path maps both
to 'pending'. Only IN_PROGRESS maps to 'running' in the REST path.

Fixes fingerprint inconsistency when switching between GraphQL batch
and REST fallback across poll cycles.

* fix(scm-github): map STARTUP_FAILURE to skipped; guard null pageInfo

- STARTUP_FAILURE conclusion now falls through to the "skipped" branch
  (matching mapRawCheckStateToStatus() REST default) instead of the
  explicit failure enumeration catch-all
- Null pageInfo guard prevents TypeError from typeof null === "object"
  JavaScript quirk when accessing hasNextPage on a null pageInfo field
- Tests added for both cases

* fix(scm-github): map COMPLETED+null conclusion to skipped not passed

When a CheckRun has status COMPLETED and conclusion null, the REST path's
mapRawCheckStateToStatus() converts it to "" which maps to "skipped".
The GraphQL path was incorrectly mapping it to "passed" via !rawConclusion.
Fix: only map rawConclusion === "SUCCESS" to "passed"; null falls through
to the else branch → "skipped", matching the REST path exactly.
2026-04-06 14:59:00 +05:30
Harsh Batheja be0ce0baac
fix(agent-claude-code): return idle state for freshly spawned sessions with no JSONL file (#891)
* fix(agent-claude-code): return idle state when no JSONL session file exists

Freshly spawned sessions had no Claude Code JSONL file yet (Claude Code
doesn't create it until the first conversation), causing getActivityState
to return null → displayed as 'unknown' in ao status.

When the process is running but no session file exists, return
{ state: 'idle', timestamp: now } so the dashboard shows the correct
state immediately after spawn.

Closes #883

* fix(agent-claude-code): use session.createdAt for idle timestamp when no JSONL file

Using new Date() as the timestamp caused isIdleBeyondThreshold to always
compute ~0ms, preventing stuck detection from ever firing for sessions that
hang before creating a JSONL file. Using session.createdAt correctly
represents when the session began, allowing stuck detection to eventually
trigger.
2026-04-03 19:44:59 +05:30
Denis Darii f92c5f6c9d Validate Linear branchName before spawn 2026-04-03 09:59:58 +02:00
Denis Darii adb3958d26 Add branchName to issue payload in Linear tracker 2026-04-03 09:37:43 +02:00
Denis Darii 42a4349e3f Update Linear tracker tests: expect branchName on issue payloads
Made-with: Cursor
2026-04-02 22:17:21 +02:00
Denis Darii 8d8a1aeb9d Query Linear issue branchName in GraphQL, map onto Issue
Made-with: Cursor
2026-04-02 22:17:20 +02:00
Harsh Batheja df8395c3ec
feat: standardize agent plugins — shared hooks, activity JSONL, backfill aider/opencode (#755)
* feat: standardize agent plugins with shared hooks, activity JSONL, and CLAUDE.md

- Add CLAUDE.md with full project context and agent plugin implementation standards
- Extract shared PATH-wrapper metadata hooks into @composio/ao-core (agent-workspace-hooks.ts)
- Backfill Aider + OpenCode: setupWorkspaceHooks, postLaunchSetup, getSessionInfo, getRestoreCommand
- Add recordActivity method to Agent interface for terminal-derived JSONL writing
- Create activity-log.ts in core: appendActivityEntry, readLastActivityEntry
- Lifecycle manager calls recordActivity before getActivityState for agents that implement it
- Upgrade detectActivity in Aider/OpenCode with real terminal prompt/permission patterns
- Upgrade Codex getActivityState to parse JSONL entry types (6 states, up from 2)
- Replace duplicated normalizePermissionMode with shared normalizeAgentPermissionMode from core
- Remove ~200 lines of duplicated shell wrapper code from Codex plugin
- Add git wrapper detection for existing branch switches (parity with Claude Code hooks)
- 484 tests passing across all 4 agent plugins

* chore: unignore CLAUDE.md and AGENTS.md, slim down AGENTS.md to pointer

- Remove CLAUDE.md and AGENTS.md from .gitignore (both should be tracked)
- Slim AGENTS.md from 96 to 30 lines — commands, TL;DR, key files
- Full context now lives in CLAUDE.md; AGENTS.md points there

* fix: eliminate redundant double file read in readLastActivityEntry

Remove the readLastJsonlEntry call that was only used as a null-check,
then immediately discarded before re-reading the same file from scratch.
Now performs a single open + stat + tail-read per poll cycle.

* fix: remove duplicate case-insensitive regex in OpenCode detectActivity

The \(y\)es.*\(n\)o pattern with /i flag was identical to the preceding
\(Y\)es.*\(N\)o/i check — remove the redundant line.

* fix: use zero-initialized buffer and slice to bytesRead in readLastActivityEntry

Replace Buffer.allocUnsafe with Buffer.alloc and slice the result to
actual bytesRead, preventing uninitialized heap data from being parsed
if the file shrinks between stat() and read().

* fix: add staleness cap for waiting_input/blocked and deduplicate recordActivity

- Add ACTIVITY_INPUT_STALENESS_MS (5 min) cap so stale waiting_input/blocked
  entries don't keep sessions stuck in needs_input on the dashboard forever.
- Extract checkActivityLogState() into core — shared by aider, opencode, codex.
- Extract classifyTerminalActivity() into core — deduplicates the identical
  recordActivity logic across all three plugins.

* fix: prioritize native Codex JSONL over AO activity log in getActivityState

Reorder detection so Codex's native 6-state JSONL (approval_request,
error, tool_call, etc.) is checked first. AO activity JSONL from
terminal parsing is now a fallback only for waiting_input/blocked states
that the native JSONL may not have captured. Previously the AO log was
always fresh (written every poll cycle by recordActivity) and shadowed
the richer native detection entirely.

* fix: restrict AO activity JSONL to waiting_input/blocked only

checkActivityLogState now only returns results for waiting_input/blocked
states. Non-critical states (active/ready/idle) return null, forcing
callers to fall through to their native signals (git commits, chat
history, OpenCode API, Codex native JSONL). This prevents the lifecycle
manager's recordActivity writes (which refresh mtime every poll cycle)
from shadowing richer detection methods and breaking stuck-detection.

* fix: prevent stale idle timestamp in aider and skip flags in git wrapper

- Remove Aider's fallback that returned idle with activityResult.modifiedAt
  (always fresh due to recordActivity writes). Now returns null when no
  git commits or chat history are found, letting the lifecycle manager
  handle stuck-detection correctly.
- Fix git wrapper catch-all case to skip flag arguments (e.g. -B) and
  look at $3 for the actual branch name.

* fix: add mtime fallback for empty Codex JSONL and remove unused exports

- When the native Codex session file exists but readLastJsonlEntry returns
  null (empty/unparseable), fall back to stat-based mtime detection instead
  of losing activity detection entirely.
- Remove unused exports getActivityLogPath and ACTIVITY_INPUT_STALENESS_MS
  from @composio/ao-core barrel — they are only used internally.

* fix: opencode activity state detection and CLAUDE.md agent plugin standards

- Fix session ID capture to handle both session_id (snake_case) and
  sessionID (camelCase) from OpenCode 1.3.x JSON responses
- Replace broken --command true with -- noop for session creation
  (true is not a valid OpenCode command since 1.3.x)
- Add JSONL mtime fallback in getActivityState so active/ready/idle
  states work even when findOpenCodeSession returns null
- Rewrite CLAUDE.md activity detection section with the full
  getActivityState contract, mandatory JSONL mtime fallback pattern,
  and 8 required tests every agent plugin must implement

* fix: opencode --command true flag and activity JSONL mtime staleness

- Fix opencode getLaunchCommand to use `--command true` instead of `-- noop`
  (aligns with test expectations and opencode CLI docs)
- Fix checkActivityLogState to use entry.ts instead of file mtime for
  staleness checking — recordActivity refreshes mtime every poll cycle,
  which prevented stale waiting_input/blocked entries from being detected
- Fix opencode getActivityState fallback to use entry state directly
  instead of re-deriving from mtime, which always returned "active"
  because recordActivity refreshes the file every cycle
- Update tests to reflect new entry-state-based fallback behavior

* fix: deduplicate recordActivity writes and restore mtime-based fallback

recordActivity was writing to the JSONL every poll cycle (~30s), which
kept refreshing the file mtime and prevented the JSONL mtime fallback
in getActivityState from ever reaching "ready" or "idle".

Fix: skip writes when the state hasn't changed and the last entry is
<20s old. This keeps mtime fresh during active work (writes every
20-30s, within the 30s activeWindow) but lets it age naturally when
the agent goes quiet.

Also restores the mtime-based age classification in the JSONL fallback
(active/ready/idle by mtime age) instead of returning the entry state
directly, which was always "active" since that's what recordActivity
writes.

Applied to both OpenCode and Aider plugins. Updated CLAUDE.md with
the dedup pattern and rationale.

* fix: align integration tests with opencode `-- noop` launch command

Update 9 test expectations from `--command true` to `-- noop` to match
the reverted getLaunchCommand implementation.

* fix: add JSONL mtime fallback to Aider getActivityState

When git commits and chat history are both unavailable (e.g. early
session startup), Aider's getActivityState now falls back to the AO
activity JSONL mtime for active/ready/idle classification — matching
OpenCode's existing step 3 fallback. Previously it returned null,
leaving the dashboard with no activity signal.

* fix: add write deduplication to Codex recordActivity

Add the same dedup logic that Aider and OpenCode already have — skip
writes when the state hasn't changed and the last entry is recent
(<20s). Prevents unbounded file growth and stale mtime refreshes.

* refactor: extract shared recordTerminalActivity into core

Move the duplicated recordActivity logic (classify + dedup + append)
from all three plugins into a shared `recordTerminalActivity` function
in `@composio/ao-core/activity-log`. Each plugin's `recordActivity`
is now a thin wrapper that delegates to the shared function.

Add core tests for classifyTerminalActivity, checkActivityLogState,
and recordTerminalActivity (10 tests).

* fix: validate JSONL entry fields, handle invalid dates, consistent operators

- Validate required fields (ts, state, source) before casting parsed
  JSON to ActivityLogEntry — prevents malformed entries from propagating
- Guard against invalid Date parsing in checkActivityLogState — returns
  null instead of comparing against NaN
- Use <= instead of < in Aider chat-history threshold comparisons to
  match OpenCode and Aider's own JSONL fallback path

* fix: use --command true for opencode run, validate sed key parameter

- Replace `-- noop` with `--command true` in opencode getLaunchCommand
  so the bootstrap uses a valid command
- Validate metadata key against [a-zA-Z0-9_-]+ in the git wrapper's
  update_ao_metadata to prevent sed metacharacter injection

* fix: extract DEFAULT_ACTIVE_WINDOW_MS constant, clarify Codex recordActivity

- Extract magic number 30_000 into DEFAULT_ACTIVE_WINDOW_MS constant in
  core types, used by Aider and OpenCode for active/ready thresholds
- Clarify in CLAUDE.md that Codex implements recordActivity as a safety
  net for when its native JSONL is missing/unparseable, not redundantly

* fix: add JSONL mtime fallback to Codex getActivityState

When native Codex session file is missing but AO JSONL has data,
derive active/ready/idle from JSONL mtime instead of returning null.
Matches the fallback pattern already in Aider (step 4) and OpenCode
(step 3).

* fix: validate ActivityState and source values when parsing JSONL entries

Validate that `state` is one of the known ActivityState values and
`source` is "terminal" or "native" before constructing the entry.
Construct the entry explicitly instead of using unsafe double cast.

* fix: wrap getOutput + recordActivity in try-catch to protect getActivityState

If runtime.getOutput() throws (e.g. tmux unresponsive), the error
previously propagated to the outer catch, skipping getActivityState
entirely. Now the entire recordActivity preamble is wrapped in its
own try-catch so getActivityState always runs.

* chore: add .ao/ to gitignore

* chore: clean up gitignore comment

* test: add coverage for activity-log and agent-workspace-hooks

- activity-log: test readLastActivityEntry (missing file), invalid
  entry.ts in checkActivityLogState, blocked state path
- agent-workspace-hooks: test buildAgentPath (dedup, defaults,
  ordering), setupPathWrapperWorkspace (create/skip wrappers,
  AGENTS.md create/skip)

Raises diff coverage from 39% toward 80% threshold.

* test: add real file I/O tests for readLastActivityEntry and recordTerminalActivity

Test readLastActivityEntry with actual temp files: valid entries,
empty file, invalid JSON, invalid state, missing fields, multi-line.
Test recordTerminalActivity dedup logic and actionable state bypass.

* fix: add activeWindow threshold to Codex native JSONL state detection

Action entries (tool_call, user_input, exec_command) now use the 30s
active window: <=30s is "active", 30s-5min is "ready", >5min is "idle".
Previously these skipped "ready" entirely, going straight from "active"
to "idle" at the 5min threshold.

* fix: add activeWindow threshold to Claude Code native JSONL state detection

Same fix as Codex — action entries (user, tool_use, progress) now use
the 30s active window for consistent 3-state classification across
all agent plugins: <=30s active, 30s-5min ready, >5min idle.

* fix: handle truncated JSONL, add exited state, fix session lookup and dedup window

- readLastActivityEntry: increase tail buffer to 4KB, skip truncated
  first line when reading from offset, try lines from end on parse error
- Add "exited" to valid ActivityState set in JSONL validation
- findOpenCodeSession: pick most recently updated session when multiple
  title matches exist, preventing stale session binding
- Increase dedup window from 20s to 60s so mtime can age past the 30s
  active window between writes, allowing "ready" state to be reached

* fix: use entry state for JSONL fallback, write AGENTS.md to .ao/, revert dedup to 20s

- Replace mtime-based active/ready/idle derivation in all 3 plugin
  fallbacks with direct entry.state + entry.ts usage. This eliminates
  the fundamental conflict between write deduplication and mtime
  freshness — the entry already has the correct detected state.
- Revert dedup window to 20s (purely I/O optimization, no longer
  affects state detection since mtime is not used)
- Write AO session context to .ao/AGENTS.md (gitignored) instead of
  modifying the repo-tracked AGENTS.md, preventing dirty worktree state

* fix: reorder Codex fallback chain so AO JSONL is checked before stat mtime

When native JSONL exists but can't be parsed, the stat() fallback
previously returned early, skipping AO JSONL waiting_input/blocked
detection and the ready state. Now it falls through to AO JSONL first,
then uses stat mtime as a last resort with proper 3-state classification.

* fix: re-validate canonicalized ao_dir against trusted roots

After resolving symlinks with pwd -P, re-check real_ao_dir against
the trusted root allowlist. Prevents paths like /tmp/../../home/user
from passing the pre-canonicalization check then escaping to arbitrary
directories after symlink resolution.

* fix: update tests for .ao/AGENTS.md location and remove unused vi import

- Update Codex setupWorkspaceHooks tests to expect .ao/AGENTS.md
  instead of workspace root AGENTS.md
- Remove unused vi import from activity-log test (lint error)

* fix: add age-based decay to JSONL entry fallback via getActivityFallbackState

Extract getActivityFallbackState in core — reclassifies entry state
based on entry.ts age (active→ready→idle) so old entries don't stay
as "active" forever when recordActivity stops being called. All three
plugins now use this shared helper for their JSONL fallback paths.

* fix: apply staleness cap to actionable states in getActivityFallbackState

Stale waiting_input/blocked entries (older than ACTIVITY_INPUT_STALENESS_MS)
are now treated as idle in the fallback, preventing them from bypassing
the staleness filtering in checkActivityLogState.

* fix: respect entry state as ceiling in getActivityFallbackState

Age-based decay can only demote (active→ready→idle), never promote.
A fresh "idle" entry stays "idle" instead of being reclassified as
"active" — the detected state from terminal output is authoritative.

* docs: update CLAUDE.md to match current activity detection implementation

- Replace inline recordActivity dedup example with recordTerminalActivity delegation
- Replace mtime-based fallback example with getActivityFallbackState
- Update step 4 description: entry state + age-based decay, not mtime
- Add new core exports to utilities section
- Document .ao/AGENTS.md location and setupPathWrapperWorkspace
- Update required test list for entry-based fallback

* fix: skip metadata helper rewrite when version marker matches

Move ao-metadata-helper.sh write inside the needsUpdate check so it's
only rewritten when wrapper scripts are outdated, not on every call.

* fix: update Codex test for metadata helper skip when version matches

Metadata helper is now inside the needsUpdate check, so when the
version marker matches, no wrapper writes occur (including helper).
2026-04-01 21:50:07 +05:30
Dhruv Sharma fe1d45b0a7 fix: address production failure modes from adversarial review
- Rollback plugin config on setup failure so a half-configured
  notifier is never left enabled (non-transactional install fix)
- Use atomic temp+rename for health summary writes to prevent
  corruption under concurrent notifications
- Scope credential injection to only when OpenClaw notifier is
  configured, avoiding ambient secret exposure to unrelated projects

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 14:39:49 +05:30
Dhruv Sharma 97b0646022 improve notifier-openclaw error messages 2026-03-31 04:02:33 +05:30
Gautam Tayal b7d3293858
Merge pull request #759 from gautamtayal1/fix/add-launch-command-script
fix (cli): tmux runtime to execute long launch commands via temp script
2026-03-30 15:48:52 +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
Gautam Tayal 1ed4430c70 fix: revert unlink script file 2026-03-29 20:34:33 +05:30
Gautam Tayal b83686b662 test(workspace): add test for handling existing branch with local default-branch fallback 2026-03-29 14:16:54 +05:30
Gautam Tayal f113f8b801 fix(workspace): improve error handling in worktree creation 2026-03-29 13:18:41 +05:30
Gautam Tayal ffcf5ea8bb feat(workspace): enhance worktree creation with remote branch resolution 2026-03-29 12:51:46 +05:30
Gautam Tayal 13192d4998 fix: ensure Enter key is sent after launching tmux session 2026-03-29 09:59:58 +05:30
Gautam Tayal 04c801fff7 fix: add cleanup error boundary for script 2026-03-29 09:49:17 +05:30
Gautam Tayal 0ad3d34d2b fix 2026-03-28 16:06:33 +05:30