The shared gh wrapper now extracts PR URLs with a regex instead of embedding a literal github URL, so the old codex assertion was stale and broke CI on PR #1300.
Represent missing activity probes as first-class signal states so lifecycle inference only treats valid idle evidence as proof. This prevents false stuck transitions, keeps API/UI lifecycle truth aligned, and makes root monorepo verification deterministic by serializing recursive build and typecheck.
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>
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>
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>
* 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>
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
* 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).
- Fix autoDetectProject path matching: expand ~ before comparing project
paths to cwd, so `path: ~/my-repo` matches `/Users/user/my-repo`
- Fix addProjectToConfig: detect duplicate directory names and auto-suffix
instead of silently overwriting existing project entries
- Fix agent detect() in all 4 plugins: replace `which` (Unix-only) with
direct `--version` invocation for cross-platform compatibility
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Change != null to !== undefined && !== null in caller-context.ts and
session-manager.ts (3 locations) to satisfy eqeqeq lint rule
- Add displayName field to all 4 agent plugin test manifest assertions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace require() with execFileSync in all 4 agent plugin detect()
functions — fixes ReferenceError in ESM-only environments
- Remove non-existent -p flag from orchestrator prompt spawn/batch-spawn
docs — prevents orchestrator agent from generating broken commands
- Return actual port from runStartup() and pass to register() — fixes
running.json storing wrong port when auto-escalation picks a free port
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reduces onboarding to `npm install -g @composio/ao && ao start`.
- Absorb init and add-project logic into `ao start` with auto-config
creation, environment detection, and project type detection
- Add single-instance tracking via running.json with already-running
interactive menu (human) and info+exit (agent)
- Add caller context detection (human/orchestrator/agent) via TTY and
AO_CALLER_TYPE env var
- Add plugin-based agent runtime detection — no hardcoded binary paths,
each plugin exports detect() and displayName
- Simplify `ao spawn` to just `ao spawn <issue>` with auto-detected
project (backward compat: `ao spawn <project> <issue>` still works)
- Set AO_CALLER_TYPE, AO_PROJECT_ID, AO_CONFIG_PATH, AO_PORT env vars
on all spawned sessions (worker, orchestrator, restore)
- Add `ao config-help` subcommand for config schema reference
- Deprecate `ao init` to thin wrapper, fully remove `ao add-project`
- Register/unregister in running.json on start/stop
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a 30-second TTL cache for session file lookups so that
getActivityState, getSessionInfo, and getRestoreCommand called in the
same refresh cycle reuse the result instead of re-scanning
~/.codex/sessions/ independently.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use the Codex session file's mtime as a proxy for agent activity.
Codex continuously appends to its rollout JSONL while working, so:
- recently modified file → active
- stale file (past threshold) → idle
- process not running → exited
This replaces the previous null return with real activity detection,
using the same threshold constant (DEFAULT_READY_THRESHOLD_MS = 5min)
as the Claude Code plugin.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Wrap entire handleApprovalRequest body in try/catch so a throwing
"approval" listener doesn't cause an unhandled promise rejection
- Remove wasClosedBefore variable that was always false (the closed
check on entry guarantees it), simplifying the connect catch block
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract duplicated approval-policy and model/reasoning flag logic from
getLaunchCommand and getRestoreCommand into shared appendApprovalFlags
and appendModelFlags helpers. Single source of truth for flag mapping.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move this.emit("error", err) after the pending-rejection loop in
handleProcessError, matching handleProcessExit's order. Without this,
emit("error") with no listeners throws synchronously, skipping
cleanup of pending requests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three tests relied on the empty default stream coincidentally matching
expected values instead of actually exercising the streaming parser.
Added setupMockStream(content) and stronger assertions to verify the
parser correctly handles missing model, missing token events, and
missing threadId.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add "jsonrpc": "2.0" to all JSON-RPC requests, notifications, and
approval responses for spec compliance
- Place flags before positional threadId in resume command for CLI
parser compatibility (codex resume --flags <threadId>)
- Add test assertions for jsonrpc field and flag ordering
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use lstat instead of stat in collectJsonlFiles so symlinks to
directories are never followed (isDirectory returns false for symlinks)
- Add MAX_SESSION_SCAN_DEPTH=4 cap as defense-in-depth
(Codex uses YYYY/MM/DD structure, max 3 levels)
- Update tests to mock lstat for directory checks separately from stat
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove additive cached_tokens/reasoning_tokens from cost calculation
(they are subsets of input_tokens/output_tokens per OpenAI API convention)
- Wrap entire connect() body in try/catch so connecting flag resets if
spawn() throws synchronously (EMFILE, ENOMEM)
- Add test for spawn-throws-synchronously recovery
- Update cost assertions to reflect correct non-additive token counting
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Reset closed flag after failed connect() so client can retry
- Wrap binary resolve guard in try/finally to clear on rejection
- Fix duplicate import and unused variable lint errors in tests
- Add test: client retries connect after transient handshake failure
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixes 6 issues flagged by Cursor Bugbot on PR #188:
1. sessionFileMatchesCwd reads only first 4 KB via open()+read() instead of
loading entire rollout file into memory (Medium)
2. Binary resolution race condition: added promise guard to prevent
concurrent resolveCodexBinary() calls (Low)
3. Concurrent connect() calls: added connecting guard to prevent
orphaned child processes (High)
4. connect() failure cleanup: wrap initialize() in try/catch that
calls close() on failure (Medium)
5. readline cleanup on process exit/error: close readline in both
handleProcessExit and handleProcessError (Medium)
6. stderr pipe drain: call stderr.resume() to prevent child blocking
when stderr buffer fills (High)
All 177 tests pass, typecheck clean.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>