Commit Graph

10 Commits

Author SHA1 Message Date
prateek 59c490a3af
fix: dashboard config discovery + CLI service layer refactoring (#70)
* fix: config discovery, activity detection, and metadata port storage

- findConfigFile() checks AO_CONFIG_PATH env var (resolved to absolute path)
- loadConfig() delegates to findConfigFile() for consistent validation
- Pure Node.js readLastJsonlEntry (no external tail binary), safe for
  multi-byte UTF-8 at chunk boundaries
- Added "ready" activity state to agent plugins
- Store dashboardPort, terminalWsPort, directTerminalWsPort in session
  metadata so ao stop targets the correct processes
- Zod schema port default aligned with TypeScript interface

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

* fix: dashboard config discovery + CLI service layer refactoring

- Config discovery via AO_CONFIG_PATH env var
- Auto port detection with PortManager
- Activity detection with ready state, pure Node.js readLastLine
- 5 CLI services: ConfigService, PortManager, DashboardManager, MetadataService, ProcessManager
- Store all service ports in metadata for ao stop
- Set NEXT_PUBLIC_ env vars for frontend terminal components
- Multi-byte UTF-8 safe readLastJsonlEntry
- Tests for all new services and utils

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

* fix: address bugbot review comments (port fallback + systemPrompt)

1. Align port fallback to 3000 everywhere (matching Zod schema default):
   - start.ts: config.port ?? 3000
   - dashboard.ts: config.port ?? 3000
   - types.ts JSDoc: "defaults to 3000"
   - orchestrator-prompt.ts: already correct at 3000

2. Add --append-system-prompt to Claude Code plugin's getLaunchCommand
   so orchestrator context is actually passed to the Claude agent.
   Previously systemPrompt was generated but silently dropped.

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

* fix: remove dead ConfigService mock from status test

The vi.mock for ConfigService.js referenced a deleted module.
Config mocking is already handled by the @composio/ao-core mock.

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

* fix: extract shared buildDashboardEnv to eliminate duplication

Dashboard env construction (AO_CONFIG_PATH, PORT, NEXT_PUBLIC_*) was
duplicated between start.ts and dashboard.ts. Extracted into
buildDashboardEnv() in web-dir.ts (already shared by both commands).

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 17:08:48 +05:30
prateek dcfee04e1b
fix: terminal servers compatible with hash-based architecture (#87)
* fix: terminal servers compatible with hash-based architecture

The terminal WebSocket servers (direct-terminal-ws and terminal-websocket)
used config.dataDir to validate sessions, which no longer exists in the
hash-based architecture. Also fixed node-pty failing to find tmux via
posix_spawnp.

Changes:
- Remove config.dataDir dependency, validate via `tmux has-session` instead
- Add resolveTmuxSession() to map user-facing IDs (ao-15) to hash-prefixed
  tmux names (8474d6f29887-ao-15)
- Use explicit tmux path discovery (findTmux) since node-pty's posix_spawnp
  doesn't reliably inherit PATH
- Include /opt/homebrew/bin in fallback PATH for macOS ARM

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

* fix: add session resolution to ttyd server and use exact tmux matching

- Add findTmux() and resolveTmuxSession() to terminal-websocket.ts
  (previously only in direct-terminal-ws.ts), fixing hash-prefixed
  session lookup for the ttyd-based terminal server
- Use tmux exact match prefix (=sessionId) in has-session checks
  to prevent ao-1 from matching ao-15 via prefix matching
- Add server compatibility tests that verify both servers handle
  hash-based architecture correctly (14 tests)
- Include server/ in tsconfig and vitest config for typecheck coverage

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

* refactor: extract tmux-utils and add proper unit tests

- Extract findTmux(), resolveTmuxSession(), validateSessionId() into
  shared server/tmux-utils.ts — eliminates duplication between
  direct-terminal-ws.ts and terminal-websocket.ts
- Add 20 real unit tests with injected mocks that test actual behavior:
  - findTmux: candidate priority, fallback to bare name
  - resolveTmuxSession: exact match, hash-prefix resolution,
    = prefix for preventing tmux prefix matching (ao-1 vs ao-15),
    null when no session found, tmux not running
  - validateSessionId: path traversal, shell injection, whitespace
- Slim down server-compatibility.test.ts to 10 structural checks
  (imports tmux-utils, no loadConfig, no config.dataDir, no existsSync)

Total: 30 tests — all pass on fix branch, 8 fail on main

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

* test: add real integration tests for direct-terminal-ws

- Refactor direct-terminal-ws to export createDirectTerminalServer()
  factory so tests can control server lifecycle without side effects
- Add 10 integration tests that create real tmux sessions, start the
  real server, connect via WebSocket, and verify the full flow:
  - Health endpoint returns 200
  - Missing session parameter → close 1008
  - Path traversal in session ID → close 1008
  - Shell injection in session ID → close 1008
  - Nonexistent tmux session → close 1008
  - Real tmux session → connects and receives terminal output
  - Hash-prefixed session resolution works end-to-end
  - Can send input and receive echoed output
  - Resize messages don't crash the connection
  - Unknown HTTP path → 404
- Tests create/destroy tmux sessions in beforeAll/afterAll
- Server runs on random port (port 0) to avoid conflicts
- Total test suite: 40 tests (20 unit + 10 compatibility + 10 integration)

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

* ci: add web server tests to CI pipeline

The web package was explicitly excluded from CI test runs
(pnpm -r --filter '!@composio/ao-web' test). Add a test-web job
that installs tmux, starts the tmux server, and runs the web
package tests (unit + integration).

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

* fix: address CI failures and bugbot review comments

- Fix lint: replace require() with ESM import in integration test
- Fix base-path mismatch: ttyd now uses user-facing sessionId for
  --base-path/URL and actual tmux name for attach-session
- Fix bare "tmux" in ttyd spawn args: use TMUX constant (full path)
- Scope CI test-web job to server/__tests__/ to avoid pre-existing
  failures in src/__tests__/

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

* test: comprehensive unit and integration test coverage

Unit tests (77): validateSessionId covers all injection vectors (shell,
path traversal, command substitution, special chars, unicode, control
chars), findTmux covers all candidate paths and error types,
resolveTmuxSession covers exact match, hash-prefix resolution, suffix
matching precision, edge cases (single char, long lists, multiple
hyphens, different tmux paths).

Integration tests (45): health endpoint lifecycle (active count tracks
connections/disconnections), HTTP routing (404s for all non-health
paths), WebSocket validation (11 injection/traversal vectors), terminal
connection (resize, multi-resize, invalid JSON, non-resize JSON),
hash-prefixed resolution (suffix match, command passthrough, session key
tracking, cross-match prevention), terminal I/O (Ctrl-C, Tab, Enter,
empty messages, rapid keystrokes, multi-line), connection lifecycle
(cleanup, rapid connect/disconnect, error recovery), server creation
(independent instances).

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

* fix: validate 12-char hex prefix in hash-prefixed session resolution

The previous endsWith("-{sessionId}") suffix match was ambiguous:
"hash-my-app-1" would falsely match a lookup for "app-1". Now validates
that the prefix matches the exact format generated by generateConfigHash
(12-char lowercase hex) before comparing the remainder.

Added unit tests for the ambiguity case and invalid prefix formats.
Updated integration test session names to use proper 12-char hex prefixes.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 03:28:55 +05:30
prateek 599710296d
fix: migrate to hash-based project isolation architecture
Complete migration to hash-based directory structure for project isolation. All bugbot issues resolved.
2026-02-18 00:19:55 +05:30
prateek 66005c05c5
feat: implement comprehensive security audit and secret leak prevention (#67)
* feat: implement comprehensive security audit and secret leak prevention

## Changes

### Security Infrastructure
- Add Gitleaks configuration (.gitleaks.toml) for secret scanning
- Add pre-commit hook via Husky to block secret commits
- Add GitHub Actions security workflow (gitleaks, dependency review, npm audit)
- Update .gitignore to exclude secret files and credentials

### Documentation
- Create SECURITY.md with security policy and best practices
- Create README.md with project overview and security section
- Create docs/DEVELOPMENT.md with developer guide
- Create docs/SECURITY-AUDIT-SUMMARY.md with full audit report

### Dependencies
- Add husky@^9.1.7 for git hooks

## Audit Results

-  Current codebase: 0 secrets found (1.47 MB scanned)
- ⚠️ Git history: 1 historical secret (OpenClaw token, documented in SECURITY.md)
-  All test files use dummy values
-  All example configs use environment variables

## Security Features

1. **Pre-commit Hook**: Scans staged files, blocks secrets before commit
2. **CI/CD Pipeline**: Scans full git history on every push/PR
3. **Automated Scanning**: Weekly scheduled scans for new vulnerabilities
4. **Comprehensive Docs**: Security policy, best practices, developer guide

## Testing

```bash
# Scan current files
gitleaks detect --no-git

# Test pre-commit hook
echo "token=ghp_fake" > test.txt
git add test.txt
git commit -m "test"  # Should be blocked
```

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

* fix: make dependency-review job non-blocking

The dependency-review action requires GitHub Advanced Security which may
not be available on all repositories. Adding continue-on-error to prevent
workflow failure when this feature is unavailable.

The check will still run and provide useful information when available,
but won't block the PR if the repository doesn't have Advanced Security.

* feat: add workflow_dispatch trigger to security workflow

Allows manual triggering of security scans for testing and re-running.

* fix: address Cursor Bugbot security review comments

Fixes all high, medium, and low severity issues identified by Cursor Bugbot:

**High Severity:**
- Redact OpenClaw token from documentation (replace with 1af5c4f...872)
- Fix pre-commit hook to FAIL (exit 1) when gitleaks is not installed
  - Previously silently skipped scanning (exit 0) providing false sense of security
- Fix bashism in pre-commit hook: replace &> with > /dev/null 2>&1 (POSIX compliant)

**Medium Severity:**
- Remove overly broad gitignore patterns (*.sql, *.db, *.sqlite)
  - These would block legitimate SQL migration files and database schemas
  - Keep focus on actual credential files only

**Low Severity:**
- Remove author email (samvit@hotmail.com) from audit documentation

All issues now resolved. Pre-commit hook will properly block commits when
gitleaks is missing, ensuring consistent secret scanning enforcement.

* fix: comment out dependency-review job requiring Dependency graph

The dependency-review GitHub Action requires 'Dependency graph' to be
enabled in repository settings. Since this feature may not be available
or configured on all repositories, commenting out this job to prevent
workflow failures.

To re-enable:
1. Go to Settings > Code security and analysis
2. Enable 'Dependency graph'
3. Uncomment the dependency-review job in this workflow

The npm-audit job provides similar dependency vulnerability scanning
and doesn't require special GitHub features.

* feat: re-enable dependency-review job after Dependency graph enabled

Now that Dependency graph is enabled in repo settings, uncomment the
dependency-review job to scan PRs for vulnerable dependencies.

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

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 10:33:50 +05:30
prateek 21335db8af
feat: publish to npm under @composio scope (#32)
* feat: add npm publishing support with @composio scope

Set up Changesets for version management, add publish metadata to all 20
packages under the @composio scope, create an unscoped wrapper package
(@composio/agent-orchestrator) for global install, and add a GitHub
Actions release workflow.

- Rename all packages from @agent-orchestrator/* to @composio/ao-*
- Add @composio/agent-orchestrator wrapper (bin shim → @composio/ao-cli)
- Add license, repository, homepage, bugs, files, engines to all packages
- Add .npmrc (access=public), MIT LICENSE file
- Add .changeset/ config with linked versioning for all packages
- Add .github/workflows/release.yml (changesets publish CI)
- Add changeset, version-packages, release scripts to root

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

* fix: exclude private web package from release build

The release script now filters out @composio/ao-web, matching the
workflow's existing exclusion and preventing a Next.js build failure
from blocking npm publishing.

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

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-15 04:28:57 +05:30
prateek 90f14a6ca5
feat: notifier-composio plugin + integration tests for all plugins (#7)
* feat: implement notifier and terminal plugins (desktop, slack, webhook, iterm2, web)

Implement all 5 notification and terminal UI plugins for the agent
orchestrator, replacing stub files with full implementations of the
Notifier and Terminal interfaces from @agent-orchestrator/core.

Notifier plugins:
- notifier-desktop: OS notifications via osascript (macOS) / notify-send
  (Linux) with priority-based sound (urgent=sound, others=silent)
- notifier-slack: Slack Incoming Webhooks with Block Kit formatting,
  action buttons, PR links, CI status context blocks
- notifier-webhook: Generic HTTP POST with JSON payloads, configurable
  headers, and retry with backoff (default 2 retries)

Terminal plugins:
- terminal-iterm2: AppleScript-based iTerm2 tab management — detects
  existing tabs by profile name, creates/reuses tabs (ported from
  scripts/open-iterm-tab reference implementation)
- terminal-web: Web terminal session tracking for the dashboard's
  xterm.js frontend, providing URL generation and open-state tracking

All plugins follow the PluginModule pattern (manifest + create export)
and pass typecheck cleanly.

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

* test: add comprehensive vitest suites for all notifier and terminal plugins

Add 96 unit tests across 5 plugin packages covering:
- notifier-desktop: priority-based sound mapping, osascript/notify-send
  command generation, platform detection (macOS/Linux/unsupported),
  title formatting, error propagation
- notifier-slack: Block Kit message structure (header/section/context/
  divider blocks), priority emoji mapping, PR link and CI status
  rendering, action button generation (URL and callback variants),
  channel routing, post method
- notifier-webhook: JSON payload serialization, custom headers, retry
  logic (success after retry, exhausted retries, zero retries, network
  errors), timestamp ISO serialization
- terminal-iterm2: AppleScript command generation, tab reuse via profile
  name detection, new tab creation with tmux attach, runtimeHandle
  preference over session ID, batch openAll with delays, error fallback
- terminal-web: session open tracking, dashboard URL configuration,
  openAll batch registration, independent state per instance

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

* fix: address PR review feedback — security, retry logic, side effects

- Add AppleScript injection escaping in terminal-iterm2 and notifier-desktop
- Webhook: only retry on 429/5xx (not 4xx), add exponential backoff
- terminal-iterm2: fix isSessionOpen selecting tab (side effect), remove dead openNewWindow
- notifier-slack: type-guard event.data access, add URL validation
- notifier-webhook: add URL validation, remove unused config interfaces
- Update all tests to cover new behaviors (108 tests passing)

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

* fix: codex review — shell injection, iTerm2 name lookup, notify-send args, retry validation

- terminal-iterm2: shell-escape session names in tmux command (single-quote wrapping)
- terminal-iterm2: use `name of aSession` instead of `profile name` for tab lookup
- notifier-desktop: fix notify-send arg order (options before title/body)
- notifier-webhook: clamp retries/retryDelayMs to safe values (non-negative, finite)
- notifier-slack: sanitize action_id to [a-z0-9_] characters only

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

* chore: lint/format compliance after rebase on main

- Fix ESLint errors: remove unused WebTerminalConfig, ignore next-env.d.ts
- Run prettier on all files for consistent formatting
- Update pnpm-lock.yaml with new lint dependencies

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

* fix: codex review round 2 — printf injection, platform guard, config validation

- terminal-iterm2: use shell-escaped name in printf title (not just tmux target)
- terminal-iterm2: add platform guard — no-op with warning on non-macOS
- notifier-webhook: validate customHeaders are string:string before spreading
- notifier-desktop: validate sound config as boolean (reject string "false")

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

* chore: update lockfile after rebase on main

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

* feat: add notifier-composio plugin and integration tests for all plugins

Add a new notifier-composio plugin as the recommended notification
transport using Composio's unified API for Slack, Discord, and Gmail.
Create comprehensive integration tests (79 tests across 6 files) for
all notifier and terminal plugins, mocking only I/O boundaries.

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

* fix: address PR review feedback — security, retry logic, side effects

- Shell injection fix: double-escape (shell + AppleScript) in iTerm2 printf/tmux
- iTerm2 no-window crash: create window if none exists
- Composio graceful degradation: warn instead of throw when SDK missing
- Composio emailTo validation: require emailTo when defaultApp is gmail
- AbortSignal.timeout() replaces manual AbortController + {once: true} listener
- Discord channelName fallback for channel_id
- Slack empty action_id fallback
- Dashboard port: terminal-web default changed from 9847 to 3000
- escapeAppleScript deduplicated into core/utils.ts
- Consistent vitest version (^3.0.0) for composio plugin
- Remove duplicate eslint ignore entry
- Integration tests updated for shared event-factory helper
- Unit tests updated for new validation and escaping behavior

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

* chore: update lockfile after rebase on main

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

* fix: prevent double ao_ prefix in Slack action_id fallback

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

* fix: decouple Linux urgency from sound config, deduplicate validateUrl

- Linux --urgency=critical now driven by event priority, not sound config
- Moved validateUrl to core/utils.ts, imported by slack and webhook plugins

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

* fix: handle ESM ERR_MODULE_NOT_FOUND for composio-core detection

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

* fix: codex review round 2 — printf injection, platform guard, config validation

- Slack action_id: append index for uniqueness (two "Retry" buttons no
  longer collide)
- Composio Gmail subject: extract GMAIL_SUBJECT constant so notify() and
  post() use the same value
- Agent integration tests: require API key env vars before running to
  prevent CI timeout when secrets are not configured
- Update lockfile after rebase on main

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

* fix: parallelize integration tests and increase CI timeout

- Remove singleFork: true from vitest config — all integration test
  files use unique session prefixes so they're safe to run concurrently
- Increase CI timeout from 15 to 20 minutes as safety margin for agent
  tests that make real API calls

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

* fix: prevent unhandled promise rejection after timeout in composio notifier

Attach a no-op .catch() to the executeAction promise so that if the
timeout fires first and the action later rejects, it doesn't trigger
an unhandledRejection event.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 16:29:59 +05:30
prateek 8707faf11c
feat: implement SCM and tracker plugins (github, linear) (#4)
* feat: implement SCM and tracker plugins (github, linear)

Implement three plugin interfaces from packages/core/src/types.ts:

- scm-github: Full GitHub PR lifecycle via `gh` CLI — PR detection by
  branch, state tracking, CI checks, reviews, review decision, pending
  comments, automated bot comment detection (cursor[bot], codecov, etc.),
  and merge readiness (CI + reviews + conflicts + draft).

- tracker-github: GitHub Issues tracker via `gh` CLI — issue CRUD,
  completion check, branch naming (feat/issue-N), prompt generation,
  list/filter/update/create with label and assignee support.

- tracker-linear: Linear issue tracker via GraphQL API (LINEAR_API_KEY) —
  issue fetch, completion check, branch naming (feat/IDENTIFIER), prompt
  generation, list with team/state/label filters, state transitions via
  workflow state resolution, issue creation with teamId config.

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

* fix: address PR review — GraphQL injection, N+1 queries, timeouts, tests

- tracker-linear: use GraphQL variables in listIssues to prevent injection
- tracker-linear: add 30s HTTP timeout to linearQuery
- tracker-linear: fix issueUrl to use workspace slug from project config
- tracker-linear: add labels/assignee support to createIssue
- scm-github: rewrite getPendingComments to use GraphQL reviewThreads
  with real isResolved status instead of hardcoding false
- scm-github: simplify getAutomatedComments to single API call (N+1 fix)
- scm-github: add 30s timeout to gh CLI calls
- scm-github: fix parseDate to return epoch instead of fabricating dates
- scm-github: add repo format validation in detectPR
- scm-github: fix getCISummary to not count all-skipped as passing
- tracker-github: add 30s timeout to gh CLI calls
- tracker-github: fix createIssue to not use unsupported --json flag
- Add comprehensive vitest tests for scm-github and tracker-github

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

* fix: address Codex review — assignee lookup, GraphQL vars, status checks

Iteration 1 fixes (8 issues from Codex review):
- tracker-linear: remove invalid assigneeDisplayName, resolve assignee
  by display name to ID via users query after creation
- tracker-linear: add HTTP status code check in linearQuery
- tracker-linear: throw on missing workflow state in updateIssue
- scm-github: use GraphQL variables ($owner, $name, $number) in
  getPendingComments instead of string interpolation
- scm-github: guard against empty comment nodes in review threads
- scm-github: use mergeStateStatus in getMergeability (BEHIND, BLOCKED)
- tracker-github: default description to "" in createIssue

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

* fix: address PR review + lint — error context, GraphQL vars, mergeability

- scm-github/tracker-github: wrap gh() errors with command context + cause
- scm-github: use GraphQL variables ($owner, $name, $number) in
  getPendingComments to prevent injection
- scm-github: guard against empty review thread nodes
- scm-github: incorporate mergeStateStatus (BEHIND/BLOCKED) in getMergeability
- tracker-linear: add identifier vs UUID comment in updateIssue
- Fix ESLint preserve-caught-error violations
- Remove unused mockGhRaw in scm-github tests

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

* fix: add pagination to getAutomatedComments and increase thread limit

- getAutomatedComments: add --paginate flag to REST API call to fetch
  all review comments beyond the default 30-item first page
- getPendingComments: increase GraphQL reviewThreads limit from 100 to
  250 (GitHub's max for connections)

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

* fix: use per_page=100 instead of --paginate for JSON-safe pagination

Replace --paginate with -F per_page=100 in getAutomatedComments to
avoid concatenated JSON arrays that break JSON.parse on multi-page
responses. 100 is GitHub's max per_page value.

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

* fix: filter out resolved threads in getPendingComments

The method name implies it should only return unresolved/pending review
threads. The isResolved data was already fetched via GraphQL but was not
being filtered on, causing resolved threads to be included in results.

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

* test: add comprehensive tracker-linear test suite (53 tests)

Covers all Tracker interface methods: getIssue, isCompleted, issueUrl,
branchName, generatePrompt, listIssues, updateIssue, createIssue.
Mocks node:https request to simulate Linear GraphQL API responses.
Tests state mapping, error handling, assignee/label resolution, and
GraphQL variable injection.

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

* fix: address 6 bugbot review comments on PR #4

- GraphQL reviewThreads(first: 250) → first: 100 (GitHub max)
- noConflicts false when mergeable is UNKNOWN (not just CONFLICTING)
- updateIssue now handles labels and assignee (not just state/comment)
- Add res.on("error") handler in linearQuery to prevent crashes
- createIssue returns only actually-applied labels, not all requested
- Tests added/updated for all fixes (144 total across 3 plugins)

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

* fix: make Linear updateIssue labels additive to match GitHub behavior

Linear's issueUpdate replaces all labels, while GitHub's --add-label is
additive. Now fetches existing label IDs first and merges with new ones
before sending to issueUpdate, ensuring consistent behavior across both
tracker implementations.

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

* fix: default listIssues to open state in tracker-linear

When no state filter is specified, tracker-github defaults to "open"
but tracker-linear was returning all issues. Now defaults to excluding
completed/canceled issues, matching tracker-github behavior.

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

* feat: add Composio SDK support to tracker-linear

Allow users with a COMPOSIO_API_KEY to use the Linear tracker without
a separate LINEAR_API_KEY. The plugin auto-detects which key is available
and routes through either the direct Linear GraphQL API or Composio's
LINEAR_RUN_QUERY_OR_MUTATION tool. All existing queries and response
parsing are reused via a GraphQLTransport abstraction.

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

* fix: address 2 bugbot review comments on PR #4

1. getCIChecks now throws on error instead of silently returning [],
   preventing a fail-open where CI appears healthy when we can't fetch
   check status. getCISummary catches the error and returns "failing".

2. Handle GitHub's UNSTABLE mergeStateStatus (required checks failing)
   as a merge blocker in getMergeability.

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

* fix: prevent unhandled promise rejection in Composio timeout race

When the timeout wins Promise.race in the Composio transport, the
resultPromise is left without a rejection handler. If the SDK call
later rejects, it becomes an unhandled promise rejection that crashes
Node.js 20+ with --unhandled-rejections=throw.

Attach no-op .catch() to both resultPromise and timeoutPromise before
the race so whichever promise loses has its rejection silently handled.

Also adds plugin integration tests (core -> real plugins -> mocked gh CLI).

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

* fix: chain error cause in getCIChecks and fix core build

- getCIChecks catch now preserves the original error via { cause: err }
  instead of discarding it, matching the pattern used by the gh() helper
- Add tsconfig.build.json to core that excludes __tests__/ from build
  compilation, preventing TS5055 errors from circular workspace deps
  (integration tests import plugin packages that depend back on core)

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

* fix: remove circular devDeps, address bugbot comments

- Remove plugin devDependencies from core/package.json that created a
  circular dependency (core -> plugins -> core), breaking CI build order
- Document in_progress state as intentional no-op in tracker-github
  updateIssue (GitHub Issues only supports open/closed)
- Remove @composio/core optional peerDependency from tracker-linear;
  the dynamic import() already handles the missing package gracefully,
  and the peer dep was pulling composio + transitive deps into lockfile

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

* feat: add real integration test for tracker-linear against Linear API

Creates a test that exercises the full tracker-linear plugin lifecycle
against the real Linear API — createIssue, getIssue, isCompleted,
listIssues, updateIssue (comment, close, reopen), generatePrompt,
branchName, issueUrl. Each run creates a throwaway test issue and
trashes it in cleanup. Skipped when LINEAR_API_KEY is not set.

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

* ci: pass LINEAR_API_KEY and LINEAR_TEAM_ID to integration tests

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

* feat: support both LINEAR_API_KEY and COMPOSIO_API_KEY in integration test

The tracker-linear integration test now runs with either credential:
- LINEAR_API_KEY: direct Linear API (full cleanup via trash)
- COMPOSIO_API_KEY: via Composio SDK (cleanup falls back to closing)

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

* fix: don't pass COMPOSIO_API_KEY to CI integration tests

When both LINEAR_API_KEY and COMPOSIO_API_KEY are set, the plugin
prefers the Composio transport which requires @composio/core SDK.
The SDK isn't installed in CI, so use the direct LINEAR_API_KEY
transport which has no extra dependencies.

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

* fix: use import.meta.url in vitest config, default createIssue description

- Replace __dirname with import.meta.url-derived dirname in ESM config
- Default createIssue description to "" for defensive safety

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

* fix: map CANCELLED and ACTION_REQUIRED CI conclusions to failed

Terminal check conclusions (CANCELLED, ACTION_REQUIRED) were falling
through to "pending", causing the lifecycle manager to wait forever.
Also changed the default else branch to "failed" (fail-closed).

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 15:45:51 +05:30
prateek 4b6d62d142
feat: agent plugins, OpenCode plugin, integration tests, CI (#5)
* feat: implement agent plugins (claude-code, codex, aider)

Implement the Agent interface from @agent-orchestrator/core for three
AI coding tools, enabling the orchestrator to launch, detect activity,
and introspect agent sessions.

Claude Code plugin (full implementation):
- JSONL session introspection (summary, session ID, cost, last message type)
- Activity detection via terminal output pattern matching
- Process detection via tmux pane TTY → ps lookup chain
- Launch command generation with permissions/model/prompt support

Codex and Aider plugins (functional stubs):
- Launch command generation with tool-specific flags
- Process detection via TTY lookup
- Stub introspection (no JSONL support yet)

Closes INT-1329

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

* fix: address PR review — shell escaping, activity detection, tests

- Replace JSON.stringify with POSIX shell escaping (single quotes) for
  all agent launch commands to prevent command injection
- Remove `unset CLAUDECODE &&` shell syntax from getLaunchCommand (breaks
  execFile); moved to getEnvironment as empty string
- Fix detectActivity returning "exited" for empty pane when process is
  confirmed alive — now returns "idle"
- Tighten BLOCKED_PATTERNS to specific error framing (^Error:, ENOENT:,
  APIError:, etc.) to avoid false positives on code output
- Check blocked patterns before input patterns to prevent "EACCES:
  permission denied" matching INPUT_PATTERNS on the word "permission"
- Remove pgrep fallback in findClaudeProcess (could match wrong session)
- Fix extractCost double-counting: prefer costUSD, fall back to
  estimatedCostUsd only when costUSD is absent
- Trim ACTIVE_PATTERNS to stable indicators only
- Add comprehensive vitest tests (127 tests across 3 plugins)

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

* fix: address Codex review + lint fixes

- Use `ps -eo pid,tty,args` instead of `comm` for process detection so
  CLI wrappers (node, python) are correctly identified
- Coerce PID from handle.data with Number() + isFinite() to handle
  string-serialized PIDs
- Fix token double-counting: prefer `usage` object, only fall back to
  flat `inputTokens`/`outputTokens` when `usage` is absent
- Add `--` before Codex prompt to prevent prompts starting with `-`
  from being parsed as CLI flags
- Check blocked patterns before input patterns so "EACCES: permission
  denied" isn't misclassified as waiting_input
- Fix lint: merge duplicate imports, strict equality, remove non-null
  assertions
- Format with prettier

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

* fix: multi-pane tmux, EPERM handling, strict process matching

Codex review iteration 1 fixes:
- Iterate all tmux pane TTYs instead of only the first — prevents
  false "exited" in multi-pane sessions
- Handle EPERM from process.kill(pid, 0) as "process exists" — EPERM
  means the process is alive but we lack permission to signal it
- Use word-boundary regex for process name matching — prevents false
  positives on similar names like "claude-code" or paths containing
  the substring
- Add tests for EPERM handling, multi-pane detection, and strict
  name matching (134 tests total)

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

* fix: exclude next-env.d.ts from eslint

Auto-generated Next.js type file triggers triple-slash-reference rule.

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

* fix: address Bugbot review — JSON.parse guard, detectActivity pattern priority

- Guard parseJsonlFile against non-object JSON values (null, arrays,
  primitives) that would cause TypeError in downstream extractors
- Split null output (non-tmux → "active") from empty output (tmux → "idle")
  so non-tmux runtimes don't falsely report idle
- Reorder pattern matching: idle before blocked so stale errors in the
  tmux scrollback don't mask an idle prompt

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

* refactor: extract shellEscape to @agent-orchestrator/core

Move the duplicated POSIX shell-escaping utility from all three agent
plugins into packages/core/src/utils.ts and re-export from the package
entry point. Plugins now import { shellEscape } from the shared package.

Addresses Bugbot review comment on PR #5.

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

* fix: add execFileAsync timeouts, use satisfies for plugin exports

- Add { timeout: 30_000 } to all execFileAsync calls (tmux, ps) per
  CLAUDE.md convention to prevent hanging on stuck processes
- Replace `const plugin: PluginModule<Agent>` with inline
  `export default { ... } satisfies PluginModule<Agent>` to preserve
  narrow literal types on manifest fields

Addresses Bugbot review comments on PR #5.

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

* refactor: rename introspect → getSessionInfo, detectActivity uses JSONL

- Rename Agent.introspect() → getSessionInfo(), AgentIntrospection → AgentSessionInfo
- Claude Code detectActivity: replace tmux screen-scraping with JSONL-based
  detection (file mtime + last entry type)
- Remove terminal pattern constants (ACTIVE_PATTERNS, IDLE_PATTERNS, etc.)
- Update all tests: JSONL-based activity tests, renamed introspect blocks
- waiting_input/blocked states deferred to hooks-based implementation (#16)

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

* feat: add OpenCode agent plugin, integration tests, and CI workflow

- Add agent-opencode plugin with getLaunchCommand, detectActivity,
  isProcessRunning, getSessionInfo (27 unit tests)
- Add integration test suite for all 4 agents (claude-code, codex,
  aider, opencode) using real binaries in tmux sessions
- Add GitHub Actions CI workflow for integration tests
- Add test:integration script to root package.json

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

* fix: update core test mocks — introspect → getSessionInfo + isProcessing

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

* fix: address Bugbot review — Windows paths, bytesRead, hardcoded paths

- toClaudeProjectPath: handle Windows backslashes and drive letters
- readLastJsonlEntry: use bytesRead to avoid null character corruption
- Remove duplicate next-env.d.ts eslint ignore entry
- Remove hardcoded developer-specific binary paths from integration tests
- Add isProcessing limitation comments with issue references (#17-19)

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 11:28:42 +05:30
prateek 0c535c98d7
fix: CI handles Next.js build separately, fix web tsconfig (#14)
* fix: CI workflow handles Next.js build separately, fix web tsconfig rootDir

- Remove rootDir from web tsconfig (conflicts with Next.js generated .next/types)
- Include .next/types/**/*.ts in web tsconfig
- CI: build non-web packages first, web build allowed to soft-fail
- CI: typecheck excludes web (typechecked by next build)
- CI: test excludes web build dependency

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

* fix: remove || true from web build — bugbot caught it, web errors should fail CI

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

* fix: override rootDir in web tsconfig to "." for Next.js compat

Next.js generates .next/types/ files that must be under rootDir.
Base tsconfig sets rootDir to "src", web needs "." to include both
src/ and .next/types/.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:41:58 +05:30
Prateek c8061ce03f chore: add ESLint, Prettier, CI workflow, and comprehensive CLAUDE.md conventions
- ESLint flat config with typescript-eslint strict rules
- Prettier config (double quotes, semicolons, 2-space indent)
- GitHub Actions CI: lint + typecheck + test on PRs
- Cursor BugBot config (.cursor/BUGBOT.md)
- Comprehensive CLAUDE.md with code conventions, security rules,
  plugin patterns, and common mistakes to avoid
- Fix unused param lint error in plugin-registry.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:01:52 +05:30