Commit Graph

26 Commits

Author SHA1 Message Date
Priyanshu Choudhary 1d8c8f75ca docs: document cross-platform abstractions and reflect Windows support
Adds docs/CROSS_PLATFORM.md as the canonical reference for cross-platform
development: the "Golden Rule" (no raw process.platform === "win32" — use
isWindows() and the helpers in platform.ts), a full inventory of every
platform helper (platform.ts, path-equality, windows-pty-registry,
pty-client, sweepWindowsPtyHosts, validateSessionId, resolvePipePath,
setupPathWrapperWorkspace, activity-state helpers, AO_SHELL/AO_BASH_PATH),
the EPERM-vs-ESRCH gotcha when probing processes, PowerShell-vs-bash
differences, IPv6 localhost stalls, agent-plugin specifics, and a 10-point
pre-merge checklist.

Updates internal docs (CLAUDE.md, AGENTS.md, docs/ARCHITECTURE.md,
docs/DEVELOPMENT.md, CONTRIBUTING.md, .github/copilot-instructions.md,
.cursor/BUGBOT.md, packages/core/README.md, packages/plugins/runtime-tmux/
README.md, packages/core/src/prompts/orchestrator.md, ARCHITECTURE.md) to
remove tmux-only / POSIX-only claims, point at the new doc, and (in
docs/ARCHITECTURE.md) describe the Windows runtime architecture: pty-host
helper, named-pipe protocol, registry, sweep, mux WS Windows branch.

Updates user-facing docs (README.md, SETUP.md, docs/CLI.md) to split
prerequisites by OS (no tmux on Windows), reflect that ao doctor and
ao update work on Windows, and note that power.preventIdleSleep is a
no-op on Linux and Windows.

