Fixes#1048. ao start used to allocate a fresh `{prefix}-orchestrator-N`
on every invocation instead of reattaching to the previous session, and
the dashboard's orchestrator link pointed at a different id than the
CLI just printed.
Changes:
runStartup (packages/cli/src/commands/start.ts):
- On startup, list existing orchestrators for the project, partition
them into live (runtime still running) and restorable (terminal but
sm.restore()-able) buckets, pick the most-recently-active from the
chosen bucket, and reuse/restore that id instead of spawning a new
one. Only spawn fresh when both buckets are empty.
- Live is preferred UNCONDITIONALLY over restorable — a newer killed
record can never beat an older-but-running one. Without this, a
cross-bucket sort could resurrect a killed record via sm.restore()
while the live orchestrator kept running, leaving two alive.
- Restored sessions get an explicit "(restored)" marker in the CLI
summary so the resurfaced id isn't a surprise.
- The phantom `${prefix}-orchestrator` id constant is removed from
every URL print, browser-open target, and summary line. Everything
now uses the real selected id.
registerStop (same file):
- ao stop now resolves the real orchestrator via sm.list(projectId)
+ isOrchestratorSession filter + most-recently-active sort, then
calls sm.kill on that id. The old phantom `${prefix}-orchestrator`
target never matched a real numbered record, so ao stop was a
silent no-op and the orchestrator kept running on disk between
start cycles. sm.list-failure warning no longer duplicates with the
generic "no orchestrator found" message.
isOrchestratorSession (packages/core/src/types.ts):
- Tightened: legacy bare-id records (`{projectId}-orchestrator` with
no role metadata) are no longer recognized as orchestrators by the
public predicate. This was the source of the dashboard/CLI id
divergence — stale bare records with a different prefix than the
numbered form were leaking into the dashboard's orchestrator list.
session-manager repair (packages/core/src/session-manager.ts):
- Split `isOrchestratorSessionRecord` (permissive, used by cleanup
protection) from a new `isRepairableOrchestratorRecord` (stricter,
used only by repairSingleSessionMetadataOnRead and
repairSessionMetadataOnRead).
- The strict repair predicate accepts role-stamped records, the bare
`{sessionPrefix}-orchestrator` correct-prefix legacy shape, and the
numbered `{sessionPrefix}-orchestrator-N` worktree shape. It
rejects foreign bare names like `{projectId}-orchestrator`, so
those records never get `role: orchestrator` backfilled on read
and therefore can no longer pass `isOrchestratorSession()` in real
`sm.list()` output via the role-metadata branch.
Tests added (~12):
- runStartup: live reuse, restore-on-killed, ignore-stale-bare
legacy records, live-beats-restorable regression, multi-live
reuse, URL fallback when --no-orchestrator.
- ao stop: kills the actual numbered id (not the phantom), handles
multiple orchestrators, tolerates sm.list throwing.
- isOrchestratorSession: rejects stale bare ids without role
metadata; accepts bare ids with role metadata stamped.
- listDashboardOrchestrators: stale bare excluded, numbered live
included, role-stamped legacy included.
- session-manager repair: does not backfill role onto foreign bare-id
records (issue #1048 regression guard).
Unblocks: review comments from cursor[bot] (dead else-if branch,
double messaging, redundant isTerminalSession check) and illegalcall
(cross-bucket sort, repair-backfill bypass of predicate tightening) —
all addressed in-place with the multi-orchestrator model preserved.
Verified: core 606/606, cli 450/450, typecheck clean across core/cli/web.
Represent missing activity probes as first-class signal states so lifecycle inference only treats valid idle evidence as proof. This prevents false stuck transitions, keeps API/UI lifecycle truth aligned, and makes root monorepo verification deterministic by serializing recursive build and typecheck.
Introduce an explicit reporting channel so worker agents can self-declare
their workflow phase (started/working/waiting/needs-input/fixing-ci/
addressing-reviews/completed). Fresh reports are trusted over weak inference
but runtime death, activity-based waiting_input, and SCM ground truth still
take precedence.
- Add `applyAgentReport`, validator, canonical mapping, and freshness helper
in `packages/core/src/agent-report.ts`.
- Wire the fallback into `determineStatus` just before the idle-beyond-
threshold promotion, skipping orchestrator and terminal sessions.
- Add `ao acknowledge` and `ao report <state>` CLI commands. Both resolve
the session from `AO_SESSION_ID` when no argument is passed.
- Teach the base agent prompt and orchestrator prompt about the new
reporting commands.
- Ship unit tests covering normalization, mapping, transition validation,
metadata persistence, freshness, and first-start behavior.
Stage 3 of the state-machine redesign (see aa-2/state-machine-redesign-
rollout-plan.md).
- New repo-utils.ts with extractOwnerRepo() and isValidRepoString()
shared across detectEnvironment, autoCreateConfig, addProjectToConfig
- Remote regex now supports GitLab subgroup paths (group/subgroup/repo)
- Repo validation accepts multi-segment paths (owner/repo and deeper)
- Updated prompt text to mention group/subgroup/repo format
- Updated ProjectConfig.repo docstring to be provider-neutral
- Tests import from shared helpers instead of duplicating regex
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@clack/prompts text() can return undefined when submitted with just
the placeholder value. Guard with typeof check in promptText and
defensive (entered || "") in both callers to prevent .trim() crash.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- detect-env: test GitHub/GitLab HTTPS/SSH remote extraction, unknown
hosts returning null, missing remote, and non-git directories
- repo-validation: test the anchored regex accepts owner/repo, rejects
empty, lone slash, missing segments, whitespace, and nested paths
- config-validation: test SCM/tracker inference skipped when repo is
missing or has no slash, and inferred correctly with owner/repo
- scm-webhooks: test eventMatchesProject returns false when project
has no repo configured
- orchestrator-prompt: existing test covers repo:undefined → "not configured"
- prompt-builder: existing test covers BASE_AGENT_PROMPT_NO_REPO selection
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Anchor repo validation regex with $ to reject trailing junk like
"acme/repo extra" or "acme/repo#frag" — both prompt locations
- Widen detectEnvironment and addProjectToConfig remote regex to
match gitlab.com in addition to github.com, so GitLab repos get
auto-detected owner/repo instead of silently skipping it
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
afterEach calls vi.restoreAllMocks() which restores the top-level mock
to the real function. Use vi.spyOn on the module namespace instead of
vi.mocked on the destructured import so the mock survives restoration.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The test has no ownerRepo detected, which triggers the interactive
repo prompt. Mock isHumanCaller to false so the prompt is skipped
in the non-interactive test environment.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Fix ao start <path> regression: when target path differs from cwd,
run autoCreateConfig on the target (which is a git repo) instead of
cwd (which may not be). Removes the double-create pattern that added
a second project entry.
2. Gate remaining PR/CI sections in orchestrator-prompt: PR Takeover,
PR Review Flow, Bulk Issue Processing, and Monitoring Progress
details are now wrapped in project.repo checks so repo-less projects
don't get contradictory instructions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move ensureGit() after path dedup check so already-registered paths
return early without requiring git
- Use realpathSync for canonical path comparison (resolves symlinks,
case variants, trailing slashes)
- Bail out with error when both SIGTERM and SIGKILL fail to stop AO
instead of unconditionally unregistering a live instance
- Rewrite path-dedup test to exercise the path-arg branch via
AO_CONFIG_PATH, covering addProjectToConfig's dedup lines
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The test was mocking detectEnvironment with isGitRepo: false, which now
correctly triggers the fail-fast error for non-git directories. Updated
to isGitRepo: true since the test is verifying config generation
defaults, not non-git behavior.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. prompt-builder: gate PR/CI instructions on project.repo — use
trimmed BASE_AGENT_PROMPT_NO_REPO when no remote configured
2. orchestrator-prompt: gate Quick Start and Available Commands
sections on project.repo — omit issue/PR commands when absent
3. types.ts: add changeset (minor for ao-core) with migration note
4. config.ts: consistent includes("/") guard for tracker inference
5. start.ts: fail fast with clear error when run in non-git directory
6. start.ts: stricter repo validation regex (requires non-empty
segments on both sides of slash)
7. start.ts: addProjectToConfig now prompts for repo like
autoCreateConfig does, with matching warning message
8. scm-webhooks.ts: pre-filter projects without repo in
findWebhookProjects to skip unnecessary SCM plugin lookups
9. lifecycle-manager.ts: fix GitLab subgroup split — use lastIndexOf
to correctly handle group/subgroup/repo paths
Closes#1154
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Replace SIGINT/SIGTERM handlers with a process `exit` handler to avoid
conflicting with the shutdown handler that flushes lifecycle state and
exits with the correct code (130 for SIGINT).
2. Expand dashboard process pattern to match dev mode (next dev, ao-web)
in addition to production (next-server, start-all.js).
3. Only kill dashboard-matching PIDs from lsof output, leaving unrelated
co-listeners (sidecars, SO_REUSEPORT) untouched.
4. Use killDashboardOnPort for the first port attempt too, preventing
blind kills on the configured port when running.json is stale.
5. Add test for mixed-PID filtering on a single port.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove SIGINT/SIGTERM/exit listeners from process when the dashboard
child exits, preventing listener accumulation if runStartup is called
multiple times in the same process.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add test for orchestrator-prompt with repo: undefined (verifies
"not configured" fallback instead of showing literal "undefined")
- Add c8 ignore for interactive prompt code in autoCreateConfig and
promptText (same pattern as existing promptConfirm/promptSelect)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of silently omitting the repo field when no GitHub remote is
found, interactively ask the user to enter their owner/repo. This way
users on GitLab or other hosts can provide the correct value during
first-run setup rather than having to manually edit the yaml afterward.
If the user skips the prompt, the config is still valid (repo remains
optional) with a clear warning about what features are unavailable.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use ?? instead of || for ownerRepo fallback (semantically correct for
null-to-undefined conversion)
- Extract requireRepo() result into a local variable in tracker-gitlab's
updateIssue and issueUrl to avoid redundant validation calls
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Make `repo` field optional in ProjectConfig and Zod schema so projects
without a detected GitHub remote can still load and run
- Remove placeholder `repo: "owner/repo"` from autoCreateConfig() and
addProjectToConfig() — omit the field entirely when no remote is found
- Always use actual workingDir for `path` instead of unreliable `~/<projectId>`
fallback for non-git directories
- Add null guards for `project.repo` across SCM plugins, tracker plugins,
lifecycle manager, webhooks, and prompt builders to prevent crashes when
repo is not configured
Closes#1154
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Port scan now uses killDashboardOnPort() which checks `ps -p <pid> -o args=`
for next-server/start-all.js before killing, avoiding collateral damage to
unrelated services on nearby ports.
2. SIGINT/SIGTERM handlers now call process.exit() after killing the child,
restoring default exit behavior so Ctrl+C doesn't leave the parent hung
if the child is unresponsive.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds test for stopDashboard finding an orphaned dashboard on a reassigned
port via the port-range scan fallback. Marks signal handler body with
c8 ignore since it only fires on process termination.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two bugs caused `ao stop` to fail when the dashboard port was auto-reassigned:
1. Ctrl+C did not propagate to the dashboard child process because Node.js
doesn't guarantee signal forwarding. Added SIGINT/SIGTERM/exit handlers
in runStartup() to explicitly kill the dashboard child.
2. stopDashboard() only checked the configured port, missing orphaned
dashboards on reassigned ports. Refactored into killOnPort() helper
and added a port-range scan fallback (up to MAX_PORT_SCAN ports).
Closes#645
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The "errors when multiple projects and no arg" test expects the error
path (not the prompt path) in resolveProject. Set mockIsHumanCaller
to false so the test reliably hits the non-interactive error branch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cover the moved isAlreadyRunning() check (non-TTY exit, quit, open,
restart, new orchestrator) and the path-based dedup guard in
addProjectToConfig(). Uses hoisted mocks for isAlreadyRunning,
isHumanCaller, promptSelect, unregister, and waitForExit to ensure
proper test isolation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move the `isAlreadyRunning()` check before any config-mutating operations
so that running `ao start` on an already-running project no longer writes
a phantom duplicate entry to agent-orchestrator.yaml. The "new orchestrator"
choice is deferred via a flag until after config is loaded.
Also add path-based deduplication in `addProjectToConfig()` so that a
project whose resolved path already exists in config is returned as-is
instead of being appended with a numeric suffix.
Closes#1150
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(cli): remove lifecycle-worker subprocess model, run polling in-process
Replaces the per-project `ao lifecycle-worker` subprocess with an in-process
polling loop managed inside `lifecycle-service`. All registered projects are
now polled from the single long-lived `ao start` process.
- Drops the `lifecycle-worker` command and its registration.
- Rewrites `lifecycle-service` around a `Map<projectId, ActiveLoop>`,
with SIGINT/SIGTERM/beforeExit graceful shutdown.
- Removes PID-file coordination (PID file, log file, status, etc.) —
these only existed to track subprocess state.
- Per-project error isolation is preserved: the core lifecycle manager's
`pollAll()` already catches per-cycle errors, and stop failures in one
project can't prevent others from stopping.
- Updates `start`/`spawn` callers and tests to drop the PID/logFile shape.
- Adds `lifecycle-service.test.ts` covering idempotency, unknown projects,
error isolation across projects, and graceful stop-all.
Closes#1185
* fix(cli): address bugbot review on lifecycle-service in-process refactor
- `ao spawn` / `ao batch-spawn` no longer call `ensureLifecycleWorker`.
That call used to start `setInterval` polling in the one-shot spawn
process, which (a) kept the CLI alive forever after the session spawned
and (b) duplicated polling already running in `ao start`. Replaced with
a `warnIfAONotRunning()` helper that checks `running.json` and prints a
hint if the orchestrator isn't up.
- `ao stop` no longer calls `stopLifecycleWorker`. That call always ran
against a fresh in-memory map (stop is a separate process from start),
so it always returned false and printed "Lifecycle worker not running"
misleadingly. SIGTERM to the `ao start` PID already triggers the shared
shutdown handler in `lifecycle-service`, which stops every loop.
- Drop duplicated shutdown closure inside `registerSignalsOnce` — signal
handlers now reference `stopAllLifecycleWorkers` directly.
- Update tests accordingly: spawn.test.ts mocks `running-state.getRunning`,
start.test.ts drops `stopLifecycleWorker` expectations.
* fix(cli): drop SIGINT/SIGTERM listeners from lifecycle-service
Installing listeners for those signals removes Node.js's default
"exit on signal" behavior (per Node docs: "its default behavior will
be removed — Node.js will no longer exit"). Since the registered
listener doesn't call process.exit(), the `ao start` process would
hang on SIGTERM with the setInterval timer keeping the event loop
alive forever — effectively breaking `ao stop`.
Default signal handling terminates the process cleanly; the OS
reclaims the interval timer and dashboard child. `stopAllLifecycleWorkers`
stays exported for callers that want explicit cleanup before exit.
* fix(cli): project-scoped spawn warning + flush lifecycle health on exit
Addresses reviewer feedback on PR #1186:
- `warnIfAONotRunning` now takes a projectId and warns not only when no
AO is running, but also when the running instance isn't polling the
target project (e.g. `ao start A` then `ao spawn` in B left users
silent about the fact that B wasn't being polled).
- `running.json` now records only the project this `ao start` actually
polls, not every project in config. Previously this list was a lie —
`ensureLifecycleWorker` is called for the selected project only.
- `ao start` installs SIGINT/SIGTERM handlers that call
`stopAllLifecycleWorkers()` (flushing per-project "stopped" health
state) and then `process.exit()`. Installing the handler safely
requires an explicit exit because SIGINT/SIGTERM listeners remove
Node's default exit behavior.
* fix(cli): remove dead stopLifecycleWorker, add missing test mock
Addresses bugbot comments on PR #1186:
- Delete `stopLifecycleWorker` from `lifecycle-service.ts`. It was only
called from `ao stop` in the old subprocess model; in the in-process
model, SIGTERM to the `ao start` pid + the shutdown handler in
`start.ts` covers cleanup. No production caller remains.
- Add `stopAllLifecycleWorkers: vi.fn()` to `start.test.ts`'s
`lifecycle-service.js` mock. Without it, `vi.mock` replaced the module
and the named import resolved to undefined; the shutdown handler's
try/catch would silently swallow the resulting TypeError, hiding any
regression in how shutdown is wired.
- Update `lifecycle-service.test.ts` to drop references to the removed
export (one test removed, one repurposed as a no-op smoke test for
`stopAllLifecycleWorkers` against an empty active map).
* feat(cli): install-aware update command, startup notifier, and doctor version check
Make `ao update` detect whether AO was installed via npm or git source and
route accordingly: git installs use the existing shell script, npm installs
prompt to run `npm install -g @aoagents/ao@latest`, and unknown installs
print guidance. Add `--check` flag for JSON version info output.
Add a startup version notifier that reads a cached update check (no network
on startup) and prints a one-liner to stderr when outdated. Background
cache refresh runs after command completion via unref'd timer.
Add a version freshness check to `ao doctor` using cached data only (no
network dependency in diagnostics).
Closes#1136
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(cli): high coverage for update service, command routing, and notifier
Add comprehensive tests per maintainer request for full coverage of all
update paths:
update-check.ts (57 tests):
- isVersionOutdated: major/minor/patch comparisons, equal, newer, missing parts
- detectInstallMethod: all 4 scenarios (git, npm-global, partial, unknown)
- readCachedUpdateInfo: fresh, expired, 23h boundary, version mismatch,
invalid JSON, missing fields, empty file
- fetchLatestVersion: success, 404, 500, network error, timeout, non-JSON,
missing version, non-string version, AbortSignal presence
- checkForUpdate: cache hit, cache bypass with force, stale cache fetch,
cache write on success, no cache write on failure, version match,
unreachable registry, result shape
- maybeShowUpdateNotice: positive print case, not outdated, no cache,
non-TTY, AO_NO_UPDATE_NOTIFIER, CI, AGENT_ORCHESTRATOR_CI,
each skipArg (update/doctor/--version/-V/--help/-h)
- scheduleBackgroundRefresh: does not throw
update.ts (17 tests):
- conflicting flags rejection
- --check: valid JSON output, forces fresh fetch, no side effects
- git: default args, --skip-smoke, --smoke-only, cache invalidation
- npm-global: no script-runner, already up-to-date, registry unreachable
exits non-zero, --skip-smoke warning, forces fresh fetch
- unknown: help message, shows latest version, registry unreachable,
suggests npm command
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): address all review feedback — coverage gaps, code fixes, Codex findings
Code fixes:
- Extract classifyInstallPath() for testable npm-global detection
- Convert IS_TTY from module-level constant to isTTY() function call
(testable without import-time mocking)
- Fix non-TTY npm update silently exiting 0 → now exits 1 so scripts
detect that no upgrade happened (Codex P2)
- Handle pre-release version tags in isVersionOutdated() — strips
suffixes before comparing, handles NaN safely
- Export getCacheDir() and writeCache() for test coverage
Test coverage (69 + 24 + 9 + 3 = 105 tests):
- classifyInstallPath: npm-global (Unix + Windows paths), git, partial, unknown
- isVersionOutdated: pre-release tags, NaN handling, same-with-prerelease
- getCacheDir: XDG_CACHE_HOME override, fallback to ~/.cache
- writeCache: success, EACCES mkdir failure, ENOSPC write failure
- handleNpmUpdate full flow: confirm → spawn success, spawn failure with
sudo suggestion, spawn error (ENOENT), non-TTY exit, flag warning,
user decline, registry unreachable
- --check with unreachable registry still outputs valid JSON
- maybeShowUpdateNotice: happy path print, AGENT_ORCHESTRATOR_CI guard,
each skipArg individually
- scheduleBackgroundRefresh: no-throw, swallows errors
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): move background refresh earlier, distinguish local vs global node_modules
- Move scheduleBackgroundRefresh() before parseAsync() so the fetch runs
in parallel with command execution. Short-lived commands now have a
better chance of seeding the cache before exit. (Codex P2)
- Distinguish global npm installs (lib/node_modules, .pnpm store) from
local project node_modules (npx, linked installs). Local installs now
classify as "unknown" instead of "npm-global" to avoid suggesting
npm install -g to npx users. (Codex P2)
- Add tests: pnpm global store path, nvm global path, npx local path
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(lint): remove forbidden import() type annotations in test mocks
Replace `vi.importActual<typeof import("node:fs")>(...)` with untyped
`vi.importActual(...)` to satisfy @typescript-eslint/consistent-type-imports.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): don't misclassify local pnpm installs as npm-global
Local pnpm projects have node_modules/.pnpm/ which matched the /.pnpm/
heuristic. Replace with /pnpm/global/ check which only matches pnpm's
actual global store path (~/.local/share/pnpm/global/...).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): non-TTY exits 0, remove misleading sudo suggestion
- Non-TTY ao update now exits 0 after printing the command. Exit 1
implied error but the user just needs to run the command manually.
- Remove blanket sudo suggestion on npm failure. npm can exit 1 for
many reasons (network, version conflict, engine mismatch). Just
print the exit code and let the user diagnose.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): pnpm global support, defer background refresh after parseAsync
- Detect pnpm global installs separately from npm global. pnpm users
now see `pnpm add -g @aoagents/ao@latest` instead of the npm command.
New InstallMethod value: "pnpm-global".
- Move scheduleBackgroundRefresh() to .then() after parseAsync() completes.
Prevents in-flight fetch from holding the event loop open during
short-lived commands when the registry is slow/offline. (Codex P2)
- Use info.recommendedCommand instead of hardcoded npm string in
handleNpmUpdate, so the correct package manager command is always used.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): use actual command name in error message, not hardcoded "npm"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): handle signaled npm update exits
* fix(cli): detect prereleases behind stable releases
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: release 0.2.5
Realign main with npm registry after off-branch publish of 0.2.3/0.2.4.
Bump all 21 linked packages to 0.2.5 and cherry-pick the startup-grace-period
fix for #989 (was in 5e4244a8 but never merged to main).
Also sync non-linked plugin versions (notifier-discord, notifier-openclaw,
scm-gitlab, tracker-gitlab) to their current npm versions.
* Revert "chore: release 0.2.5"
This reverts commit eb17f32834.
* chore: bump all package versions to 0.2.5, remove release workflow
- Bump all 25 packages to 0.2.5 to realign with npm registry
- Update package-version test to expect 0.2.5
- Remove stale .changeset/linear-spawn-branch-name.md
- Delete .github/workflows/release.yml (changesets-based NPM publish)
---------
Co-authored-by: Prateek <karnalprateek@gmail.com>
Co-authored-by: AO Bot <ao-bot@composio.dev>