Updates the agent-orchestrator skill (skills/agent-orchestrator/SKILL.md
and references/config.md) so it advertises Windows support, drops tmux
from the required-bins list, and gives the right Windows guidance for the
"spawn tmux ENOENT" error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 01:16:37 +05:30
Priyanshu Choudhary 41d44af32b Merge remote-tracking branch 'origin/main' into feat/windows-platform-adapter
# Conflicts:
#	packages/cli/src/commands/start.ts
#	packages/core/src/session-manager.ts
#	packages/plugins/agent-claude-code/src/index.ts
2026-05-04 15:47:14 +05:30
Harshit Singh Bhandari caa7f60a4b
refactor(spawn): plugin-owned preflight + collapse project resolution (#1622)
* feat(core): add PreflightContext + optional preflight() to plugin interfaces

Foundation for PR 2 of the ao spawn refactor: lets plugins own their own
prerequisites instead of the CLI hardcoding 'if runtime === tmux check
tmux' / 'if tracker === github check gh auth' switches.

PreflightContext describes intent (willClaimExistingPR, role) rather
than CLI flag names, so plugins never learn about flags. New flags map
to new intent fields only when a plugin actually needs them.

Adds preflight?(ctx) as an optional method on Runtime, Agent, Workspace,
Tracker, SCM. Backwards-compatible: existing plugins keep working
unchanged. Subsequent commits move checkTmux into runtime-tmux and
checkGhAuth into the github plugins, then update spawn.ts to iterate
selected plugins instead of switching on plugin names.

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

* feat(plugins): implement preflight() in runtime-tmux + tracker-github + scm-github

Each plugin now owns its own prerequisite checks (tmux binary, gh auth)
behind the optional PluginModule preflight() contract added in the
previous commit. The CLI no longer needs to know which plugin needs
which tool — it just iterates the selected plugins.

- runtime-tmux: checks 'tmux -V' and throws with platform-appropriate
  install hint (brew / apt / dnf / WSL)
- tracker-github: checks 'gh --version' and 'gh auth status'
  unconditionally (tracker is exercised on every spawn that has an
  issueId AND on lifecycle polling for issue closure)
- scm-github: same gh auth checks but only when the spawn will exercise
  PR-write paths — gates on context.intent.willClaimExistingPR

Subsequent commit refactors the CLI to iterate plugins instead of
hardcoded 'if runtime === tmux' switches.

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

* refactor(cli): make ao spawn iterate plugin preflight, collapse project resolution

Three small changes bundled because they all touch spawn.ts:

1. Plugin-iterating preflight: replaces the hardcoded
   'if runtime === tmux check tmux' / 'if tracker === github check gh
   auth' switches in runSpawnPreflight with a 4-line loop that walks the
   selected plugins and calls each one's optional preflight(). Plugin
   internals are no longer leaked into the CLI; new plugins only need to
   declare their own preflight.

2. Project-resolution collapse: the prefix/no-prefix and issue/no-issue
   paths previously had three near-duplicate code blocks each with its
   own try/catch around autoDetectProject. Replaced by one
   resolveProjectAndIssue() helper that uses resolveSpawnTarget's
   fallback parameter — caller wraps in a single try/catch.

3. Micro-deletes: drop the unused 'return session.id' in spawnSession
   (callers already ignore it; the SESSION=<id> stdout line is the
   scriptable contract). Drop checkTmux/checkGhAuth from lib/preflight.ts
   (now in their respective plugins) along with their orphaned tests.

LOC: roughly net-zero. Wins are structural — adding runtime-podman /
tracker-jira / scm-bitbucket no longer requires editing spawn.ts.

Pre-existing start.test.ts 'stop command' failures are unrelated (verified
on upstream/main bare).

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

* perf(plugins): dedupe gh-auth check across tracker-github + scm-github

Address greptile P2 on PR #1622: when a project has both tracker:
github and scm: github with --claim-pr, both plugin preflights ran
'gh --version' + 'gh auth status' independently — 4 execs where 2
suffice, and two identical error messages on failure.

Add memoizeAsync(key, fn) to core (process-scoped Promise cache) and
have both github plugins share the key 'gh-cli-auth'. Second caller
hits the in-flight (or resolved) promise — zero extra subprocess
overhead, one error on failure.

Caches both successes and rejections: failed checks should never
re-run within a process (cache dies with the CLI, user fixes the
underlying issue and re-invokes).

5 unit tests for memoizeAsync covering: single-fire dedup, value
identity, distinct keys, rejection caching, concurrent in-flight dedup.

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

* refactor(spawn): collect-all preflight + per-plugin tests + key-namespacing docs

Address self-review feedback on PR #1622:

1. **Collect-all preflight** (spawn.ts): runSpawnPreflight previously
   aborted at the first plugin's failure, so a user with multiple broken
   prereqs (tmux missing AND gh logged out) had to fix-and-retry to
   discover the second one. Now collects every plugin's error and
   reports them together ("2 preflight checks failed:\n  1. ...\n
   2. ..."). Single-failure path is unchanged — that error throws as-is
   without the wrapper. Test added: 'collects every plugin's preflight
   failure into one combined error'.

2. **Drop redundant workspace literal fallback** (spawn.ts):
   DefaultPluginsSchema in core/config.ts applies .default("worktree")
   to workspace, same as runtime/agent. The literal '?? "worktree"'
   was asymmetric defensive theater — dropped to match the runtime/agent
   form.

3. **memoizeAsync key-namespacing convention** (process-cache.ts):
   Added a JSDoc section documenting that two callers using the same
   key get shared state (intentional for cross-cutting checks like
   gh-cli-auth, dangerous for plugin-internal caching). Recommends
   namespacing plugin-internal keys as 'plugin-name:thing'.

4. **Per-plugin preflight unit tests**:
   - runtime-tmux: tmux-present resolves; tmux-missing throws with
     platform-specific install hint (verified per-platform branch)
   - tracker-github: happy path, gh-not-installed, gh-not-authenticated
   - scm-github: no-op when willClaimExistingPR=false (zero gh calls),
     full check when true, plus install/auth failure branches

   Process cache cleared in beforeEach so each test starts fresh.
   Required exporting _clearProcessCacheForTests from core/index.ts
   (matches existing _testUtils pattern in gh-trace.ts).

Pre-existing start.test.ts 'stop command' failures unchanged
(verified on bare upstream/main).

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

* fix(test): collapse duplicate @aoagents/ao-core import in tracker-github test

eslint no-duplicate-imports caught it on CI — combined the value and
type-only imports into one statement.

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 14:03:26 +05:30
i-trytoohard ef8ac42dd4
chore: release 0.4.0 (#1625)
* chore: release 0.4.0

Consume 33 changesets across the linked package group. All public
packages bumped to 0.4.0 and published to npm.

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

* test(agent-codex): bump package-version assertion to 0.4.0

Release gate test was still asserting 0.3.0 after the 0.4.0 bump.

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

* chore: align CHANGELOG headers with @aoagents npm scope

The H1 of every package CHANGELOG.md still read @composio/* from
before the npm scope rename. Body entries that historically reference
@composio/* are left intact — they document what was true at the time
of those releases.

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

---------

Co-authored-by: Prateek <karnalprateek@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 06:57:24 +05:30
yyovil 3aa66d1a51 fix(runtime): harden dashboard launch shutdown 2026-05-02 02:53:35 +05:30
Copilot e548584130
chore: align workspace package.json versions with npm registry (#1587)
* Initial plan

* chore: bump all workspace package versions from 0.2.5 to 0.3.0

Agent-Logs-Url: https://github.com/ComposioHQ/agent-orchestrator/sessions/da5b2769-e7d4-4d08-a60c-bd5f695d1ca7

Co-authored-by: harshitsinghbhandari <212377671+harshitsinghbhandari@users.noreply.github.com>

* fix: update package-version test to expect 0.3.0

Agent-Logs-Url: https://github.com/ComposioHQ/agent-orchestrator/sessions/ad61e33e-417f-4482-b06c-0b60826b7f2d

Co-authored-by: harshitsinghbhandari <212377671+harshitsinghbhandari@users.noreply.github.com>

* chore: revert non-ao version bumps

Only @aoagents/ao drives the 'ao update available' prompt
(packages/cli/src/lib/update-check.ts compares against the
@aoagents/ao registry version and reads the local @aoagents/ao
package.json). All other workspace bumps are unnecessary.

* chore: align workspace versions with npm registry

Catch up source-of-truth package.json versions to what is already
published on npm. The registry reflects releases done via Changesets;
the in-tree files had drifted to 0.2.5.

  0.2.5 -> 0.3.0: cli, core, web, agent-aider, agent-claude-code,
                  agent-codex, agent-opencode, notifier-composio,
                  notifier-desktop, notifier-slack, notifier-webhook,
                  runtime-process, runtime-tmux, scm-github,
                  terminal-iterm2, terminal-web, tracker-github,
                  tracker-linear, workspace-clone, workspace-worktree
  0.2.5 -> 0.2.6: notifier-discord, notifier-openclaw, scm-gitlab,
                  tracker-gitlab
  0.1.0 -> 0.1.1: agent-cursor

Also updates agent-codex package-version.test.ts to expect 0.3.0.

* test(cli): use future version in update-check cache test

The cache-fresh test assumed getCurrentVersion() returned a value
older than the cached latestVersion. With packages/ao now at 0.3.0
and resolvable from cli via pnpm's hoisted store at test time,
getCurrentVersion() returns 0.3.0, so isOutdated against a cached
latestVersion of 0.3.0 is false and the assertion fails.

Use 99.0.0 in the cache so the comparison stays meaningful regardless
of the current installed version.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: harshitsinghbhandari <212377671+harshitsinghbhandari@users.noreply.github.com>
Co-authored-by: harshitsinghbhandari <claudeagain@pkarnal.com>
2026-05-01 15:31:04 +05:30
Dhruv Sharma a8bc746947
feat(core): opt-in gh CLI tracer + scm/tracker migration (Phase A1a) (#1238)
* feat(core): add opt-in gh CLI tracer and migrate scm/tracker plugins

Introduces execGhObserved() in @aoagents/ao-core: a thin wrapper around
execFile("gh", ...) that writes a JSONL trace row to $AO_GH_TRACE_FILE
on both success and failure. Captures status line, HTTP status, ETag,
rate-limit headers, duration, stdout/stderr byte counts, exit code, and
signal. No-op when the env var is unset, so default behavior is
unchanged.

Migrates three call sites to the observer:
- scm-github/graphql-batch.ts — PR-list guard, commit-status guard,
  GraphQL batch query
- scm-github/index.ts — gh() and ghInDir() helpers
- tracker-github/index.ts — internal gh() helper

This is Phase A1a of experiments/PLAN.md: tracer infrastructure +
migration. The full GhRunner contract (Promise<GhResult>,
GhRunnerError.ghResult on reject, body capture, redaction, 64 KB cap)
lands in A1b along with the scorecard baseline.

Also adds experiments/ reference docs: the v2.3 plan, the gh-CLI call
catalog, two ETag verification writeups, and a trace harness + summary
script.

* docs(experiments): add A1a validation status and A1b blockers

Record the five A1b pre-freeze blockers surfaced by Adil's 1,487-row
baseline and an independent drill run: graphql-batch missing -i,
extractOperation flag mis-bucketing, analyzer not segmenting burn by
reset window, CLI-subcommand opacity (GH_DEBUG=api stderr vs coarse
/rate_limit bracket — not equivalent), and sessionId/projectId not
threaded through plugin callsites. Note bare gh() helper cleanup as
known-open follow-up.

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

* fix(tracer): close A1b blockers 1-4 — graphql-batch visibility, operation naming, analyzer segmentation

- Add -i flag to executeBatchQuery in graphql-batch.ts and split HTTP
  headers from JSON body before parsing, making all gh.api.graphql-batch
  rows visible to status and rate-limit analysis (was 186 invisible rows)
- Fix extractOperation() in gh-trace.ts to walk past -* flags before
  picking the operation segment, eliminating the gh.api.--method bucket
- Add per-reset-window burn segmentation to both analyzers so runs
  straddling a reset boundary produce per-window deltas instead of a
  single invalid cross-reset delta
- Add experiment scripts: analyze-trace.mjs (deep trace analysis) and
  drill-tracer.mjs (standalone tracer exerciser)
- Document Gap 1 decision in PLAN.md: accept CLI subcommands as opaque
  for A1, bracket A2 runs with /rate_limit snapshots for coarse burn
- Add progress timeline to PLAN.md showing A→B→C track dependencies

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

* docs(experiments): add A2 baseline matrix runbook

Practical execution plan for the Phase A2 scenario x scale x topology
matrix: 7 priority cells, per-cell procedure, /rate_limit bracketing
for Gap 1 subcommand burn, output format for baseline.md, and the
scorecard that gates Track B.

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

* fix(tracer): guard stderr/stdout against undefined, bound operation cardinality

Addresses code review findings:
1. Guard Buffer.byteLength and parseIncludedHttpResponse against
   undefined stderr/stdout — fixes 48 SCM test regressions where
   mocked execFile paths don't populate stderr
2. extractOperation() now takes only the first path segment of REST
   URLs (e.g. "repos" from "repos/acme/repo/pulls/123/...") to keep
   operation bucket cardinality bounded and stable across runs
3. Fix A2 runbook /rate_limit snapshots to produce valid JSON using
   jq's now|todate instead of appending raw timestamp
4. Add blocker 5 dependency to runbook prereqs and per-session cells

All 140 SCM tests pass (0 failures).

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

* docs(experiments): add rate-limiting research artifacts

Baseline measurements, discussion notes, benchmark harness spec,
and updated master plan from two independent trace runs at 5-6 sessions.

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

* feat(experiments): add benchmark harness for GH rate-limit measurement

Three modes: setup (spawn sessions, wait for PRs), measure (trace API
calls over a fixed window, produce scorecard), report (recompute from
existing trace). Node.js stdlib only, shells out to ao CLI and gh CLI.

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

* fix(scm-github): handle 304 Not Modified in ETag guard catch blocks (B1)

`gh api -i` exits code 1 on HTTP 304 responses, causing the catch blocks
in checkPRListETag and checkCommitStatusETag to assume the resource changed
and trigger unnecessary GraphQL batch queries every poll cycle.

Fix: inspect stdout/stderr in the catch block for the 304 status line before
falling back to "assume changed". Also unifies the 304 detection regex to
handle HTTP/1.1, HTTP/2, and HTTP/2.0 status lines, and adds rateLimit
introspection to the batch GraphQL query.

Benchmark result (quiet-steady, 5 sessions, 15 min):
- GraphQL points/hr: 260/5,000 (5%) — down from 820–1,416 pre-fix
- ETag guard 304 rate: 100%
- GraphQL batch calls during measurement: 0

Also fixes the benchmark harness to create placeholder tmux sessions with a
claude symlink so the lifecycle actually polls sessions instead of
short-circuiting to "killed".

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

* docs(experiments): update plan and notes with B1 benchmark results

B1 fix validated at 5, 10, and 20 sessions in quiet-steady state:
- 5 sessions: 260 GraphQL pts/hr (5% budget)
- 10 sessions: 640 pts/hr (13%)
- 20 sessions: 680 pts/hr (14%) — sub-linear scaling confirmed
- 50-session projection: ~800-1000 pts/hr (16-20%)
- ETag guard 304 rate: 100% at all scale points
- graphql-batch calls: 0 during measurement at all scale points

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

* feat(core): log gh wrapper invocations for D1

* fix(core): preserve wrapper logging for dash-prefixed gh args

* feat(core): add gh wrapper cache for PR discovery and issue context (D4)

Add read-through caching to the ~/.ao/bin/gh wrapper, targeting the two
largest agent-side waste buckets identified in D4 analysis:

1. PR discovery (gh pr list --head): infinite TTL for positive results.
   598 calls → ~10 per 10-session run (98% reduction).
2. Issue context (gh issue view): 300s TTL.
   75 calls → ~20 per 10-session run (73% reduction).

The wrapper now caches successful read-only responses in
$AO_DATA_DIR/.ghcache/$AO_SESSION/ and serves them on subsequent
identical calls. Negative results (empty []) are never cached.
gh pr create populates the PR discovery cache immediately.

Also lifts PATH wrapper installation from individual agent plugins into
session-manager, making it universal for all agents including Claude Code:

- session-manager injects PATH + GH_PATH into every runtime.create()
- session-manager calls setupPathWrapperWorkspace() for all agents
- Removes duplicate buildAgentPath/setupPathWrapperWorkspace boilerplate
  from codex, aider, opencode, and cursor plugins

Includes D4 implementation plans in experiments/.

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

* docs(experiments): add full capacity discovery (5→50 sessions) and CI churn results

Complete scaling curve measured: 50 sessions uses only ~28% of GraphQL
budget with 100% ETag guard hit rate at every scale. Poll cycle lag
identified as first bottleneck (66s at 50 sessions vs 30s target).
CI churn benchmark shows ETag invalidation is a latency problem, not
a rate-limit problem (+9% GraphQL, +4.4x p50 latency).

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

* docs(experiments): record real-agent catastrophe and Track D handoff

5-real-agent run on todo-app exhausted GraphQL bucket in 31 min (~9572 pts/hr,
~37x quiet-steady at the same session count). AO polling consumed ~10 calls;
the rest came from agents themselves via the metadata-only ~/.ao/bin/gh
wrapper, which has no tracing. Captures findings, adds Track D (agent-side
gh consumption) plus B5 (migrate remaining bare gh callsites to
execGhObserved), and includes the runbook + benchmark scripts Adil will
build on for the cross-machine reproduction.

* feat(core): add cache-hit/miss tracing to gh wrapper (D4)

The wrapper trace now logs a cacheResult entry for every cacheable
command: hit, miss-stored, miss-negative, or miss-error. This makes
benchmark runs conclusive — you can count cache hits vs real gh calls
directly from the JSONL trace instead of inferring from rate-limit
deltas.

Bump wrapper version to 0.4.1.

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

* feat(scm-github): replace repo-scoped Guard 1 with PR-scoped ETag checks (D4)

Guard 1 now checks GET /repos/{owner}/{repo}/pulls/{number} per PR
instead of GET /repos/{owner}/{repo}/pulls?... per repo. This means:

- Only changed PRs flow into the GraphQL batch
- Unchanged PRs are served directly from the enrichment cache
- shouldRefreshPREnrichment returns a refresh plan (prsToRefresh +
  cachedResults) instead of a boolean

When 1 of 10 PRs changes, the old guard refreshed all 10 via GraphQL.
Now only the 1 changed PR is fetched; the other 9 are served from cache
at zero GraphQL cost.

Trade-off: more REST guard calls (1 per PR instead of 1 per repo), but
304 responses cost zero rate limit points.

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

* Forward AO_AGENT_GH_TRACE to session runtimes

* revert: remove PR-scoped ETag guards (Change 3)

Reverts 25ae6013. The per-PR Guard 1 added more REST calls (1 per PR
instead of 1 per repo) without meaningful GraphQL savings at 10-session
scale. Core REST delta went from 16 to 142 while GraphQL rate stayed
flat. The repo-scoped guard is sufficient for current workloads.

Preserves the subsequent 6fc64f4f commit (AO_AGENT_GH_TRACE forwarding).

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

* fix(core): use real gh binary in execGhObserved, bypass wrapper

execGhObserved() was calling bare "gh" which resolved to ~/.ao/bin/gh
(the wrapper) when that directory was in PATH. This caused:
- AO-side gh calls going through the agent wrapper
- All trace rows with aoSession=null polluting the agent trace
- Cache functions silently failing (no AO_SESSION in AO process)

Now strips ~/.ao/bin from PATH and resolves the real gh binary
(e.g. /opt/homebrew/bin/gh) at startup. Cached after first resolution.

AO process → execGhObserved → real gh → AO_GH_TRACE_FILE
Agent process → ~/.ao/bin/gh wrapper → AO_AGENT_GH_TRACE + cache

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

* fix(core): harden gh wrapper caching and agent-side tracing

Cache correctness:
- Include --json fields in cache key (prevents stale partial responses)
- Only cache stdout, not stderr (prevents warning contamination)
- Fix trailing newline inconsistency in PR discovery cache
- Support --key=value arg syntax for all cached flags
- Remove PR create cache pre-population (hardcoded fields, no JSON escaping)
- Log miss-write-failed when ao_cache_write fails (previously silent)

Agent trace improvements:
- Add operation field to invocation rows (gh.pr.list, gh.issue.view, etc.)
- Add durationMs, exitCode, ok to cache outcome rows
- Log passthrough for all non-cached code paths (pr/create, default case)
- Replace exec with child process in default case to enable post-call tracing

Bump wrapper version to 0.6.0.

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

* fix(runtime-tmux): re-export PATH after shell init to survive macOS path_helper

macOS zsh runs path_helper during shell startup which resets PATH,
wiping entries set via tmux new-session -e. This caused ~/.ao/bin
to be lost, so the gh/git wrappers were never intercepting agent
calls — no caching, no tracing, no metadata auto-updates.

Fix: send `export PATH=...` via send-keys after the shell has
initialized but before the launch command, ensuring PATH sticks.

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

* fix(runtime-tmux): use launch script for PATH re-export instead of send-keys

The previous send-keys approach sent 1000+ literal keystrokes for the
PATH value, which broke terminal input buffers and caused stuck quote
prompts. Instead, include the PATH export in the launch script file
which is executed directly — no terminal buffer issues.

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

* docs(experiments): add AO-side gh rate-limit trace report

5-session, 15-minute trace analysis with full call breakdown,
ETag guard effectiveness, anomaly investigation, and ranked
reduction opportunities.

Key findings:
- GraphQL at 41%/hr with 5 sessions (bottleneck at ~12 sessions)
- 47% of calls are individual REST fallbacks that batch should cover
- Review thread GraphQL calls (55/15min) can be folded into batch
- detectPR() and guard failures are working as designed

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

* docs(experiments): add AO rate-limit reduction plan with Step 1

Step 1: Remove individual REST fallback from determineStatus().
110 calls (65 pr view + 45 pr checks) eliminated per 15-min window.
Batch enrichment covers all PRs every 30s — fallback is unnecessary
insurance for an event that never occurred in real traces.

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

* experiments(m2): drop agent-trace gate for claude-code

Claude Code uses native PostToolUse hooks (.claude/settings.json), bypassing
the ~/.ao/bin/gh PATH wrapper, so AO_AGENT_GH_TRACE stays empty even when
Claude makes gh calls. The previous smoke gate required AGENT_ROWS>0 and
aborted every claude-code M2 batch at smoke.

- limit-finder.sh: add REQUIRE_AGENT_TRACE env + --no-require-agent-trace flag,
  gate the AGENT_ROWS integrity check behind it.
- m2-ab-run.sh: bump SMOKE_DURATION to 420s; auto-pass --no-require-agent-trace
  when AGENT=claude-code; simplify smoke_check to gate only on AO_ROWS>0
  (B1 lives in AO-side scm-github, measured by AO trace).

Follow-up tracked as task #39: instrument Claude's hook/tool path or document
that AO_AGENT_GH_TRACE does not cover Claude Code.

* feat(tracker-github): cache issue reads in-process (5 min TTL)

The lifecycle worker polls getIssue/isCompleted repeatedly for the same
issue across a session. Trace data from a 5-session tier-5 bench run
showed the same (repo, issue) pair fetched 64+ times with >97% duplicate
rate — ~744 of 4,059 AO gh calls in 10 minutes were redundant issue views.

Adds an in-process Map<string, CachedIssue> per createGitHubTracker()
instance, keyed by `${repo}#${id}`, TTL 5 min, bounded to 500 entries
(LRU evict-oldest on overflow).

- getIssue: read-through cache, populate on miss
- isCompleted: routes through getIssue (was a separate narrow gh call)
- updateIssue: invalidate the entry before mutating
- createIssue: unchanged, naturally populates via the existing getIssue
- Failures are not cached

Cache lives inside createGitHubTracker so each create() returns an
isolated cache (test isolation comes for free).

Expected reduction: ~744 → ~15 gh issue view calls per tier-5 run.

Tests: 41 existing + 10 new cache tests, all passing.

* feat(scm-github): cache 5 gh pr view callsites with per-method TTLs

The lifecycle worker repeatedly polls each PR for state, summary, reviews,
and review decision. Trace data showed gh pr view was the single largest
AO-side endpoint at 1,280 calls per 5-session tier-5 run with >97% duplicate
rate (e.g. PR #184 polled 86× for --json state alone in 11.5 minutes).

Adds an in-process per-instance cache inside createGitHubSCM(), keyed by
${owner}/${repo}#${prKey}:${method} so different field-sets stay isolated.
Per-method TTLs balance reduction against staleness on decision-influencing
fields:

- resolvePR: 60s (identity metadata only)
- getPRState: 5s
- getPRSummary: 5s (includes state)
- getReviews: 5s
- getReviewDecision: 5s

assignPRToCurrentUser, mergePR, and closePR each invalidate the entire PR
cache for that PR after the mutation, so AO never sees stale state from its
own writes. Failures are not cached.

getCIChecksFromStatusRollup and getMergeability are intentionally NOT cached
here — those need ETag-based revalidation, not blind TTL, and will land
separately.

Expected reduction: ~1,165 of ~1,280 gh pr view calls per tier-5 run.

Tests: 73 existing + 12 new cache tests, all 153 passing.

* feat(scm-github): cache CI checks, mergeability, pending comments, detectPR

Completes the AO-side hot-read caching alongside the prior PR view cache.
All use 5s TTL per the approved policy for decision-influencing fields —
well under one lifecycle poll cycle so state transitions are still seen
next pass.

- getCIChecks (gh pr checks): 5s TTL
- getMergeability (composite pr view + CI + state): 5s TTL on the composite
- getPendingComments (gh api graphql review threads): 5s TTL —
  ETag doesn't help on GraphQL per Experiment 2
- detectPR (gh pr list --head BRANCH): 5s TTL, POSITIVE-ONLY.
  Empty results are never cached so a freshly created PR is discovered
  on the very next poll. The branch-keyed cache entry is invalidated
  by mergePR/closePR alongside the number-keyed entries.

Combined with the prior PR view cache, covers the top 6 AO-side gh
operation categories that accounted for ~85% of calls in tier-5 traces.

Tests: 85 existing + 9 new cache tests, all 162 passing.

* experiments(m2): parse REPO from yaml before using it in banner

m2-ab-run.sh referenced $REPO in the header banner before parsing it,
causing 'unbound variable' abort under 'set -u'. Parse it right after
CONFIG_FILE is set.

* test(core): mock full Issue shape in plugin-integration cleanup tests

After tracker-github routed isCompleted() through getIssue() to share
the issue cache, these mocks needed the full Issue shape (number, title,
body, url, state, stateReason, labels, assignees) instead of the narrow
{state} shape that worked when isCompleted made its own --json state call.

* perf(scm-github): tune cache TTLs based on trace replay

Replayed feat run1 + main run2 tier-5 traces (4059 + 1748 rows, 38 min, 5
sessions each) against the shipped cache logic. Three TTLs were materially
under-tuned for the actual lifecycle poll cadence:

- detectPR:           5s → 30s   (was 0.5% hit rate; per-branch poll cadence
                                  is ~90s, so 5s caught nothing. 30s catches
                                  intra-cycle bursts when multiple sessions
                                  share a branch. Positive-only stays.)
- getReviewDecision:  5s → 10s   (within "10-30s TTL or ETag" policy)
- getPendingComments: 5s → 10s   (same policy class)

All three are still well under one poll cycle; freshness contract unchanged
in practice. Other TTLs (5s on state/CI/mergeability, 60s on resolvePR,
5min on issue) hit the targets they were set for and stay as-is.

Replay results before/after:
- feat run1:  53.7% → 57.8% reduction (2179 → 2345 hits of 4059 calls)
- main run2:  47.4% → 52.6% reduction
- Net: ~55% AO-side gh calls eliminated across both traces

Adds experiments/cache-replay.mjs — a counterfactual replay tool that
walks an execGhObserved JSONL trace and simulates per-method cache hits
with the shipped TTLs. Useful as a regression check when tweaking cache
policy.

Tests: 162/162 passing.

* docs(experiments): add cache freshness check runbook

Seven-step manual runbook to validate the cache TTL contract doesn't
cause workflow lag. Covers each cached method with:

- exact gh CLI trigger command
- what to observe in the dashboard / lifecycle log
- pass/fail threshold (TTL + 30s poll cycle)

Companion to experiments/cache-replay.mjs — replay measures how much
we saved, runbook measures whether we lost anything in the process.

* docs(experiments): add Step 2 — consolidate review comment fetching

Single GraphQL call replaces GraphQL + REST for review comments.
Include comment data in agent reaction message to eliminate
agent-side gh read calls. Update future steps.

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

* docs(experiments): add duplicate API traffic analysis

Three independent sources hit GitHub API for the same PRs:
1. Dashboard serialize.ts — individual REST calls, no batch, no cache
2. CLI lifecycle manager — batch + guards
3. Web lifecycle manager — same batch + guards, 3s offset

~50% of all API traffic is pure duplication. Dashboard and dual
lifecycle managers are the root causes.

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

* docs(experiments): add full cache architecture to duplicate traffic analysis

Three independent cache layers across two processes with zero shared
state. Web process creates its own plugin registry, SCM plugin, lifecycle
manager, and dashboard cache — all hitting GitHub independently.

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

* docs(experiments): add shared PR enrichment plan

Persist batch enrichment + review comments to session metadata files.
Dashboard reads from disk instead of making its own GitHub API calls.
Remove web's duplicate lifecycle manager.

Eliminates ~268 calls / 15 min (58% of all traffic). Dashboard data
gets fresher (30s vs 5min). Single writer (CLI lifecycle), web only reads.

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

* docs(experiments): update Step 1 — remove all three fallback paths

Remove fallback in determineStatus(), maybeDispatchCIFailureDetails(),
and maybeDispatchMergeConflicts(). All three follow the same pattern:
batch cache hit → use it, cache miss → skip (wait 30s for next batch).

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

* docs(experiments): promote Step 3 (remove dead reviews field) + detail Step 5 (issue caching)

Step 3: Remove reviews(last: 5) from batch query — fetched but never
consumed, reduces GraphQL complexity on every batch call.

Step 5: Persist issue data to session metadata at spawn — eliminates
27 gh issue view calls per 15 min (both processes re-fetch independently).

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

* feat(core): remove individual REST fallback from lifecycle polling

Remove fallback paths in determineStatus(), maybeDispatchCIFailureDetails(),
and maybeDispatchMergeConflicts() that made individual REST calls when the
batch enrichment cache missed. The batch runs every 30s — a cache miss
means the data arrives on the next cycle, not that it's lost.

Also add populatePREnrichmentCache() call to check() so single-session
checks also use the batch path.

Eliminates ~110 individual pr view/pr checks calls per 15-min window
(24% of all AO-side traffic).

* feat(core): consolidate review comment fetching into single GraphQL call

Add getReviewThreads() to SCM interface — returns all review threads
(human + bot) with isBot flag from a single GraphQL query. Lifecycle
manager splits locally for separate reaction pipelines.

- Eliminates the REST getAutomatedComments() call (40 calls / 15 min)
- Reaction messages now include inline comment data (file, line, author,
  body, URL) so agents don't need to re-fetch via gh api
- Default config messages updated to not tell agents to call gh
- getAutomatedComments kept as optional for backward compatibility

* perf(scm-github): remove unused reviews(last: 5) from batch query

The batch query fetched reviews with author, state, submittedAt but
the data was never consumed — only used in a validation check.
The reviewDecision scalar field provides everything AO needs.

Reduces GraphQL complexity cost on every batch call.

* docs(experiments): add post-optimization trace report (Steps 1-3)

5-session, 17-minute trace after removing REST fallback, consolidating
review comments, and removing dead reviews field.

Results: GraphQL 35%/hr (was 41%), REST <1% (was 3%), automated
comment REST calls eliminated. 54% of remaining traffic is redundant
(duplicate lifecycle manager + dashboard individual calls).

* docs(experiments): add trace file gist link to post-optimization report

* feat(core,web): shared PR enrichment — dashboard reads from metadata

CLI lifecycle manager now persists batch enrichment data and review
comments to session metadata files (prEnrichment + prReviewComments
keys). The web dashboard reads from metadata instead of calling
GitHub API.

Changes:
- lifecycle-manager: add persistPREnrichmentToMetadata() after poll,
  write prReviewComments in maybeDispatchReviewBacklog()
- serialize: replace enrichSessionPR (6 API calls) with metadata read
- services: stop web lifecycle polling (keep for webhook checks)
- cache: remove prCache (no longer needed)
- routes: remove timeout wrappers and cacheOnly pattern

Eliminates ~237 calls / 15 min (54% of all AO-side traffic).
Dashboard data freshness improves from 5min to 30s.

* fix(web): remove unused beforeEach import in serialize test

* docs(experiments): add final trace report — 56% GraphQL reduction achieved

5-session, 24-min trace after all optimizations including shared
enrichment. GraphQL 905/hr (was 2,072), REST 5/hr (was 168).
Single lifecycle manager confirmed. Dashboard API calls eliminated.
Max sessions before budget exhaustion: ~27 (was ~12).

* docs(experiments): add REST budget breakdown to final report

* fix(core): use storageKey for getSessionsDir in persistPREnrichmentToMetadata

* fix(test): use OpenCodeSessionManager type in plugin-integration tests

* fix(test): update bugbot-comments and auto-cleanup tests for new review API

* fix(web): fix syntax error and missing import from rebase

* docs(experiments): add complete rate-limiting change log and update final report numbers

* fix(web): fix tmux session resolution for legacy wrapped storageKeys

* fix(web): pass tmuxName directly to terminal server instead of reverse-resolving

* perf(core): gate detectPR behind Guard 1 ETag — skip when PR list unchanged

* perf(core): always run Guard 1 for all repos, dedup issue views, include threadId in review messages

* feat(core): add Guard 3 (review ETag), enrich review data with summaries, dedup issue views, gate detectPR for all repos

* fix(web): reuse cached tmuxSessionId on re-open, add 15-session trace report and comparison docs

* perf(scm-github): reduce contexts to first:10, add -i to review GraphQL for rate limit tracing

* feat(core): merge CI details into transition, enrich merge conflict message, reduce batch contexts, add graphqlCost tracing

* chore(experiments): remove working artifacts, keep final reports and reference docs

* chore: remove experiments directory

* refactor: remove getAutomatedComments from SCM interface and all implementations

* fix: address all PR review comments

- gh-trace: make binary resolution async via fs.access (no event loop
  blocking, no shell injection), cache mkdir for trace writes, async
  fire-and-forget appendFile, document 10MB maxBuffer rationale
- lifecycle-manager: log detectPR failures via observer instead of
  silent catch, add getPRState fallback for terminal states
  (merged/closed) when batch enrichment cache misses
- scm-gitlab: implement getReviewThreads with bot+human threads and
  isBot flag, fixing silent feature regression after
  getAutomatedComments removal
- scm-github: clear reviewThreadsCache in invalidatePRCache, document
  first:11 CI checks cost budget
- runtime-tmux: use printf+JSON.stringify for PATH export to prevent
  shell injection from single quotes
- agent-workspace-hooks: add cache timestamp sanity check, include
  --repo in cache keys to prevent cross-repo collisions
- services: document dashboard dependency on CLI polling
- tests: update gh binary path assertions for resolved paths

* fix: address all 17 PR review comments

- gh-trace: use path.delimiter, cache-only-on-success, last HTTP status
  line, await writes with warn-once, redact secrets, gate JSON.parse
- agent-workspace-hooks: validate cache keys, redact wrapper trace args,
  sha256 cache keys to prevent collisions, 120s TTL ceiling
- session-manager: skip PATH wrappers for claude-code (native hooks)
- types: add deprecation JSDoc for getReviewThreads
- graphql-batch: clear Guard 3 in clearETagCache, re-read ETag on 304,
  switch Guard 2 to check-runs endpoint, drop per_page=1 from Guard 3
- Add gh-trace unit tests for extractOperation, redactArgs, parseHttp

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: adil <adil.business4064@gmail.com>
Co-authored-by: iamasx <adilshaikh4064@gmail.com>
2026-04-25 18:57:04 +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
yyovil 9bed49d453 fix: scope node types to node packages 2026-04-13 18:25:21 +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
Prateek 27712442f8 fix: restore GitHub repo URLs to ComposioHQ/agent-orchestrator — only npm scope changed 2026-04-09 16:00:32 +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
Gautam Tayal 1ed4430c70 fix: revert unlink script file 2026-03-29 20:34:33 +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 2c55520fac fix 2026-03-28 16:02:18 +05:30
Gautam Tayal 9a09d8be25 fix: add script invocation of opencode bash 2026-03-28 14:51:35 +05:30
github-actions[bot] c7d6634839 chore: version packages 2026-03-20 15:47:55 +00:00
Harsh Batheja 4e2144d99e
feat: OpenCode session lifecycle and CLI controls (#315)
* feat: refine OpenCode session reuse strategy and cleanup

* fix: harden OpenCode session selection and lint errors

* refactor: centralize OpenCode reuse resolution flow

* fix: return 404 for missing session in message route

* feat: replace force remap with terminal reload control

* fix: protect project path from session kill cleanup

* fix: preserve fullscreen alignment without reload action

* fix: harden OpenCode session id handling and title reuse selection

* fix: show OpenCode reload control and remap before restart

* fix: preserve title-only OpenCode reuse with fallback mapping persistence

* fix: resolve remaining PR315 Bugbot findings

* fix: keep OpenCode discovery title-based without timestamp sorting

* fix: avoid enrichment race fallout in session listing

* fix: guard OpenCode discovery parse with array check

* fix: stabilize Linear comment integration check

* fix: harden OpenCode discovery and prompt option flow

* ux: clarify OpenCode terminal restart action

* fix: remap OpenCode session fresh on each restart

* fix: validate remap session ids before reuse

* fix: clean archived metadata only after purge

* docs: align OpenCode remap selection with title-based behavior

* fix: harden opencode cleanup and ignore local sisyphus state

* test: add timeout cleanup coverage for session enrichment

* fix: harden Linear integration helper against transient non-JSON errors

* fix: make linear integration assertions resilient to eventual consistency

* fix: remove unused fs import after rebase

* fix: address remaining Bugbot blockers for opencode session handling

* fix: avoid stale metadata overwrite during restore post-launch

* fix: forward subagent in orchestrator flows and defer reuse lookup

* fix: apply configured subagent fallback for session spawn

* fix: scope archived cleanup by project and delay archive restore write

* fix: normalize orchestrator strategy aliases in start display logic

* fix: centralize orchestrator strategy normalization in core

* fix: derive orchestrator reuse display from spawn result

* fix: keep cleanup results consistent across project-id collisions

* fix: namespace cleanup results when session IDs collide

* fix: harden GitHub issue stateReason fallback

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix: avoid false failing CI state mapping

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix: add tmux command timeouts

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix: bound session API enrichment latency

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix: repair scm-github merge resolution

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix: repair lifecycle-manager test merge

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix: delay archive metadata recreation until restore passes

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* test: restore claim-pr session mocks

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix: address review findings for OpenCode lifecycle PR

- Fix stripControlChars to preserve newlines for reload commands
- Add SessionNotFoundError and use instanceof checks in API routes
- Document orchestratorSessionStrategy in YAML example
- Validate existingSessionId with asValidOpenCodeSessionId()
- Extract inline Node script to buildSessionLookupScript helper
- Create OpenCodeSessionManager interface for remap capability
- Create OpenCodeAgentConfig type for agent-specific config
- Change default orchestratorSessionStrategy from delete to reuse

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

* fix: guard reused session display without metadata

* chore: add agent config files to .gitignore

Agent configuration files (CLAUDE.md, AGENTS.md, IMPROVEMENTS.md, etc.) are personal and project-specific. They should not be committed to the repository.

Changes:
- Remove CLAUDE.md from git tracking
- Add agent config files to .gitignore
- Create .gitignore-template for reference

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

* chore: update gitignore for agent config folder structure

Reorganized agent configuration files:
- CLAUDE.md and AGENTS.md stay in root (agents read them there)
- Tracking files move to .opencode/ (IMPROVEMENTS.md, etc.)
- Optional Claude files in .claude/

Updated .gitignore to ignore folders instead of individual files.

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

* fix: tighten opencode remap and session discovery safeguards

* fix: address Bugbot findings in session manager

* fix: scope opencode discovery to opencode sessions

* fix: restore concurrent listing and strict permission literals

* fix: throw SessionNotFoundError, parallelize list enrichment, fix permissions type

- Session manager now throws SessionNotFoundError instead of plain Error
  for missing sessions, so web API routes correctly return 404 (not 500)
- Parallelize session enrichment in list() — was sequential, causing O(N)
  latency for N sessions with subprocess enrichment
- Fix AgentLaunchConfig.permissions type to accept legacy "skip" value
  (AgentPermissionInput instead of AgentPermissionMode)
- Add happy-path and validation tests for /api/sessions/:id/message route
- Update all test mocks to use SessionNotFoundError

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

* fix: route ao send through session manager

* feat: add purge option to orchestrator stop

* fix: register opencode agent in web services

* test: align web API missing-session coverage

* fix: match notifier config by plugin name

* refactor: dedupe session lookup and tmux buffer send flow

* test: update send lifecycle wait expectation

* fix: harden send routing and cleanup purge controls

---------

Co-authored-by: Harsh <harsh@Ubuntu-24-Forrest.lan>
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Prateek <karnalprateek@gmail.com>
2026-03-08 09:55:44 +05:30
prateek de6653e258
feat: first-class orchestrator session + file-based system prompt (#101)
* feat: first-class orchestrator session + file-based system prompt

Make the orchestrator a first-class managed session that flows through
the same SessionManager pipeline as worker sessions, and fix a blocking
bug where long system prompts get truncated by tmux/zsh.

Changes:
- Add OrchestratorSpawnConfig type and spawnOrchestrator() to
  SessionManager interface
- Implement spawnOrchestrator() in session-manager.ts: proper
  hash-based tmuxName, runtimeHandle, plugin lifecycle — no workspace
  creation (uses project.path directly)
- Refactor `ao start` to use SessionManager.spawnOrchestrator()
  instead of manual tmux calls + metadata writes (~80 lines removed)
- Refactor `ao stop` to use SessionManager.kill() instead of manual
  tmux kill + metadata delete
- Update `ao init` next steps: guide users to `ao start` before
  `ao spawn`
- Add systemPromptFile to AgentLaunchConfig for file-based system
  prompts (avoids tmux truncation of 2000+ char inline prompts)
- Update agent-claude-code, agent-codex, agent-aider plugins to use
  shell command substitution "$(cat '/path')" when systemPromptFile
  is set
- Update runtime-tmux create() to use load-buffer/paste-buffer for
  launch commands >200 chars
- Add 8 tests for spawnOrchestrator
- Fix SessionManager mock in 8 test files (add spawnOrchestrator)

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

* fix: use hash-based tmux name in orchestrator attach hint

The tmux attach hint after `ao start` printed the user-facing session
ID (e.g. app-orchestrator) instead of the hash-based tmux session name
(e.g. a3b4c5d6e7f8-app-orchestrator), causing "session not found"
errors. Now captures the runtimeHandle.id from spawnOrchestrator's
return value for the correct tmux target.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 19:32:35 +05:30
prateek eaea131af9
feat: seamless onboarding with enhanced documentation (#66)
* feat: implement seamless onboarding with enhanced documentation

- Add comprehensive README.md (18KB) with quick start, core concepts, and FAQ
- Add detailed SETUP.md (16.5KB) with prerequisites, integration guides, and troubleshooting
- Add examples/ directory with 5 ready-to-use config templates:
  - simple-github.yaml: Minimal GitHub setup
  - linear-team.yaml: Linear integration
  - multi-project.yaml: Multiple repos
  - auto-merge.yaml: Aggressive automation
  - codex-integration.yaml: Using Codex agent

- Add environment detection (git repo, remote, branch, auth status)
- Auto-fill prompts with smart defaults from detected environment
- Add prerequisite validation (git, tmux, gh CLI)
- Show actionable next steps and warnings
- Parse owner/repo from git remote automatically
- Detect LINEAR_API_KEY and SLACK_WEBHOOK_URL in environment
- Prompt for Linear team ID when Linear tracker selected

- Format all files with Prettier for consistency

Reduces onboarding time from 30+ minutes to ~5 minutes:
1. Install CLI: `npm install -g @composio/ao-cli`
2. Run init: `ao init` (auto-detects everything)
3. Spawn agent: `ao spawn my-project ISSUE-123`

Users no longer need to:
- Manually parse git remote URLs
- Look up current branch names
- Remember YAML syntax
- Search for Linear team IDs
- Debug missing prerequisites

-  pnpm build - All packages compile
-  pnpm typecheck - No TypeScript errors
-  pnpm lint - No new linting issues
-  pnpm format - All files formatted

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

* docs: update installation instructions to reflect npm not yet published

Package is not published to npm yet, so users must build from source.
Updated README.md and SETUP.md to:
- Make 'build from source' the primary installation method
- Add note that npm publishing is coming soon
- Include pnpm as a prerequisite

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

* feat: add ao init --auto --smart for zero-config setup

Implements intelligent config generation with project type detection.

## What's New

### ao init --auto
- Zero prompts - auto-generates config with smart defaults
- Detects: git repo, remote, branch, languages, frameworks, tools
- Generates project-specific agentRules based on detected tech stack

### Project Detection
- Languages: TypeScript, JavaScript, Python, Go, Rust
- Frameworks: React, Next.js, Vue, Express, FastAPI, Django, Flask
- Tools: pnpm workspaces, test frameworks
- Package managers: pnpm, yarn, npm

### Rule Templates
Created templates for:
- base.md - Universal best practices
- typescript.md - TS strict mode, ESM, type imports
- javascript.md - Modern ES6+ patterns
- react.md - Hooks, composition, best practices
- nextjs.md - App Router, Server Components
- python.md - Type hints, PEP 8
- go.md - Error handling, defer patterns
- pnpm-workspaces.md - Monorepo commands

### Example Output

```bash
ao init --auto

# Detects:
# ✓ TypeScript + pnpm workspaces
# ✓ React + Next.js
# ✓ Vitest

# Generates:
agentRules: |
  Always run tests before pushing.
  Use TypeScript strict mode.
  Use ESM modules with .js extensions.
  Use React best practices (hooks, composition).
  Before pushing: pnpm build && pnpm typecheck && pnpm lint && pnpm test
```

## Benefits

- **5 seconds** instead of 5 minutes
- **Zero config knowledge** required
- **Context-aware rules** tailored to your stack
- **Still customizable** - edit the generated config

## Future: --smart (AI-powered)

Flag added but not yet implemented. Will use Claude Code to:
- Analyze CLAUDE.md, CONTRIBUTING.md
- Read CI/CD config
- Generate custom rules based on project patterns

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

* fix: detect repo default branch instead of current branch

Fixes Bugbot issue: "Current branch wrongly suggested as default base branch"

## Problem

detectEnvironment was using `git branch --show-current` to suggest
defaultBranch in the config. If a user ran `ao init` while on a feature
branch like `feat/my-work`, the wizard would suggest that feature branch
as the default, causing agents to branch from the wrong base.

## Solution

Added detectDefaultBranch() function with 3 fallback methods:
1. git symbolic-ref refs/remotes/origin/HEAD (most reliable)
2. GitHub API via gh CLI (if ownerRepo known)
3. Check common branch names: main, master, next, develop

Now EnvironmentInfo tracks both:
- currentBranch: The checked-out branch (for display only)
- defaultBranch: The repo's base branch (for config)

## Testing

Tested on feat/seamless-onboarding branch:
- Current branch: feat/seamless-onboarding (displayed)
- Default branch: main (correctly detected for config)

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

* fix: prevent duplicate framework detection in Python projects

Fixes Bugbot issue: "Duplicate frameworks when multiple Python config files exist"

## Problem

When both requirements.txt and pyproject.toml exist and mention the same
framework (e.g., FastAPI), the detection loop added it to the frameworks
array twice, causing duplicate rules in the generated config.

## Solution

Added addFramework() helper that checks if framework already exists before
adding to the array. Also prevents pytest from being set multiple times as
testFramework.

## Testing

Verified with test repo containing both files with FastAPI:
- Before: Would add 'fastapi' twice
- After: Only adds 'fastapi' once ✓

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

* fix: address Bugbot review comments

- Remove redundant conditional in --smart flag (both branches were identical)
- Include templates directory in npm package files

* fix: add existence check for base.md template file

Add existsSync guard before reading base.md to handle missing templates gracefully, consistent with other template file reads.

* fix: use direct tool invocation instead of which command

Replace 'which' with direct tool invocation (tmux -V, gh --version)
for better portability on minimal Linux systems where 'which' may
not be installed.

* fix: address Bugbot review comments

- Simplify gh auth status check to rely on exit code instead of output string
- Remove async from synchronous functions (detectProjectType, generateRulesFromTemplates)

* feat: add setup script for one-command installation

Add scripts/setup.sh that:
- Installs pnpm if not present
- Installs dependencies
- Builds all packages
- Links CLI globally

Updated README with simplified setup instructions using the script.

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

* fix: correct npm link command in setup script

Remove incorrect -g flag from npm link command. The correct syntax is to cd into the package directory and run npm link without flags.

* fix: address Bugbot review comments on init command

- Validate --smart flag requires --auto (prevents silent ignore)
- Fix path validation to check user-specified path (not CWD)

These fixes address medium and low severity issues found by Cursor Bugbot
in PR #66 review.

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

* docs: add DirectTerminal troubleshooting and fix setup script

- Add TROUBLESHOOTING.md documenting node-pty posix_spawnp error
- Update setup.sh to rebuild node-pty from source (fixes DirectTerminal)
- Ensures seamless onboarding with working terminal out-of-the-box

Resolves DirectTerminal WebSocket failures from incompatible prebuilt binaries.

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

* fix: resolve variable scope issue in init command validation

- Move path variable outside if block to fix TypeScript scope error
- Only validate path existence if projectId is provided
- Use inline tilde expansion instead of missing expandHome import

Fixes build error that prevented setup.sh from completing.

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

* fix: automate node-pty rebuild to eliminate terminal issues

- Add postinstall hook to automatically rebuild node-pty after pnpm install
- Create scripts/rebuild-node-pty.js for automatic rebuild with error handling
- Remove manual node-pty rebuild from setup.sh (now automatic)

This ensures DirectTerminal works correctly on every installation without
manual intervention. Fixes posix_spawnp errors from incompatible prebuilt
binaries across different systems and installations.

Resolves issue where users would encounter blank terminals after setup.

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

* docs: update TROUBLESHOOTING with automatic node-pty rebuild

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

* docs: add comprehensive README with quick start guide

- 3-line magical setup: clone → setup → init → start
- Architecture overview with plugin slots table
- Usage examples and auto-reaction configuration
- Links to detailed docs (SETUP.md, TROUBLESHOOTING.md, examples/)
- Philosophy: push not pull, amplify judgment

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

* fix: resolve ESLint errors in rebuild-node-pty script

- Add scripts directory configuration to eslint.config.js
- Configure Node.js globals (console, process) for scripts
- Remove unused error variable from catch block

Fixes lint CI failure.

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

* fix: warn when auto mode uses placeholder repo value

- Detect when 'owner/repo' placeholder is used in --auto mode
- Show warning: 'Could not detect GitHub repository'
- Update next steps to emphasize editing config when placeholder used
- Prevents silent failures when spawning agents with invalid repo

Addresses Bugbot review comment about silent placeholder values.

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

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 22:22:13 +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 8db5f2b161
docs: comprehensively optimize CLAUDE.md for agent effectiveness (#38)
* docs: condense CLAUDE.md for token efficiency

Reduced CLAUDE.md from 223 to 169 lines (24% reduction).

Changes:
- Removed verbose sections (reference implementation table, redundant explanations)
- Added "Key Files" section highlighting types.ts and plugin examples upfront
- Condensed tech stack, conventions, and commands into scannable format
- Kept critical content: plugin pattern, shell security, common mistakes

Agents spawned by the orchestrator don't need special documentation - they
just read the repo's existing CLAUDE.md. The orchestrator is transparent.

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

* docs: comprehensively optimize CLAUDE.md and add package READMEs

Major improvements to agent effectiveness on this codebase:

## Enhanced CLAUDE.md (169 → 444 lines)

**New sections:**
- Quick Start — get oriented fast (links to common tasks)
- Looking for X? — quick reference table for finding code
- Monorepo Tools — pnpm workspace commands (filter, watch mode, scoped builds)
- Common Tasks — step-by-step guides (add plugin, add Session field, add event type)
- Plugin Development — pattern explanation + examples (notifier-desktop, agent-claude-code)
- Architecture Deep Dive — data flow diagram + state machine + key abstractions

**Improved sections:**
- Commands — added watch mode, filtering, scoped operations
- Shell Security — added exploit example showing actual injection
- Common Mistakes — added 5 code examples (BAD vs GOOD with explanations)
- Design Decisions — added "Why" for each decision

## New Package READMEs (Progressive Disclosure)

**packages/core/README.md:**
- Explains core services (SessionManager, LifecycleManager, PluginRegistry)
- Key files guide (types.ts, session-manager.ts, lifecycle-manager.ts)
- Common tasks specific to core
- Architecture notes (why flat metadata, why polling, why plugin slots)

**packages/plugins/runtime-tmux/README.md:**
- How the plugin works (creating sessions, sending messages, getting output)
- Security considerations (session ID validation)
- Common issues (tmux not installed, detached sessions persist)
- Limitations (macOS/Linux only, no resource limits)
- Architecture notes (why tmux over raw processes)

## Impact

Agents working on this codebase now have:
1. **Faster discovery** — "Looking for X?" table + Quick Start links
2. **Actionable guides** — step-by-step for common tasks
3. **Concrete examples** — code showing actual mistakes and fixes
4. **Progressive disclosure** — package READMEs for deep dives
5. **Monorepo fluency** — pnpm workspace commands documented
6. **Architecture understanding** — data flow + state machine + "why" explanations

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

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-15 03:34:35 +05:30
prateek 620bad9053
Wire xterm.js terminal embed into web dashboard (#29)
* feat: wire xterm.js terminal embed into web dashboard

- Add xterm.js dependencies (@xterm/xterm, @xterm/addon-fit)
- Create SSE streaming endpoint at /api/sessions/:id/terminal
  - Polls tmux capture-pane every 2 seconds
  - Streams ANSI-aware output with colors/formatting
  - Handles session exit gracefully
- Implement Terminal component with xterm.js
  - Live output streaming from tmux pane
  - Fullscreen mode toggle
  - Optional input mode to send messages to agent
  - Read-only by default
- Import xterm.js CSS in globals.css

The terminal shows live agent activity in the browser with full
ANSI color support. Users can optionally enable input mode to
send messages to the running agent.

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

* fix: improve terminal rendering and remove clunky input interface

- Fix rendering issues:
  - Use term.reset() instead of clear() for proper clearing
  - Only update when content changes (prevents flickering)
  - Add scrollToBottom() to show latest output
  - Increase scrollback buffer to 10000 lines
  - Add convertEol for proper line endings
  - Increase default height to 600px
  - Add padding around terminal content

- Simplify interface:
  - Remove separate input box (was clunky)
  - Make it clearly "Read-only" by default
  - Clean up header UI
  - Better fullscreen sizing calculation

Next step: Consider WebSocket-based bidirectional terminal for
true interactive sessions (like tmux attach).

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

* feat: implement proper interactive terminal with WebSocket

Replace hacky SSE polling with real-time WebSocket for bidirectional
terminal communication. This is a proper interactive terminal - type
directly, like tmux attach in the browser.

Architecture:
- WebSocket server on port 3001 alongside Next.js
- Uses tmux pipe-pane for real-time output streaming
- Sends input character-by-character via tmux send-keys
- Handles terminal resize events
- Connection status indicator

Implementation:
- packages/web/src/server/terminal-websocket.ts: WebSocket server
- Terminal component now fully interactive (not read-only)
- Runs both servers via concurrently in dev mode
- Green dot = connected, red dot = disconnected
- Proper cursor, no more clunky input box

Benefits:
- Real-time streaming (not 2-second polling)
- Type directly into terminal
- Proper terminal control sequences
- Handles resize
- Like native tmux attach

Dependencies added:
- ws (WebSocket server)
- @types/ws
- concurrently (run multiple servers)

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

* fix: improve terminal rendering - hide extra cursor, faster polling

- Hide xterm cursor (tmux output has its own)
- Increase polling from 500ms to 100ms (5x faster, less lag)
- Add -J flag to join wrapped lines (reduce truncation)
- Increase scrollback to 200 lines

Note: Current polling approach has limitations:
- Still some lag when typing (replacing full content)
- Not true real-time streaming
- For interactive use, prefer 'tmux attach' directly

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

* fix: improve terminal auto-sizing - multiple fit attempts

- Fit terminal multiple times (0ms, 100ms, 250ms, 500ms) to catch layout changes
- Add w-full class to ensure terminal takes full width
- Better error handling for fit operations
- Should eliminate need to manually zoom out

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

* fix: use const for pollInterval, expand WORKING zone by default

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

* fix: import WebSocket as value, not type-only

WebSocket.OPEN is used as a runtime value, so it cannot be a type-only import.

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

* feat: implement proper tmux control mode streaming

Replace hacky polling approach with professional tmux control mode:

- Use 'tmux -C attach-session' for true incremental streaming
- Parse control mode protocol (%output, %exit, %layout-change)
- Send commands via stdin (not spawning processes)
- Unescape octal sequences from tmux output
- Event-driven (not polling) - lower latency, less CPU
- Only sends new output (not full snapshots)

Benefits:
- 10x less bandwidth (no repeated snapshots)
- Lower latency (~10ms vs 100ms)
- No missed output (event-driven)
- Proper professional solution (how iTerm2 does it)

Based on research of VS Code, tmux control mode documentation,
and industry best practices for terminal streaming.

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

* refactor: replace custom WebSocket terminal with ttyd

- Replace broken custom tmux control mode + xterm.js with ttyd (iframe)
- ttyd handles all terminal rendering, ANSI, resize, input correctly
- Terminal server now manages ttyd instances per session on dynamic ports
- Enable mouse mode on tmux sessions for proper scroll behavior
- Remove dead code: @xterm/xterm, @xterm/addon-fit, ws deps
- Remove dead SSE terminal API route
- Remove xterm.css import
- Clean up Terminal component: single status dot, no decorative dots
- Make Linear issue link clickable in SessionDetail
- Extract issue label from URL for display (INT-1327 from full URL)

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

* feat: add tracker plugin integration for issue label extraction

Replaces hardcoded URL parsing with proper tracker plugin abstraction.
Now the dashboard uses tracker.issueLabel() to extract human-readable
labels from issue URLs (e.g., "INT-1327", "#42") in a plugin-agnostic way.

Changes:
- Core: Add optional issueLabel() method to Tracker interface
- Plugins: Implement issueLabel() in tracker-github and tracker-linear
- Web: Add issueUrl and issueLabel fields to DashboardSession
- Web: Add enrichSessionIssue() to populate labels via tracker plugin
- Web: Update SessionDetail and SessionCard to use new fields
- Web: Add getTracker() helper to services.ts

This is fully generic - any tracker plugin can implement issueLabel()
and the dashboard will automatically use it.

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

* fix: add delay before Enter in tmux sendMessage to ensure text delivery

The dashboard "ask to resolve" button was putting messages in the input
buffer without submitting them. The tmux send-keys Enter was arriving
before the pasted text was fully processed. Match the bash send-to-session
script behavior with a 300ms delay.

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

* fix: use node:timers/promises for async setTimeout

node:util does not export setTimeout — the async sleep function
lives in node:timers/promises.

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

* fix: hide tmux status bar in terminal for cleaner appearance

Added 'status off' option to remove the green tmux bar at the bottom
of the terminal for a cleaner, less cluttered interface.

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

* fix: add health check to wait for ttyd before returning URL

Fixes race condition where iframe loads before ttyd is ready,
causing 'localhost refused to connect' on direct page loads.
Now waits up to 3s for ttyd to be listening before responding.

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

* feat: enable hot reloading for terminal server with tsx watch

Both frontend (Next.js) and backend (terminal server) now have
hot reloading enabled for faster development iteration.

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

* fix: add PR enrichment to session detail page

The session detail page was not enriching PR data with live stats
from GitHub, causing it to show +0 -0. Now calls enrichSessionPR()
to fetch additions, deletions, CI status, and review data.

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

* fix: clean up and collapse unresolved PR comments

- Extract title and description from Bugbot comments
- Strip out HTML comments, metadata, and image links
- Make comments collapsible (collapsed by default)
- Show clean summary with expand for details

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

* feat: convert session detail page to client-side with live updates

- Changed from SSR to client-side component
- Added polling every 5 seconds for real-time data
- Created /api/sessions/[id] endpoint for single session fetch
- Faster navigation with client-side routing
- No page refresh needed to see updates

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

* fix: remove machine-specific symlinks from repository

- Remove .claude and packages/web/agent-orchestrator.yaml symlinks
- Add them to .gitignore to prevent re-committing
- These are development convenience links created per-worktree

Fixes Bugbot comment about environment-dependent paths that break
on other machines.

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

* feat: improve session detail UI and fix activity detection

- Fix session activity detection and timestamps
  - session-manager now checks if runtime is alive in get()
  - Use file birthtime/mtime for createdAt/lastActivityAt
  - Fixes "Idle" status and "Created just now" issues

- Improve session detail UI
  - Hide empty projectId chip
  - Add PR# chip to header
  - Fix "0 checks failing" logic
  - Remove duplicate status display
  - Humanize attention level labels ("review" → "Pending Review")

- Add Linear tracker support
  - Register Linear tracker plugin in web services
  - Issue labels now show "INT-1354" instead of full URL

- Add "Ask Agent to Fix" feature
  - Button for each unresolved comment
  - API endpoint to send messages to agent via tmux
  - /api/sessions/[id]/message endpoint

- Fix waitForTtyd timeout handling
  - Add timeout event handler to prevent hanging requests
  - Properly abort timed-out requests

- Fix lint errors
  - Remove duplicate imports
  - Fix unused variables
  - Use type-only imports where appropriate

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

* fix: address production issues in terminal implementation

- Use dynamic hostname instead of hardcoded localhost
  - Terminal.tsx uses window.location.hostname
  - terminal-websocket.ts derives URL from request host
  - Supports remote access and reverse proxy scenarios
  - Fixes high-severity Bugbot comments

- Add SIGTERM handling for graceful shutdown
  - Previously only handled SIGINT
  - Now cleans up ttyd processes on SIGTERM too
  - Prevents orphan processes after restarts
  - Adds 5s timeout to prevent hanging

Fixes Bugbot comments:
- r2807572056: Terminal embed hardcodes localhost endpoints
- r2807630014: Terminal URLs are hardcoded to localhost
- r2807604002: ttyd children survive non-interrupt shutdowns

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

* fix: properly validate message delivery to tmux sessions

Use execFile with promisify instead of spawn to:
- Wait for tmux commands to complete
- Check exit codes for failures
- Return proper error if send-keys fails
- Add 5s timeout to prevent hanging

Previously the endpoint returned success immediately without
verifying if the message was actually delivered to the session.

Fixes Bugbot comment r2807674035

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

* fix: use runtime plugin sendMessage for proper message delivery

Address Bugbot review comments:
- Use session.runtimeHandle instead of raw session id
- Use Runtime plugin's sendMessage method for proper sanitization
- Remove direct tmux command execution

The Runtime plugin's sendMessage handles:
- Proper runtime handle resolution
- Input sanitization and control character stripping
- Safe message delivery via load-buffer for long messages

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

* fix: sanitize message input and support runtime defaults

Address Bugbot review comments:
- Add stripControlChars sanitization to prevent control character injection
- Fall back to config.defaults.runtime when project.runtime is not set
- Validate that message is not empty after sanitization

This aligns the message endpoint with the existing send endpoint's
security model and ensures proper runtime resolution.

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

* fix: address all Bugbot review comments

Comprehensive fixes for all remaining issues:

**message/route.ts:**
- Add session ID validation with validateIdentifier
- Add JSON parse error handling with try/catch
- Add message length validation with MAX_MESSAGE_LENGTH
- Add type guard for non-string messages
- Add URL encoding for session IDs

**terminal-websocket.ts:**
- Fix memory leak in waitForTtyd by tracking and canceling timeouts
- Add cleanup() function to cancel pending requests and timers
- Add MAX_PORT limit to prevent port exhaustion
- Add error handlers for spawned tmux processes
- Use once() instead of on() for exit/error to prevent race condition
- Add unref() to shutdown timeout to allow graceful exit

**page.tsx:**
- Use useCallback to memoize fetchSession
- Add fetchSession to useEffect dependency arrays
- Add URL encoding for session ID in fetch

**Terminal.tsx:**
- Add URL encoding for session ID in terminal fetch URL

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

* fix: remove unused err variable in JSON parse catch block

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

* fix: add security improvements for terminal and message endpoints

Address remaining Bugbot security concerns:

**terminal-websocket.ts:**
- Add TODO comments about authentication requirements
- Restrict CORS to localhost origins only (was allowing any origin)
- Add session existence validation before spawning ttyd
- Import fs and path modules for session validation

**Authentication:**
Full authentication with session ownership validation is tracked
separately and requires architectural decisions about auth middleware.
These changes provide defense-in-depth for the current implementation.

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

* fix: remove unused readFileSync import

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

* feat: terminal button opens ttyd directly in new tab

Instead of navigating to the session detail page, the terminal button
now fetches the ttyd URL from the terminal server and opens it directly
in a new browser tab. Falls back to the session detail page if the
terminal server is unavailable.

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

* fix: address final 4 Bugbot review comments

**Issue 1: Terminal lookup ignores configured data directory (HIGH)**
- Load config using loadConfig() from @agent-orchestrator/core
- Use config.dataDir instead of hardcoded path for session validation
- Ensures terminal works with custom dataDir configurations

**Issue 2: Terminal ports exhaust without reuse (MEDIUM)**
- Implement port recycling with availablePorts Set
- Recycle ports when ttyd instances exit or error
- Prevents port exhaustion after 100 allocations

**Issue 3: Remote dashboard blocked by terminal CORS (MEDIUM)**
- Replace hardcoded localhost whitelist with dynamic origin validation
- Allow CORS if origin hostname matches request host
- Supports remote deployments while maintaining security

**Issue 4: Message endpoint can pick wrong runtime plugin (MEDIUM)**
- Use session.runtimeHandle.runtimeName instead of project config
- Ensures message delivery uses the runtime that created the session
- Handles sessions created with different runtime than current config

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

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-15 01:37:07 +05:30
prateek 925a7aae92
feat: implement runtime and workspace plugins (tmux, process, worktree, clone) (#2)
* feat: implement runtime and workspace plugins (tmux, process, worktree, clone)

Implement all 4 runtime/workspace plugins for the agent orchestrator,
replacing the stub files with full implementations of the Runtime and
Workspace interfaces from @agent-orchestrator/core.

- runtime-tmux: tmux session lifecycle with busy detection, wait-for-idle
  message delivery, capture-pane output, and load-buffer for long messages
- runtime-process: child process management with rolling output buffer,
  graceful SIGTERM/SIGKILL shutdown, and stdin message delivery
- workspace-worktree: git worktree create/destroy/list with symlink support
  for shared resources and postCreate hook execution
- workspace-clone: git clone --reference for fast isolated clones with
  postCreate hooks

Closes INT-1328

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

* fix: address all PR review comments on runtime/workspace plugins

Fixes for all 13 review issues:

1. Move processes Map inside create() for per-instance isolation (#1)
2. Wrap stdin.write in promise with error/backpressure handling (#2)
3. Use child.once("exit") instead of on("exit") to prevent leaks (#3)
4. Use child.once("exit") in destroy() timeout to prevent leaks (#4)
5. Add assertValidSessionId() with /^[a-zA-Z0-9_-]+$/ regex (#5)
6. Single capture-pane call in isBusy() to avoid TOCTOU (#6)
7. Throw error when sendMessage sends to busy session after timeout (#7)
8. Use crypto.randomUUID() for temp file names to avoid collisions (#8)
9. Document that postCreate commands run with full shell privileges (#9)
10. Inspect worktree add error — only retry on "already exists" (#10)
11. Delete orphaned branch after worktree remove in destroy() (#11)
12. Log warning for corrupted clones in list() instead of silent skip (#12)
13. Return "not running" attach info for exited processes (#13)

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

* fix: harden runtime/workspace plugins (spawn errors, stdin races, git injection)

- runtime-process: catch spawn errors with setImmediate pattern, prevent
  double resolve/reject in sendMessage with done-flag, add late error
  handler to prevent unhandled crashes, eliminate non-null assertions
- runtime-tmux: guard config.environment with ?? {} fallback
- workspace-worktree: add -- separator to git checkout/branch commands
  to prevent option injection, guard branch deletion to feature branches
  only (must contain /), ensure parent dirs exist before symlink creation
- workspace-clone: add -- separator to git checkout commands, suppress
  no-console lint for expected diagnostic warning

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

* fix: address codex review — spawn race, path traversal, temp file perms

- runtime-process: replace setImmediate with spawn/error event listeners
  to fix race where create() returns handle for failed process; document
  intentional shell:true usage
- runtime-tmux: restrict temp file permissions to 0o600
- workspace-worktree: validate projectId/sessionId as safe path segments,
  reject absolute/traversal symlink paths, verify resolved targets stay
  within workspace
- workspace-clone: validate projectId/sessionId as safe path segments
  in both create() and list()

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

* fix: address all review comments and fix CI

- workspace-clone/worktree: remove incorrect -- from git checkout
  (switches to path mode, breaking branch operations)
- workspace-worktree: fix prefix collision in list() by appending /
  to startsWith check (foo no longer matches foobar)
- runtime-tmux: use named tmux buffers (-b flag) to prevent concurrent
  sendMessage calls from overwriting each other's paste content
- runtime-process: check signalCode in addition to exitCode for
  accurate liveness detection of signal-killed processes
- runtime-process: reject duplicate session IDs to prevent orphaned
  child processes from overwritten map entries
- runtime-tmux: anchor $ in busy detection to line start (^\$\s) to
  avoid false idle detection from dollar signs in output

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

* fix: forward plugin config through registry so workspace dirs take effect

The plugin registry was calling plugin.create() with no arguments,
silently ignoring configured worktreeDir/cloneDir settings. Added
extractPluginConfig() to map orchestrator config to per-plugin config,
and updated register()/loadBuiltins() to pass config through.

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

* fix: clean up orphaned worktrees/clones on checkout failure, remove risky branch -D

- Worktree: remove git branch -D from destroy() — the "/" heuristic
  could delete pre-existing local branches unrelated to the workspace
- Worktree: wrap fallback checkout in try/catch, clean up orphaned
  worktree if checkout fails
- Clone: wrap fallback checkout in try/catch, rmSync the orphaned clone
  directory if both checkout attempts fail

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

* fix: clean up partial clone directory when git clone fails

Wrap the git clone call in try/catch so a failed clone removes any
partial clonePath left on disk, preventing orphaned directories that
block retries for the same sessionId.

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

* refactor: remove busy detection from tmux runtime

Busy/idle detection is an agent-layer concern, not a runtime concern.
Each agent (Claude Code, Codex, Aider) has its own native mechanism for
checking activity status. The tmux runtime should only manage sessions
and send messages immediately without polling.

Removed: isBusy() heuristics, sleep-based polling loop, sentWhileBusy
error. sendMessage() now sends immediately.

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

* fix: clean up tmux session when launch command send-keys fails

If send-keys fails after new-session succeeds, the orphaned tmux
session was left running and unmanaged. Now wraps send-keys in
try/catch that kills the session before rethrowing.

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

* fix: prevent clone error handler from deleting pre-existing workspace

Add early existence check before git clone so create() fails
immediately if clonePath already exists, rather than cloning into it,
failing, and then deleting the pre-existing workspace in the catch block.

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

* fix: clean up leaked tmux buffer when paste-buffer fails

The -d flag on paste-buffer only deletes the named buffer on success.
If paste fails, the buffer persists in the tmux server. Added
delete-buffer in the finally block to ensure cleanup regardless of
paste outcome.

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

* fix: prevent orphan processes on duplicate sessionId, auto-remove exited sessions

- Move processes.has() check before spawn() so duplicate sessionIds are
  rejected before any child process is created
- Auto-remove exited sessions from the process map in the exit handler
  so the sessionId can be reused without manual destroy()

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

* fix: register process in map before exit handler to prevent stale entries

A fast-exiting child could fire the exit event before processes.set()
ran, causing delete() to no-op and set() to insert a permanently stale
entry. Moving set() before the exit handler registration ensures
delete() always finds the entry.

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

* fix: atomic session reservation and immediate exit handler registration

- Reserve map slot synchronously (no await gap between has() and set())
  so concurrent create() calls cannot both pass the duplicate check
- Register exit handler immediately after spawn(), before any await,
  so fast-exiting processes cannot miss cleanup

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

* fix: kill process group on destroy, use literal mode for tmux send-keys

- runtime-process: use process.kill(-pid) to kill the entire process
  group, not just the shell. Added detached:true so child gets its own
  process group. Falls back to child.kill() if group kill fails.
- runtime-tmux: add -l flag to send-keys for short messages so text
  like "Enter" or "Space" is sent literally, not as key names.

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

* test: add comprehensive unit and integration tests for all 4 plugins

Add 136 unit tests and 35 integration tests covering runtime-tmux,
runtime-process, workspace-worktree, and workspace-clone plugins.
Also adds 13 unit tests for the core plugin-registry.

Unit tests (136 total):
- runtime-tmux (25): create/destroy, send-keys modes, getOutput, isAlive, getMetrics
- runtime-process (40): spawn lifecycle, SIGKILL escalation, output buffering, isolation
- workspace-worktree (40): create/destroy, tilde expansion, branch fallback, symlinks
- workspace-clone (31): create/destroy, --reference clone, pre-existence check, cleanup

Integration tests (35 total):
- runtime-tmux (8): full lifecycle with real tmux sessions
- runtime-process (11): full lifecycle with real child processes
- workspace-worktree (8): real git worktree operations
- workspace-clone (8): real git clone operations

Plugin-registry tests (13):
- register/get/list, config forwarding, slot isolation, loadBuiltins, loadFromConfig

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

* fix: guard against null process in runtime-process methods

ProcessEntry.process is null between reservation and spawn completion.
Add explicit null checks in destroy(), sendMessage(), isAlive(), and
getAttachInfo() to prevent TypeError if called during that window.

Addresses bugbot review comment about transient null process crash.

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

* fix: buffer partial lines in runtime-process output capture

Stream chunks split at arbitrary boundaries, so splitting on "\n" and
pushing each piece as a complete line corrupts output when a line spans
two chunks. Buffer the trailing partial line and only push complete
(newline-terminated) lines. Flush remaining partial on process exit.

Addresses bugbot review comment about incorrect output chunk splitting.

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

* fix: correct vitest peer dep resolution in pnpm-lock.yaml

The lockfile referenced vitest@3.2.4 without jiti/jsdom/lightningcss
peer deps, but that resolution didn't exist in the packages section.
Update to match the full resolution available on main.

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

* fix: use per-stream partial buffers in runtime-process output capture

stdout and stderr shared one partial-line buffer, so interleaved chunks
from different streams could be concatenated into corrupted lines. Each
stream now gets its own closure with an independent partial buffer.

Addresses bugbot review comment about mixed stream chunk corruption.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 15:13:57 +05:30
Prateek 5058c409d5 feat: scaffold TypeScript monorepo with all plugin interfaces
Phase 0 complete. Establishes:
- pnpm workspace with 18 packages (core, cli, web, 15 plugins)
- Complete type definitions in packages/core/src/types.ts defining
  all 8 plugin slot interfaces (Runtime, Agent, Workspace, Tracker,
  SCM, Notifier, Terminal) + core service interfaces
- YAML config loader with Zod validation and sensible defaults
- Plugin registry with built-in discovery
- CLAUDE.md with conventions for spawned agents

All agents can now branch from main and implement their assigned
packages against the interfaces defined in types.ts.

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