Skip to content

Weekly upstream sync 2026-08-02 (251 commits) - #25

Closed
loganbronstein wants to merge 252 commits into
mainfrom
upstream-week-2026-08-02
Closed

loganbronstein wants to merge 252 commits into
mainfrom
upstream-week-2026-08-02

Conversation

@loganbronstein

Copy link
Copy Markdown
Owner

HEADS UP: this merge had conflicts. The diff includes conflict markers (<<<<<<<, =======, >>>>>>>). Logan, decide which side wins.

Weekly upstream sync (2026-08-02)

Upstream grandamenium/cortextos added 251 commit(s) since the last run.
Each one is summarized in plain English below.


What's new

  1. Merge pull request chore(community): register claude-to-codex-migration in catalog grandamenium/cortextos#857 from grandamenium/chore/catalog-claude-to-codex-migration

  2. Maintenance in community: add claude-to-codex-migration to catalog

  3. Merge pull request feat(community): claude-to-codex agent migration skill grandamenium/cortextos#849 from grandamenium/feat/claude-to-codex-migration-skill

  4. New feature in community: add claude-to-codex-migration skill

  5. Bug fix in pty: auto-accept Claude Code 2.1.x Bypass Permissions screen (headless crash-loop)

  6. New feature in security: auto-install pre-push hook + windowed roster/cron leak-guard (SEC-1 L3) (feat(security): auto-install pre-push hook + windowed roster/cron leak-guard (SEC-1 L3) grandamenium/cortextos#704)

  7. Bug fix in bus: manage-cycle operates on the target agent's config, not the caller's (fix(bus): manage-cycle operates on the target agent's config, not the caller's grandamenium/cortextos#636)

  8. New feature in crm-assistant: connect + verify tools in setup, not just detect (feat(crm-assistant): connect + verify tools in setup, not just detect grandamenium/cortextos#703)

  9. Docs: teach opencode runtime in agent-management skill + README/CLAUDE (docs: teach opencode runtime in agent-management skill + README/CLAUDE grandamenium/cortextos#702)

  10. New feature in daemon: context-handoff lifecycle + native opencode adapter (feat(daemon): context-handoff lifecycle + native opencode adapter grandamenium/cortextos#699)

  1. New feature in daemon: context-handoff mechanism — default-on at 60% model window (feat(daemon): context-handoff mechanism, default-on at 60% window grandamenium/cortextos#685)
  1. New feature in security: server-side leak-guard CI check + broadened gitignore (SEC-1 L1/L4) (feat(security): server-side leak-guard CI check (SEC-1 L1/L4) grandamenium/cortextos#698)
  1. Maintenance in security: purge leaked internal fleet-metadata reports and operator paths from public repo
  1. Bug fix in bus: remove Codex token expiry auto-send from check-usage-api.sh (fix(bus): remove Codex token expiry auto-send grandamenium/cortextos#684)
  1. Revert "Add workflows-engineering community skill"
  1. Add workflows-engineering community skill
  1. Bug fix in dashboard: auto-populate Max plan usage widget (Claude + Codex) from usage cache (fix(dashboard): auto-populate Max plan usage widget (Claude + Codex) from usage cache grandamenium/cortextos#669)
  1. Bug fix in daemon: retry Telegram command registration so restarts don't drop the slash menu (fix(daemon): retry Telegram command registration on restart grandamenium/cortextos#668)
  1. Bug fix in daemon: require explicit onboarding marker, do not auto-write on heartbeat (fix(daemon): require explicit onboarding marker, do not auto-write on heartbeat grandamenium/cortextos#667)
  1. New feature: add voice-agent-factory community skill (feat: add voice-agent-factory community skill grandamenium/cortextos#610)
  1. Bug fix in fast-checker: inject unhandled callbacks with PTY-injection sanitization (fix(fast-checker): inject unhandled callbacks with PTY-injection sanitization grandamenium/cortextos#604)
  1. Bug fix in security: quote Unicode-whitespace-led forged headers in sanitizeForPtyInjection (fix(daemon): sanitizeForPtyInjection misses Unicode White_Space line-leading runs grandamenium/cortextos#596) (fix(security): quote Unicode-whitespace-led forged headers in sanitizeForPtyInjection (#596) grandamenium/cortextos#603)
  1. Bug fix in security: validate task ids + assignee before path construction (Weekly upstream sync 2026-06-02 (32 commits) #13/Weekly upstream sync 2026-06-03 (32 commits) #14) (fix(security): validate task ids + assignee before path construction grandamenium/cortextos#598)
  1. Bug fix in security: sanitize remaining PTY-injection media paths (fix(daemon): sanitize PTY injection — dynamic-fence body + forged-header neutralization grandamenium/cortextos#592 follow-up) (fix(security): sanitize remaining PTY-injection media paths (#592 follow-up) grandamenium/cortextos#597)
  1. New feature in pty: make --dangerously-skip-permissions configurable per agent (feat(pty): make --dangerously-skip-permissions configurable per agent grandamenium/cortextos#593)
  1. Bug fix in hooks: harden permission-gate auto-approve against bypass + path escape (fix(hooks): harden permission-gate auto-approve against bypass + path escape grandamenium/cortextos#594)
  1. Bug fix in daemon: sanitize PTY injection — dynamic-fence body + forged-header neutralization (fix(daemon): sanitize PTY injection — dynamic-fence body + forged-header neutralization grandamenium/cortextos#592)
  1. New feature in usage-monitor: unified Claude Max + Codex usage tracking (feat(usage-monitor): unified Claude Max + Codex usage tracking grandamenium/cortextos#563)
  1. Bug fix in daemon: audit silent-failure class — BOM, PATH-unaware execFile, supervision gaps (fix(daemon): audit silent-failure class — BOM, PATH-unaware execFile, supervision gaps grandamenium/cortextos#459) (fix(daemon): silent-failure audit — BOM/PATH/poller supervision (closes #459) grandamenium/cortextos#556)
  1. Revert "feat(memory): bidirectional task<->vault sync (closes feat(memory-07): vault task sync — bidirectional task↔obsidian integration grandamenium/cortextos#357) (feat(memory): bidirectional task<->vault sync (closes #357) grandamenium/cortextos#553)" (Revert #553 (vault task sync) — re-review #357 separately grandamenium/cortextos#555)
  1. New feature in memory: bidirectional task<->vault sync (closes feat(memory-07): vault task sync — bidirectional task↔obsidian integration grandamenium/cortextos#357) (feat(memory): bidirectional task<->vault sync (closes #357) grandamenium/cortextos#553)
  1. Bug fix in daemon: image-poison crash auto-recovery (fix(telegram+daemon): suppress local_file in photo + auto-recover image-poison crashes grandamenium/cortextos#446 recover-half) (fix(daemon): auto-recover from image-poison crash (from #446) grandamenium/cortextos#552)
  1. Bug fix in daemon: kill false-positive crash detection — no-unlink markers + first-heartbeat clear (fix(daemon): kill false-positive crash detection — no-unlink markers + first-heartbeat clear grandamenium/cortextos#445) (fix(daemon): kill false-positive crash detection on --continue rollovers (closes #445) grandamenium/cortextos#550)
  1. New feature in telegram: add CTX_WHISPER_LANG env for transcription language (feat(telegram): CTX_WHISPER_LANG transcription language (closes #428) grandamenium/cortextos#549)
  1. Bug fix in cli: validate org name in init and add-agent --org (cli: init and add-agent --org accept mixed-case org names that runtime + dashboard then reject grandamenium/cortextos#407) (fix(cli): validate org name in init + add-agent (closes #408) grandamenium/cortextos#548)
  1. Bug fix in dashboard: close auth bypass + unblock health probe (GAP-0030, GAP-0034) (fix(dashboard): close middleware auth bypass via JWT verification (closes #471) grandamenium/cortextos#547)
  1. Add research agent community template (Add research agent community template grandamenium/cortextos#543)
  1. New feature in telegram: multi-user ALLOWED_USER (comma-separated) (feat(telegram): multi-user ALLOWED_USER (comma-separated) grandamenium/cortextos#467)
  1. Bug fix in codex-pty: resume persisted codex thread on daemon restart (fix(codex-pty): resume persisted codex thread on daemon restart grandamenium/cortextos#437)
  1. Bug fix in daemon: gate shouldContinue JSONL check on Claude runtime only (fix(daemon): gate shouldContinue JSONL check on Claude runtime only grandamenium/cortextos#463)
  1. Bug fix in build: register hook-loop-detector entry in tsup.config.ts (fix(build): register hook-loop-detector entry in tsup.config.ts grandamenium/cortextos#398)
  1. New feature in skills: add idea-grooming, business-news-monitor, multi-perspective-grilling (feat(skills): add idea-grooming, business-news-monitor, multi-perspective-grilling grandamenium/cortextos#452)
  1. New feature in dashboard: Obsidian wiki viewer (feat(dashboard): Obsidian wiki viewer grandamenium/cortextos#412)
  1. New feature in dashboard: quota indicator + quota-watchdog scripts (feat(dashboard): quota indicator + quota-watchdog scripts grandamenium/cortextos#411)
  1. Bug fix in dashboard: use systemName for heartbeat lookup + agent detail link in AgentStatusGrid (fix(dashboard): use systemName for heartbeat lookup + agent detail link grandamenium/cortextos#395)
  1. telegram: add react-telegram command for single-emoji acks (telegram: add react-telegram command for single-emoji acks grandamenium/cortextos#406)
  1. telegram: stop HTML-escaping in plain-text mode (telegram: stop HTML-escaping in plain-text mode grandamenium/cortextos#402)
  1. Bug fix in cli: wire up cortextos update apply path + field-name parity (feat(cli): cortextos update — opt-in framework update with confirmation grandamenium/cortextos#421) (fix(cli): wire up cortextos update apply path + field-name parity (#421) grandamenium/cortextos#423)
  1. Bug fix in templates: set permissions.defaultMode=bypassPermissions in all agent templates (fix(template): add bypassPermissions to agent template settings.json grandamenium/cortextos#198) (fix(templates): set permissions.defaultMode=bypassPermissions in all agent templates (#198) grandamenium/cortextos#347)
  1. Bug fix in hooks: bus fan-out reachable when Telegram creds absent (closes hook-crash-alert: bus fan-out unreachable when Telegram creds absent (#298 follow-up) grandamenium/cortextos#317) (fix(hooks): bus fan-out reachable without Telegram creds (closes #317) grandamenium/cortextos#371)
  1. Bug fix in daemon: send back-online Telegram for codex-app-server runtime (codex-app-server runtime does not send Telegram boot/restart notification grandamenium/cortextos#392) (fix(daemon): send back-online Telegram for codex-app-server runtime (#392) grandamenium/cortextos#393)
  1. Bug fix in daemon: thread --model through spawn-worker to AgentPTY (closes spawn-worker silently drops --model flag; workers freeze at Claude Code model selection screen grandamenium/cortextos#283) (fix(daemon): thread --model through spawn-worker to AgentPTY (closes #283) grandamenium/cortextos#372)
  1. Bug fix in daemon: clear error message on invalid config.json (closes bug: trailing comma in config.json causes cryptic model error on agent restart grandamenium/cortextos#345) (fix(daemon): clear error message on invalid config.json (closes #345) grandamenium/cortextos#370)
  1. Bug fix in daemon: IPC distinguishes DEDUPED / NOT_FOUND / NOT_RUNNING on start/stop/restart/inject-agent (fix(daemon): IPC distinguishes DEDUPED / NOT_FOUND / NOT_RUNNING on start/stop/restart/inject-agent grandamenium/cortextos#349)
  1. Bug fix in types: AgentConfig.crash_window for PR feat(daemon): CrashLoopPauser — sliding-window crash detection + auto-pause grandamenium/cortextos#153 CrashLoopPauser (supersedes feat(daemon): CrashLoopPauser — sliding-window crash detection + auto-pause grandamenium/cortextos#153) (fix(types): AgentConfig.crash_window for PR #153 CrashLoopPauser (supersedes #153) grandamenium/cortextos#377)
  1. Bug fix in env: enforce CTX_AGENT_DIR subordination to CTX_FRAMEWORK_ROOT (security: sandbox daemon must pin CTX_FRAMEWORK_ROOT — isolation breach near-miss on PR #309 review grandamenium/cortextos#313) (fix(env): enforce CTX_AGENT_DIR subordination to CTX_FRAMEWORK_ROOT (#313) grandamenium/cortextos#348)
  1. Bug fix: replace stale CronList refs with cortextos bus list-crons (fix: replace stale CronList refs in templates and community skills grandamenium/cortextos#403)
  1. Add agentic CRM assistant community template (Add agentic CRM assistant community template grandamenium/cortextos#401)
  1. New feature in telegram: wire whisper-cli voice transcription into media pipeline (feat(telegram): wire whisper-cli voice transcription into media pipeline grandamenium/cortextos#384)
  1. Bug fix in task: bump random suffix from 3 to 8 digits to eliminate ID-collision flake (fix(task): eliminate task-ID collision flake (3→8 random digits) grandamenium/cortextos#385)
  1. New feature in codex: codex-app-server runtime parity (16-commit clean branch) (feat(codex): codex-app-server runtime parity (16-commit clean branch) grandamenium/cortextos#369)
  1. docs+skills: Agent Awareness Standard + Phase 0E services health check (orphan commits from soak) (docs+skills: Agent Awareness Standard + Phase 0E services health check (orphan commits from soak) grandamenium/cortextos#360)
  1. Windows: native claude binary detection + Task Scheduler PM2 persistence (Windows: native claude binary detection + Task Scheduler PM2 persistence grandamenium/cortextos#343)
  1. Bug fix in dashboard: suppress hydration warning on (fix(dashboard): suppress hydration warning on <body> grandamenium/cortextos#333)
  1. New feature in ops: add optional self-healing watchdog scripts under scripts/self-healing/ (feat(ops): add optional self-healing watchdog scripts grandamenium/cortextos#327)
  1. Bug fix in dashboard: hoist key onto Fragment in workflows row map (fix(dashboard): hoist key onto Fragment in workflows row map grandamenium/cortextos#324)
  1. Docs in agent-management: document hook reload lifecycle (docs(agent-management): hook reload lifecycle + hard-restart-from-CLI bug grandamenium/cortextos#323)
  1. Bug fix in pty: preserve Windows path-expansion env vars in agent PTY (fix(pty): preserve Windows path-expansion env vars in agent PTY grandamenium/cortextos#268)
  1. Bug fix in daemon: emit telegram_received bus event on inbound messages (fix(daemon): emit telegram_received bus event on inbound messages grandamenium/cortextos#267)
  1. Bug fix in dashboard: atomic CSRF refetch at submit time to defeat StrictMode mount-race (fix(dashboard): atomic CSRF refetch at submit time to defeat StrictMode mount-race grandamenium/cortextos#255)
  1. Bug fix in bus: reduce observability noise across heartbeats, events, and experiments (fix(bus): reduce observability noise across heartbeats, events, and experiments grandamenium/cortextos#242)
  1. Docs in templates: clarify update-heartbeat vs log-event-heartbeat in HEARTBEAT.md Step 1 (docs(templates): clarify update-heartbeat vs log-event-heartbeat in HEARTBEAT.md Step 1 grandamenium/cortextos#307)
  1. New feature in pty: CodexPTY adapter — exec-mode Codex CLI runtime (feat(pty): CodexPTY adapter — exec-mode Codex CLI runtime grandamenium/cortextos#322)
  1. New feature in daemon: add telegram_polling config flag to suppress poller on specialist agents (feat(daemon): add telegram_polling config flag to suppress poller on specialist agents grandamenium/cortextos#297)
  1. Bug fix in metrics: exclude info/warning severity from errors_today count (fix(metrics): exclude info/warning severity from errors_today count grandamenium/cortextos#266)
  1. New feature in cli: cortextos import-agent — upgrade path from cortextos-single (feat(cli): cortextos import-agent — upgrade path from cortextos-single (clean replacement for #205) grandamenium/cortextos#344)
  1. Bug fix in cli: invert 1M-context default for new agents — opt-in 200K fallback (fix(cli): invert 1M-context default for new agents — opt-in 200K fallback grandamenium/cortextos#201)
  1. Bug fix in bus: ping requesting agent's bot on createApproval (closes 50h+ stall) (fix(bus): ping requesting agent's bot on createApproval (closes 50h+ stall) grandamenium/cortextos#301)
  1. New feature in hooks: notify chief + analyst on agent crash (Hook 1: agent_crashed -> alert chief + analyst grandamenium/cortextos#298)
  1. New feature in kb: fault-injectable mmrag client for ingest test coverage (feat(kb): fault-injectable mmrag client for ingest test coverage grandamenium/cortextos#314)
  1. Bug fix in kb: bump ingest timeout + retry Gemini 503s (fix(kb): bump ingest timeout + retry Gemini 503s grandamenium/cortextos#309)
  1. New feature in daemon: Phase 5 external persistent crons — daemon-owned scheduler
  1. Docs in changelog: bump Unreleased to v0.2.0 + add Phase 5.4 entries
  1. Maintenance: move PHASE-N-REPORT.md files into docs/phase-reports/
  1. merge: grandamenium/main into feat/external-persistent-crons (absorbs feat(bus): hooks framework — Day-1 stub + Day-2 per-handler wiring + telemetry grandamenium/cortextos#272 hooks framework)
  1. Bug fix in daemon: drop redundant agent-name prefix from cron-teaching banner
  1. New feature in daemon: emit cron-teaching upgrade banner during migrate-crons (Part C)
  1. Docs in migration: document upgrade-cron-teaching for existing workspaces (Part B)
  1. New feature in cli: add bus upgrade-cron-teaching scanner (Part A of fix(templates): deny rules for .claude/.git/.vscode/shell configs (CC 2.1.126 regression) grandamenium/cortextos#292 follow-up)
  1. Bug fix in cron: serialize bus add/remove/update-cron to fix iter 12 lost-update race
  1. Tests in cron: pin iter 12 — concurrent bus update-cron lost-update race
  1. Bug fix in cron: persist last_fire_attempted_at to prevent crash-mid-fire double-fire
  1. Tests in cron: pin iter 10 daemon-crash mid-fire double-fire (iter 11 fix flagged)
  1. Bug fix in cron: distinguish legitimately-empty from corrupt in lastGoodSchedule fallback
  1. Tests in cron: pin remove-cron mid-fire (no double-fire) + flag iter 9 bug
  1. Bug fix in cron: lazy-create scheduler when reload hits start-window gap
  1. Bug fix in cron: defer reload for in-flight fires to prevent double-fire race
  1. Merge fix/scheduler-uses-cron-state-for-catchup into feat/external-persistent-crons
  1. Bug fix in cron: scheduler catch-up reads cron-state.json
  1. Merge fix/cron-list-display-merge into feat/external-persistent-crons
  1. Bug fix in cron: list-crons merges cron-state.json into Last Fire display
  1. Merge pull request docs(templates): replace deprecated CronCreate teaching with daemon-managed cron pattern grandamenium/cortextos#285 from grandamenium/fix/template-cron-rewrite
  1. Docs in templates: final cron-pattern straggler sweep
  1. Docs in templates: expand cron-pattern cleanup to all template+community trees
  1. Docs in templates: replace deprecated CronCreate teaching with daemon-managed cron pattern
  1. Merge pull request fix(cron): salt inject + advance nextFireAt + remove vestigial watchdogs grandamenium/cortextos#284 from grandamenium/fix/cron-busy-loop
  1. Bug fix in cron: salt inject + advance nextFireAt + remove vestigial watchdogs
  1. Bug fix in daemon: clamp session timer to int32 setTimeout max (fix(daemon): clamp session timer to int32 setTimeout max grandamenium/cortextos#282)
  1. New feature in bus: hooks framework — Day-1 stub + Day-2 per-handler wiring + telemetry (feat(bus): hooks framework — Day-1 stub + Day-2 per-handler wiring + telemetry grandamenium/cortextos#272)
  1. New feature in crons: subtask 5.7 — Phase 5 final integration & sign-off
  1. New feature in crons: subtask 5.6 — Phase 5 documentation validation
  1. New feature in crons: subtask 5.5 — Phase 5 compliance & audit verification
  1. New feature in crons: subtask 5.4 — Phase 5 performance & scaling tests
  1. New feature in crons: subtask 5.3 — Phase 5 failure mode & recovery testing
  1. New feature in crons: subtask 5.2 — Phase 5 user journey backtests
  1. New feature in crons: subtask 5.1 — Phase 5 E2E system simulation (7 scenarios)
  1. New feature in crons: subtask 4.6 — Phase 4 dashboard full backtesting + sign-off
  1. New feature in crons: subtask 4.5 — test-fire button with confirmation + cooldown + manual-fire opt-out
  1. New feature in crons: subtask 4.4 — cron fleet health dashboard with gap detection
  1. New feature in crons: subtask 4.3 — cron history viewer with pagination + filter + export
  1. New feature in crons: subtask 4.2 — cron CRUD forms + mutation IPC
  1. New feature in crons: subtask 4.1 — dashboard workflows page (read-only list + IPC)
  1. New feature in crons: subtask 3.5 — Phase 3 documentation backtesting
  1. New feature in crons: subtasks 3.1-3.4 — comprehensive docs pass
  1. New feature in crons: subtask 2.6 — Phase 2 full backtesting
  1. New feature in crons: subtask 2.5 — multi-agent integration test
  1. New feature in crons: subtask 2.4 — cron-management skill full rewrite
  1. New feature in crons: subtask 2.3 — scrub /loop refs from community templates + skills
  1. New feature in crons: subtask 2.2 — auto-migration config.json → crons.json
  1. New feature in crons: subtask 2.1 — daemon-managed cron auto-load on boot
  1. New feature in crons: subtask 1.6 — Phase 1 full backtesting integration test
  1. New feature in crons: subtask 1.5 — cron execution logging
  1. New feature in crons: subtask 1.4 — bus commands for external crons
  1. New feature in crons: subtask 1.3 — daemon scheduling engine + cron parser
  1. New feature in crons: subtask 1.2 — atomic file I/O module
  1. New feature in crons: subtask 1.1 — CronDefinition schema + path constants
  1. Bug fix in telegram: validate BOT_TOKEN and CHAT_ID against Telegram API before enable + setup writes .env (fix(telegram): validate BOT_TOKEN and CHAT_ID at add-agent and setup time grandamenium/cortextos#235)
  1. Bug fix in daemon: extend gap detection to cron-expression crons (fix(daemon): gap detection skips cron-expression crons — only interval-based crons monitored grandamenium/cortextos#169) (fix(daemon): extend gap detection to cron-expression crons (#169) grandamenium/cortextos#184)
  1. Bug fix in telegram: switch to HTML parse mode — eliminates silent content drops (fix(telegram): switch to HTML parse mode — eliminates silent content drops grandamenium/cortextos#181)
  1. Bug fix in daemon: use CronCreate directly on boot to skip /loop cloud-prompt (fix(daemon): use CronCreate directly on boot to skip /loop cloud-prompt grandamenium/cortextos#210)
  1. Bug fix in bus: hard-restart now sends IPC restart-agent to terminate the session (fix(bus): hard-restart now sends IPC restart-agent to terminate the session grandamenium/cortextos#217)
  1. Bug fix in daemon: guard worker PTY null-write + add crash visibility (fix(daemon): guard worker PTY null-write + add crash visibility grandamenium/cortextos#223)
  1. Bug fix in test: use relative timestamps in channels route test (fix(test): use relative timestamps in channels route test grandamenium/cortextos#226)
  1. Bug fix in daemon: make handoff pickup message a concrete tool call, not prose instruction
  1. Bug fix in daemon: pass handoff doc on Tier 3 force-restart + elevate pickup message priority (fix(daemon): pass handoff doc on Tier 3 force-restart + elevate pickup message priority grandamenium/cortextos#197)
  1. Bug fix in daemon: guard PTY write callbacks against null to prevent daemon crash (fix(daemon): guard PTY write callbacks against null to prevent daemon crash grandamenium/cortextos#196)
  1. Bug fix in daemon: pre-arm .force-fresh at Tier 2 handoff to break --continue loop (fix(daemon): pre-arm .force-fresh at Tier 2 handoff to break --continue restart storm grandamenium/cortextos#194)
  1. Merge pull request feat(hermes): Hermes agent runtime support + ctx-watchdog UX fixes grandamenium/cortextos#174 from grandamenium/fix/ctx-watchdog-ux
  1. New feature in daemon: context-aware handoff + hard restart watchdog
  1. Bug fix in daemon: stagger gap nudges + guard duplicate cron verification (issue fix(daemon): gap nudges fire in burst + cron verification silently drops on timeout grandamenium/cortextos#182) (fix(daemon): stagger gap nudges + guard duplicate cron verification (#182) grandamenium/cortextos#183)
  1. Bug fix in daemon: skip type=disabled cron entries in gap detector and verification (fix(daemon): skip type=disabled cron entries in gap detector and verification grandamenium/cortextos#176)
  1. Bug fix in pty: rotate stdout.log at 50 MB to prevent file-cache pressure (fix(pty): rotate stdout.log at 50 MB to prevent file-cache pressure grandamenium/cortextos#175)
  1. Bug fix in ctx-watchdog: clear stale handoff deadline on new session
  1. Bug fix in ctx-watchdog: persist circuit breaker state across --continue restarts
  1. New feature in hermes: Hermes agent runtime support
  1. Bug fix in ctx-watchdog: Tier 1 Telegram warning fires once per session only
  1. Bug fix in daemon: restore getAgentDir/getConfig + consumeHandoffBlock to AgentProcess
  1. Bug fix in ux: update AGENTS.md templates to skip boot msg on handoff restarts
  1. Bug fix in ux: suppress boot message and cold-restart phrasing on handoff restarts
  1. New feature in community: add security agent template
  1. New feature in daemon: context-aware handoff + hard restart watchdog
  1. Merge pull request fix(org): normalize org casing at KB-write and dashboard sync grandamenium/cortextos#164 from grandamenium/fix/org-casing-normalization-clean
  1. Merge pull request feat(bus): auto-emit activity events from common bus commands grandamenium/cortextos#163 from grandamenium/feat/bus-auto-emit-activity-events-clean
  1. Merge pull request fix(cli): graceful exit when BOT_TOKEN or SLACK_BOT_TOKEN not configured grandamenium/cortextos#162 from grandamenium/fix/graceful-missing-bot-token-clean
  1. Bug fix in org: normalize org casing at KB-write and dashboard sync
  1. init
  1. New feature in bus: auto-emit activity events from send-message, ack-inbox, update-heartbeat, send-telegram
  1. init
  1. Bug fix in cli: graceful exit with clear message when BOT_TOKEN or SLACK_BOT_TOKEN not configured
  1. Merge pull request fix(daemon): prevent duplicate crons on rapid session restarts grandamenium/cortextos#114 from noogalabs/clean/cron-dedup-session-restore
  1. Merge pull request fix(org): use readdirSync for exact-case match — fixes macOS case-insensitive fs grandamenium/cortextos#131 from noogalabs/fix/telegram-conflict-org-case-sensitivity
  1. Merge pull request fix(pr140): replace hardcoded speaker name, revert catalog churn grandamenium/cortextos#148 from grandamenium/fix/pr140-hardcoded-name-and-catalog-churn
  1. New feature in community: add local-ultrareview skill — 3-stage Opus pipeline with live logs and implementation plan
  1. New feature in skills: post-merge npm-audit gate in upstream merge workflow (feat(skills): post-merge npm-audit gate in upstream sync workflow grandamenium/cortextos#147)
  1. Bug fix in security: bump next@16.2.4 + hono@4.12.14 — close GHSA-q4gf-8mx6-v5v3 + GHSA-458j-xx4x-4375 (fix(security): bump next@16.2.4 + hono@4.12.14 — close GHSA-q4gf-8mx6-v5v3 + GHSA-458j-xx4x-4375 grandamenium/cortextos#146)
  1. Bug fix in dashboard: reverse-proxy login failures — TRUST_PROXY docs, CF-Connecting-IP fallback, rate-limit error message (fix(dashboard): reverse-proxy login failures (issue #138) grandamenium/cortextos#144)
  1. Bug fix in bus: manage-cycle list respects --agent filter instead of returning global list (fix(bus): manage-cycle list respects --agent filter instead of returning global list grandamenium/cortextos#143)
  1. Bug fix in cli: goals generate-md accepts mixed-case agent and org names (fix(cli): goals generate-md accepts mixed-case agent and org names grandamenium/cortextos#142)
  1. New feature in telegram: message_reaction update routing — surface emoji reactions to the agent (feat(telegram): message_reaction update routing — surface emoji reactions to the agent grandamenium/cortextos#141)
  1. Bug fix in daemon: treat missing cron-state entry as cold-start, not skip (issue fix(daemon): gap detection cold-start -- treat missing cron-state entry as infinite gap grandamenium/cortextos#110) (fix(daemon): treat missing cron-state entry as cold-start, not skip (issue #110) grandamenium/cortextos#120)
  1. New feature in scripts: add setup-hooks.sh + pre-push build+test gate (feat(scripts): add setup-hooks.sh + pre-push build+test gate grandamenium/cortextos#121)
  1. Bug fix in pr140: replace hardcoded 'Greg' with ADMIN_USERNAME, revert catalog.json churn
  1. New feature in telegram: include recent conversation history in message context
  1. Bug fix in dashboard: org filter not applied on page load; login redirect uses wrong URL (fix(dashboard): org filter not applied on page load; login redirect uses wrong URL grandamenium/cortextos#139)
  1. Bug fix in hooks: remove hook-extract-facts from PreCompact — never worked, revert to simple notification (fix(hooks): remove hook-extract-facts from PreCompact grandamenium/cortextos#135)
  1. New feature: add agentcard-purchase skill to community catalog
  1. Bug fix in cli: add CLAUDE_CODE_DISABLE_1M_CONTEXT=true to new agent .env template (fix(cli): add CLAUDE_CODE_DISABLE_1M_CONTEXT=true to new agent .env template grandamenium/cortextos#137)
  1. Bug fix in hooks: add timeouts to PreCompact hooks to prevent compaction abort (fix(hooks): add timeouts to PreCompact hooks to prevent compaction abort grandamenium/cortextos#134)
  1. Bug fix in org: use readdirSync for exact-case match to fix macOS case-insensitive fs
  1. Bug fix in pr99: apply M2C1 stuck detector to template m2c1-worker skill (fix(pr99): apply M2C1 stuck detector to m2c1-worker skill template grandamenium/cortextos#129)
  1. Bug fix in pr102: move rate-limit-management to community/skills/ (fix(pr102): move rate-limit-management skill to community/skills/ grandamenium/cortextos#130)
  1. Bug fix in pr98: apply M2C1 plan/act gate to template m2c1-worker skill (fix(pr98): apply M2C1 Plan/Act gate to m2c1-worker skill template grandamenium/cortextos#128)
  1. Bug fix in pr97: add delegation-matrix to community/skills/ and m2c1-worker template (fix(pr97): move delegation-matrix skill to community/skills/ + m2c1-worker template grandamenium/cortextos#127)
  1. Bug fix in pr96: move opencli to community/skills/ (fix(pr96): move opencli skill to community/skills/ grandamenium/cortextos#126)
  1. Bug fix in pr95: move officecli to community/skills/ (fix(pr95): move officecli skill to community/skills/ grandamenium/cortextos#125)
  1. Bug fix in pr93: move obsidian-log to community/skills/ (fix(pr93): move obsidian-log skill to community/skills/ grandamenium/cortextos#124)
  1. Bug fix in pr92: move framework-upstream-auto-update to community/skills/ (fix(pr92): move framework-upstream-auto-update skill to community/skills/ grandamenium/cortextos#123)
  1. Bug fix in hooks: quiet hours + dedup + rate-limit reclassification for crash alert (fix(hooks): quiet hours + dedup + rate-limit reclassification for crash alert grandamenium/cortextos#109)
  1. Bug fix in env: relax CTX_ORG validation to path-traversal-only (fix(env): relax CTX_ORG validation to path-traversal only grandamenium/cortextos#117)
  1. New feature in task: atomic claim, audit log, dependency DAG, semantic compaction (feat(task): atomic claim, audit log, dependency DAG, semantic compaction grandamenium/cortextos#116)
  1. Bug fix in catalog+dashboard+cli: install path, SIGHUP survival, org normalization, Hono CVEs (fix(catalog+dashboard+cli): install path, SIGHUP survival, org normalization, Hono CVEs grandamenium/cortextos#115)
  1. Bug fix in daemon: prevent duplicate crons on rapid session restarts
  1. Bug fix in daemon: resolve Telegram photo paths from config.working_directory (BUG-049) (fix(daemon): resolve Telegram photo paths from config.working_directory (BUG-049) grandamenium/cortextos#108)
  1. Bug fix in bus: auto-notify assignee when create-task is called with --assignee (issue create-task should auto-notify the assignee via send-message grandamenium/cortextos#78) (fix(bus): auto-notify assignee on create-task (issue #78) grandamenium/cortextos#91)
  1. Docs in templates: replace Playwright MCP references with agent-browser CLI (docs(skills): adopt agent-browser CLI in templates (replaces Playwright MCP guidance) grandamenium/cortextos#64)
  1. Bug fix in task: cross-org lookup for update-task and complete-task via findTaskFile helper (fix(task): cross-org lookup for update-task and complete-task via findTaskFile helper grandamenium/cortextos#61)
  1. Bug fix in kb: warn-and-skip on missing knowledge-base config instead of unhandled crash (fix(kb): warn-and-skip on missing knowledge-base config instead of unhandled crash grandamenium/cortextos#60)
  1. Bug fix in telegram: retry sendMessage with parse_mode=null on parse-entity errors + --plain-text opt-in (fix(telegram): retry sendMessage with plain text on parse-entity errors + add --plain-text opt-in grandamenium/cortextos#59)
  1. Bug fix in ci: install dashboard deps in test job for vitest dashboard coverage (fix(ci): install dashboard deps in test job for vitest dashboard coverage grandamenium/cortextos#111)
  1. New feature in dashboard: deliverable outputs with preview panel, allowed roots, XSS fix (feat: deliverable outputs with preview panel, allowed roots, and enforcement toggle grandamenium/cortextos#52)
  1. New feature in dashboard: Comms Hub — real-time agent communication center (feat: add Comms Hub — real-time agent communication center for dashboard grandamenium/cortextos#47)
  1. New feature in daemon: persist cron fire timestamps and add gap-detection nudge (issue feat(daemon): AgentManager should persist and re-arm cron jobs across session restarts grandamenium/cortextos#67) (feat(daemon): persist cron fire timestamps and add gap-detection nudge (issue #67) grandamenium/cortextos#68)
  1. Bug fix in daemon: replace exec() with execFile() in heartbeat watchdog (issue SEC: fast-checker.ts uses exec() with string interpolation for heartbeat command grandamenium/cortextos#54) (fix(daemon): replace exec() with execFile() in heartbeat watchdog (issue #54) grandamenium/cortextos#55)
  1. New feature in approvals: Telegram inline-button approvals on the activity channel (feat(approvals): Telegram inline-button approvals on the activity channel grandamenium/cortextos#63)
  1. Bug fix in daemon: classify PM2 shutdown PTY exits as planned stops, persist crash audit to restarts.log (fix(daemon): classify PM2 shutdown PTY exits as planned stops grandamenium/cortextos#57)
  1. Bug fix in pty: redact JWT-shaped tokens from OutputBuffer before they reach memory or disk (fix(pty): redact JWT-shaped tokens from OutputBuffer grandamenium/cortextos#56)
  1. Bug fix in telegram: add fetch timeout so poller cannot silently hang (fix(telegram): add fetch timeout so poller cannot silently hang grandamenium/cortextos#86)
  1. Bug fix in agents-md: single-quote all send-telegram examples to prevent dollar-sign stripping (BUG-052) (fix(agents-md): single-quote send-telegram examples to prevent dollar-sign stripping (BUG-052) grandamenium/cortextos#48)
  1. Bug fix in daemon: guard auto-start behind require.main to prevent accidental spawn (fix(daemon): guard auto-start behind require.main to prevent accidental spawn grandamenium/cortextos#45)
  1. Bug fix in daemon: single-quote send-telegram instructions to prevent $-number stripping (BUG-050) (fix(daemon): single-quote send-telegram instructions to prevent $-number stripping (BUG-050) grandamenium/cortextos#41)
  1. Bug fix in telegram: only advance poller offset after handlers succeed (fix(telegram): only advance poller offset after handlers succeed grandamenium/cortextos#30)
  1. New feature in daemon: add 50-min heartbeat watchdog to fast-checker (Weekly upstream sync 2026-08-09 (255 commits) #26)
  1. Bug fix in dashboard: dashboard login over non-localhost (Tailscale/LAN/VPN) (Weekly upstream sync 2026-08-02 (251 commits) #25)
  1. Bug fix in daemon: re-read max_session_seconds on timer fire (BUG-048) + add usage API script (fix(daemon): re-read max_session_seconds on timer fire (BUG-048) grandamenium/cortextos#37)
  1. Bug fix in daemon: poll isAlive() before pty.kill() to prevent SIGHUP on clean exit (BUG-032) (Weekly upstream sync 2026-06-09 (9 commits) #19)
  1. Bug fix in daemon: add signal re-entrancy guard to prevent SIGTERM cascade (BUG-003) (Weekly upstream sync 2026-06-08 (9 commits) #18)
  1. Bug fix in telegram: relative paths for media files (BUG-046) (Weekly upstream sync 2026-07-26 (247 commits) #24)
  1. New feature: comprehensive Windows support (14 fixes) (Weekly upstream sync 2026-07-05 (246 commits) #22)
  1. New feature in daemon: auto-verify cron restoration after agent bootstrap (Weekly upstream sync 2026-06-10 (9 commits) #20)
  1. Bug fix in dashboard: SparkLine container dimensions + Brand Voice markdown rendering (Weekly upstream sync 2026-06-28 (3 commits) #21)
  1. Docs: remove hardcoded test count from CLAUDE.md
  1. Bug fix in agents: guard cwd fallback when CTX_FRAMEWORK_ROOT is explicitly set
  1. Bug fix in daemon: make AgentManager multi-org aware (BUG-043) (Weekly upstream sync 2026-06-07 (9 commits) #17)
  1. Bug fix in cli: validate agent name in add-agent to match resolveEnv (BUG-041) (Weekly upstream sync 2026-06-06 (8 commits) #16)
  1. Bug fix in daemon: close PTY exit/stop timing race that bypassed BUG-011 (BUG-040, closes BUG-038) (Weekly upstream sync 2026-06-05 (2 commits) #15)
  1. Bug fix: close-out batch (BUG-015, 021, 031, 032; verify-close 008, 009, 026, 027) (Weekly upstream sync 2026-06-03 (32 commits) #14)
  1. Bug fix: batch 7 stability/UX fixes (BUG-002, 013, 016, 019, 033, 034-partial, 035) (Weekly upstream sync 2026-06-02 (32 commits) #13)
  1. Bug fix in cli: write lifecycle markers on disable/stop to prevent false CRASH alarms (BUG-036) (Weekly upstream sync 2026-06-01 (32 commits) #12)
  1. Bug fix in daemon: close PTY race in AgentProcess.stop() (BUG-011) (Weekly upstream sync 2026-05-31 (32 commits) #11)
  1. Bug fix in cli: require --all to stop every agent (Weekly upstream sync 2026-05-09 (83 commits) #5)
  1. Bug fix in daemon: rebuild Telegram poller on restartAgent (fix: cherry-pick 3 wedge-mitigation fixes from upstream #4)
  1. Bug fix in onboarding: pass --instance flags + reuse empty default instance (Weekly upstream sync 2026-05-15 (4 commits) #10)
  1. New feature in install: add CORTEXTOS_BRANCH env var for testing pre-merge fixes (Weekly upstream sync 2026-05-14 (4 commits) #9)
  1. Bug fix in daemon: respect enabled-agents.json in discoverAndStart + listAgents (Weekly upstream sync 2026-05-13 (4 commits) #8)
  1. Bug fix in install: chmod 600 the instance .env file (Weekly upstream sync 2026-05-12 (2 commits) #7)
  1. Merge pull request Silence routine --continue restart pings; reword crash recovery #3 from grandamenium/fix/fast-checker-test-sync
  1. Tests in fast-checker: sync isAgentActive tests with hook-based implementation
  1. Merge pull request Weekly upstream sync 2026-05-03 (8 commits) #2 from grandamenium/fix/typing-indicator-always-on
  1. Merge pull request fix(bus): evaluate-experiment --score stored in own field; value positional now optional #1 from grandamenium/fix/restart-all-race-condition
  1. Bug fix in fast-checker: remove stdout.log size check from isAgentActive()
  1. Bug fix in daemon: queue pending restarts when stop+start race on restart-all

How to merge

  • Reply to this PR with merge all to take everything.
  • Reply with merge 1 3 5 (numbers from the list above) to cherry-pick those.
  • Reply with skip to dismiss this whole PR.
  • If there's a merge conflict, you'll see it on the Files tab. Logan tells boss what to do.

This PR was opened automatically by boss every Sunday at 6:30pm Chicago time.

grandamenium and others added 30 commits April 7, 2026 10:02
fix(daemon): queue pending restarts when stop+start race on restart-all
fix(fast-checker): remove stdout.log size check from isAgentActive() — fixes permanent typing indicator
…tation

PR #2 removed the stdout.log size check from isAgentActive() but the
test still expected the old file-growth behavior. Updated tests to
match the new hook-based approach (lastMessageInjectedAt + last_idle.flag).

Added two new tests covering the hook-based path:
- returns true when message injected and no idle flag yet
- returns false when idle flag is newer than last injection

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
test(fast-checker): sync isAgentActive tests with hook-based implementation
The .env file in ~/.cortextos/<instance>/ was being written with the
default mode (typically 644, world-readable). Although the parent
directory is 700 so cross-user access is blocked, .env files
conventionally hold secrets and any future feature writing a token
or credential to this file would silently expose it to any local
process running as the same user.

The fix mirrors the existing pattern used a few lines below for
bus-signing-key (line 269) and dashboard.env (line 306): one chmod
call wrapped in a try/catch to ignore the no-op on Windows.

Verified locally:
  - npm test → 382/382 passed
  - chmodSync was already imported
  - existing .env files are unaffected (the chmod is inside the
    `if (!existsSync(envPath))` block)

Refs: BUG-001 in our internal tracker. Found during a fresh-install
audit on a clean macOS install.

Co-authored-by: grandamenium <noreply@anthropic.com>
…ents (#8)

The daemon's discoverAndStart loop scans the framework orgs/ directory for
agent dirs and starts each one, but never read the instance-level
enabled-agents.json that the CLI's `cortextos enable`/`disable` commands and
the dashboard's lifecycle API write to. Result: a user could `cortextos
disable foo`, see no error, restart the daemon, and find that foo was running
again — the disable was a no-op across restarts. The two views never agreed.

Symmetrically, `cortextos list-agents` (via `bus/agents.ts:listAgents`)
treated enabled-agents.json as authoritative and skipped the directory scan
entirely if the file existed. This caused list-agents to miss any agent that
the daemon had discovered on disk but that wasn't in the file (e.g., an agent
created via `cortextos add-agent` against a different instance, or a directory
added manually).

This commit aligns both sides on the same logical model:

  The framework orgs/ directory is the canonical "what exists" set.
  enabled-agents.json provides explicit user-set overrides on top of that.
  Default for a discovered agent with no entry in the file is enabled.

Two surgical changes:

1. src/daemon/agent-manager.ts: discoverAndStart now reads
   ${ctxRoot}/config/enabled-agents.json and skips any discovered agent whose
   entry has `enabled: false`. The existing per-agent config.json `enabled:
   false` check is preserved (it takes precedence — both gates are checked).
   A new private helper readInstanceEnableList() encapsulates the file read
   with safe fallback on missing/corrupt file.

2. src/bus/agents.ts: listAgents now always scans orgs/ directories. The
   "skip dir scan if enabled-agents.json exists" branch is removed. The file
   is still read and used to override the default enabled state on each
   discovered agent. Stale file entries (have an entry but no matching dir)
   are appended at the end so users can see them and clean them up.

Tests added (7 new, all passing locally):

  tests/unit/bus/agents.test.ts:
    - shows agents from dir scan even when enabled-agents.json exists
    - respects enabled: false from enabled-agents.json for agents in dir scan

  tests/unit/daemon/agent-manager.test.ts (NEW file):
    - skips agents marked enabled: false in enabled-agents.json
    - starts all discovered agents when enabled-agents.json is missing
    - starts all discovered agents when enabled-agents.json is empty {}
    - still respects per-agent config.json enabled: false (existing behavior)
    - handles corrupt enabled-agents.json by defaulting to enabled-all

Total test count: 382 → 389. All passing.

What this PR does NOT change:
  - cortextos enable/disable: keep writing to enabled-agents.json (now actually
    respected by the daemon)
  - cortextos install: keeps creating an empty enabled-agents.json
  - cortextos add-agent: keeps writing to the file
  - dashboard: keeps writing to the file via existing API routes
  - All IPC handlers in src/daemon/ipc-server.ts: unchanged
  - The schema of enabled-agents.json: unchanged
  - All other CLI subcommands: unchanged

Backward compatible. No migration. Existing installs benefit from the fix
without any user action — disable now actually persists across daemon
restarts.

Refs: BUG-028 in our internal tracker. The structural root cause behind
several other entries (BUG-024, BUG-025, possibly BUG-009).

Co-authored-by: grandamenium <noreply@anthropic.com>
…es (#9)

The canonical curl install pulls install.mjs from a specific branch URL,
but install.mjs itself unconditionally clones the default branch (`main`)
from CORTEXTOS_REPO. This means there's no way to test a fix in isolation
via the curl install path before it merges to main — you have to either
manually `git clone --branch` and run install.mjs locally, or wait until
your fix is merged to verify it via the canonical install path.

This commit adds CORTEXTOS_BRANCH support, mirroring the existing
CORTEXTOS_REPO and CORTEXTOS_DIR overrides:

  CORTEXTOS_BRANCH=fix/some-fix curl -fsSL \
    https://raw.githubusercontent.com/grandamenium/cortextos/fix/some-fix/install.mjs \
    | node

Both URL components (the branch in the curl path AND the env var) point
at the same branch — install.mjs is fetched FROM that branch, and then
clones it via `git clone --branch ${REPO_BRANCH}`. The branch name is
validated against a strict regex (a-zA-Z0-9._/-) to prevent shell
injection via the env var.

Default behavior unchanged: without CORTEXTOS_BRANCH set, install.mjs
clones `main` exactly as before. Backward compatible.

This is a methodology improvement that lets us run the standard 2-cycle
test loop (install pre-fix from main → merge → install post-fix from
main) against any branch in 1 install command instead of needing a
3-step manual git clone workaround for each branch test.

Verified locally:
  - npm test → 389/389 passing (no tests for install.mjs itself; this
    just confirms nothing else broke)
  - The diff is text-only changes to install.mjs
  - Branch validation regex matches all standard git ref characters

Co-authored-by: grandamenium <noreply@anthropic.com>
…#10)

The /onboarding skill had two related bugs that produced split-brain
state on every clean install:

BUG-029: every CLI subcommand defaults --instance to literal 'default'
when neither --instance nor CTX_INSTANCE_ID is set. The skill set a
local INSTANCE_ID variable but never exported it, and only passed
--instance to `init` (line 192). add-agent (line 300), enable (line
333), and ecosystem (line 470) all silently wrote to ~/.cortextos/default/
even though the user's actual instance was cortextos1 (or higher). This
left the agent registration in default's enabled-agents.json while the
daemon ran on cortextos1, splitting the user's view from the daemon's
view.

BUG-017: the auto-instance-numbering loop unconditionally picked
cortextosN even though `cortextos install` (called earlier in the flow)
always creates an empty default/ instance. Result: every install ended
with TWO instance dirs — an orphaned default/ that nothing used, and a
cortextos1/ with the actual state. Repeating the install accumulated
orphans (default, cortextos1, cortextos2, ...).

The two bugs reinforced each other. With BUG-029 the wrong-instance
writes scattered state across the orphan default/ created by BUG-017.

Fixes:

1. Auto-numbering now reuses default/ if it exists and is empty
   (enabled-agents.json content is `{}`, the fresh-install state).
   Otherwise falls back to the next free cortextosN slot. This means
   the typical "fresh install + run /onboarding" flow ends with ONE
   instance dir, not two.

2. Added --instance "${INSTANCE_ID}" to all four CLI calls in the
   skill: init (already had it), add-agent, enable, ecosystem. This
   guarantees every write lands in the right instance dir.

3. Added `export CTX_INSTANCE_ID` and `export CTX_ROOT` so any indirect
   subprocess (e.g. PM2 reading ecosystem.config.js, the bus catalog
   command, the dashboard env loader) inherits the right instance.
   Belt and suspenders alongside the explicit flags.

4. Replaced the misleading note that said "CTX_INSTANCE_ID is set
   automatically by the framework" — it isn't. Replaced with an
   explicit IMPORTANT block telling future maintainers that every CLI
   call MUST pass --instance and explaining why.

Test plan:
  - npm test → 389/389 still passing (no source changes; skill is .md)
  - Manual: ./reset.sh && curl ... | node && claude ~/cortextos
    && /onboarding → after completion, `ls ~/.cortextos` should show
    ONE instance dir, not two; that dir's enabled-agents.json should
    contain the orchestrator entry.

Refs: BUG-017, BUG-029 in our internal tracker. Closes both. The
structural bug in the daemon (BUG-028) is already fixed in #8 — this
PR fixes the user-visible symptoms in the onboarding flow that were
producing the same wrong-instance state.

Co-authored-by: grandamenium <noreply@anthropic.com>
* fix(daemon): rebuild Telegram poller on restartAgent

restartAgent() previously stopped and restarted the agent process and
fast checker but never touched the TelegramPoller. After every IPC
restart-agent call the old poller kept running in stale state forever
and no new poller was created — the agent silently stopped receiving
Telegram messages until the daemon itself restarted.

Delegate to stopAgent + startAgent so the poller, checker, TelegramAPI,
crash callback, and slash-command registration are all rebuilt from
fresh .env credentials. Participates in the pendingRestarts race
protection (commit 39163d9) for free.

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

* test(agent-manager): add regression tests for BUG-007 restartAgent fix

Two unit tests pinning the new restartAgent behavior:

1. delegates to stopAgent then startAgent (in order) — verifies the
   new code path uses the high-level methods (which clean up and
   rebuild ALL per-agent resources, including the Telegram poller)
   rather than the partial inline cleanup that previously missed
   poller, TelegramAPI, crash callbacks, and slash commands.

2. is a no-op when the agent does not exist — verifies the new
   existence guard so a stale dashboard or IPC client can't crash
   the daemon by calling restart-agent on a missing name.

Mocks reuse the existing AgentProcess/FastChecker/TelegramAPI/
TelegramPoller stubs added for the BUG-028 tests. Total test count:
389 → 391, all passing.

---------

Co-authored-by: James Goldbach <cortextos@Jamess-Mac-mini.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
`cortextos stop` with no arguments silently stopped every running
agent in sequence, cascading the entire fleet. The description
incorrectly claimed it would "stop the daemon," which led at least
one autonomous agent to run it expecting `pm2 stop cortextos-daemon`
semantics and instead kill six production agents.

The dangerous behavior now requires an explicit \`--all\` flag. The
no-arg form prints a helpful error pointing at the three legitimate
intents (stop one, stop all, stop the daemon) and exits with code 2
before any IPC call. Description and argument help text rewritten to
be accurate and to explicitly disclaim daemon-stop semantics.

Co-authored-by: James Goldbach <cortextos@Jamess-Mac-mini.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
The current AgentProcess.stop() has a race window: it sets stopping=true,
sends Ctrl-C/`/exit`/SIGKILL to the PTY, then sets stopping=false at the
end. The PTY's exit callback (set in start() at line 81) fires
asynchronously and may run AFTER stopping=false. The handleExit
function's `if (this.stopping) return` guard then doesn't catch it, and
the function falls through to crash recovery: increments crashCount,
sets status='crashed', schedules a setTimeout restart. So an agent we
*intentionally* stopped gets resurrected ~5-10 seconds later under the
wrong assumption that it crashed.

We saw direct evidence of this race in PR #4's cycle 2 daemon log: the
AgentManager-level pendingRestarts protection fired TWICE during a
single restart sequence, racing two startAgent calls through the same
flow. The pendingRestarts logic was a workaround for the underlying
race fixed by this PR.

The fix:

1. Add `exitPromise` and `resolveExit` fields to AgentProcess. start()
   creates a fresh promise; the existing onExit handler resolves it
   AFTER calling handleExit. stop() awaits this promise (with a 5-second
   safety timeout for hung PTYs) AFTER pty.kill() and BEFORE setting
   stopping=false. This guarantees the exit handler has fired and seen
   stopping=true (so it skipped crash recovery) before stopping is reset.

2. Replace sessionRefresh()'s body with `await this.stop(); await
   this.start()`. The previous inline implementation duplicated the stop
   logic AND had a separate bug where the OLD pty's exit handler could
   fire AFTER the NEW pty was set up, nulling out the wrong reference.
   Delegating to stop() + start() inherits the BUG-011 fix automatically
   AND eliminates that duplicate-pty bug for free. (Same lesson as
   PR #4's restartAgent fix.)

Tests added (4 new, all passing):

  tests/unit/daemon/agent-process.test.ts (NEW file):
    - stop() awaits the PTY exit handler before resolving
    - stop() does NOT trigger crash recovery on intentional stop (the
      regression test for the bug we're fixing)
    - handleExit DOES trigger crash recovery on UNINTENTIONAL exit
      (regression check — make sure we didn't break real crash recovery)
    - sessionRefresh() delegates to stop() then start() (in order)

Total test count: 391 → 395, all passing.

What this PR does NOT change:
  - The stopping flag's existence or its semantics — the only change is
    WHEN it gets reset (after the exit fires, not before)
  - The handleExit function — left untouched, just gets resolveExit
    called AFTER it
  - The crash recovery branch — left untouched, regression test pins it
  - The AgentManager-level pendingRestarts logic — separate concern
    (BUG-031 if it doesn't go away after this lands)

Refs: BUG-011 in our internal tracker. Likely also closes BUG-010
(boris2 SIGHUP code 129 on restart) which was the symptom of the
spurious crash recovery firing on an intentional stop, then start()
running into a half-cleaned-up state.

Co-authored-by: grandamenium <noreply@anthropic.com>
…ASH alarms (BUG-036) (#12)

The SessionEnd crash-alert hook (src/hooks/hook-crash-alert.ts) determines
whether an agent's exit was a crash by checking for marker files in
~/.cortextos/<inst>/state/<agent>/. Before this fix, `cortextos disable`
and `cortextos stop` did not write any marker, so every intentional shutdown
was misclassified as a crash and triggered a false 🚨 CRASH alarm via
Telegram. This was trust-destroying — once a user saw one false alarm,
they stopped trusting all future crash alarms, including real ones.

Fix:

1. src/hooks/hook-crash-alert.ts — add two new marker types (.user-disable
   and .user-stop) with distinct emojis (⏸️ ⏹️) so users can tell at a
   glance whether they ran disable (semi-permanent) vs stop (transient).

2. src/cli/enable-agent.ts — disableAgentCommand now writes a .user-disable
   marker via writeDisableMarker() BEFORE the IPC stop-agent call. Helper
   is exported for unit testing.

3. src/cli/stop.ts — stopCommand now writes a .user-stop marker via
   writeStopMarker() in both the single-agent and --all branches BEFORE
   the IPC stop-agent call. Helper is exported for unit testing.

4. tests/unit/cli/lifecycle-markers.test.ts (NEW) — regression tests for
   both helpers covering: correct path/content, mkdirSync of missing dirs,
   error swallowing on filesystem failure, no collision between disable
   and stop markers for the same agent.

Pattern matches src/cli/bus.ts:1285-1289 and :1355-1358 (the proven
marker-write pattern from soft-restart, verified working in Phase 4 of
the core stability test plan).

Out of scope for this PR (separate follow-ups):
- daemon SIGTERM shutdown path (different code path in agent-manager.ts)
- broader lifecycle UX policy / 12-row test matrix (BUG-034)
- dashboard restart events (BUG-030)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: grandamenium <noreply@anthropic.com>
…tial, 035) (#13)

* fix: batch 7 stability/UX fixes (BUG-002, 013, 016, 019, 033, 034 partial, 035)

Multi-bug batch PR consolidating seven independent fixes into one
install/onboarding test cycle. Each fix is isolated and individually
small; together they close one P0-relevant cluster (false-CRASH
elimination), three P1 reliability fixes, two P2 robustness fixes,
and one P3 tunable.

BUG-002 (P1) — ecosystem.config.js bakes CTX_INSTANCE_ID at PM2 load
  src/cli/ecosystem.ts now emits raw JS that resolves
  process.env.CTX_INSTANCE_ID at PM2 startup time, with the install
  default as a fallback. Instance switching no longer requires
  re-running cortextos ecosystem.

BUG-013 (P2) — corrupt enabled-agents.json silently destroys state
  src/cli/enable-agent.ts:readEnabledAgents() now backs up corrupt
  files as enabled-agents.json.broken-<timestamp>, logs a warning,
  and validates shape (must be a JSON object). Previously, parse
  failures returned {} silently and the next write overwrote the
  corrupt file with {}, destroying user state with no warning.

BUG-016 (P3) — max_restarts: 10 too low
  Bumped to 50 in the generated ecosystem.config.js. PM2 max_restarts
  is independent of in-daemon agent crash counting.

BUG-019 (P1) — dashboard runs as npm run dev outside PM2
  Generated ecosystem.config.js now includes a cortextos-dashboard
  PM2 entry alongside cortextos-daemon. Dashboard now gets restart-
  on-crash, log files in ~/.pm2/logs/, and reboot survival.

BUG-033 (P2) — /onboarding polls Telegram AFTER user confirms send
  Reordered .claude/commands/onboarding.md instructions: the LLM no
  longer waits for typed confirmation before running the long-poll
  loop. The 30s timeout on getUpdates IS the user confirmation window.

BUG-034 partial (P1) — daemon SIGTERM shutdown false CRASH alarms
  AgentManager.stopAll() now writes a .daemon-stop marker in each
  agent state dir BEFORE stopping it. The SessionEnd crash-alert
  hook reads the marker and reports a clean daemon shutdown
  notification instead of a false CRASH. Eliminates the per-agent
  false-crash flood on pm2 restart cortextos-daemon.

BUG-035 (P1) — cortextos enable is cwd-dependent
  src/cli/enable-agent.ts:discoverProjectRoot() now tries
  CTX_FRAMEWORK_ROOT, then CTX_PROJECT_ROOT, then ~/cortextos, then
  process.cwd() as a last resort. Same fix applied inline to
  src/cli/ecosystem.ts. Error message when no .env is found now lists
  the paths actually checked.

Tests:
  tests/unit/cli/enable-agent-validation.test.ts (NEW) — 12 tests
  covering discoverProjectRoot precedence and readEnabledAgents
  validation paths.

Build: clean. npm test: 414/414 passing (402 baseline + 12 new).

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

* fix(ecosystem): also require dashboard/node_modules/.bin/next before adding PM2 entry

Caught in cycle 2: the BUG-019 fix added a cortextos-dashboard PM2 entry
based only on dashboard/package.json existing, but if the user runs
cortextos ecosystem before npm install in dashboard/, the dashboard PM2
entry crash-loops with "next: command not found".

Tighter check: require both package.json AND node_modules/.bin/next.
If the dashboard isn't installed yet, silently skip the PM2 entry. The
user can re-run cortextos ecosystem after npm install to add it.

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

---------

Co-authored-by: grandamenium <noreply@anthropic.com>
…026, 027) (#14)

End-of-night close-out PR consolidating four conservative code fixes plus
four pure verification closures. The four code fixes are all small,
defensive, or observability improvements. The four verifications confirm
that bugs were transitively closed by earlier PRs.

BUG-015 (P2) — IPC source logging
  Added optional `source: string` field to IPCRequest. Daemon now logs
  every incoming IPC request as `[ipc] <type> <agent> from <source>`.
  Instrumented all 12 CLI callsites: cortextos status, enable, disable,
  start, stop (single + --all), bus self-restart, bus soft-restart,
  bus soft-restart-all, bus status. Older callers fall back to 'unknown'.

BUG-021 (P2) — pm2 startup mid-flow blocking
  /onboarding skill no longer prompts the user to paste a sudo command
  mid-flow. Phase 9b now captures pm2 startup output silently. Phase 10
  delivers the captured sudo command in an OPTIONAL section at the very
  end, after the user is fully onboarded. Reboot survival becomes
  opt-in rather than blocking the critical path.

BUG-031 (P2) — pendingRestarts regression detector
  Replaced the dormant pendingRestarts queue actions in agent-manager.ts
  with console.warn regression-check lines. PR #11 (BUG-011) closed the
  underlying race that pendingRestarts was working around, so the queue
  should never fire. Preserved as a safety net + telemetry: if BUG-011
  ever regresses, the warning fires immediately and we know to
  investigate. Once we have weeks of zero-warning production data, we
  can delete the queue mechanism entirely.

BUG-032 (P3) — PTY exits with SIGHUP code 129
  Defensive fix in AgentProcess.stop():
    - Changed `pty.write('/exit\\r')` -> `pty.write('/exit\\r\\n')`
      (Claude Code REPL parses CRLF, not lone CR)
    - Bumped post-/exit wait from 3000ms -> 5000ms (give the child
      time to flush + exit cleanly before the PTY is torn down)
  This addresses the most likely contributors to the SIGHUP. If cycle
  1/2 still shows code 129, the more aggressive fixes (drain buffer,
  exit-detection-before-kill) will be a follow-up PR.

Verify-and-close (no code changes — exploration confirmed already
correct in source):
  BUG-008 (P1) — Telegram 409 conflict — likely closed by PR #4
    (restartAgent rebuilds Telegram poller). Cycle 1 will confirm by
    scanning for 409 in daemon log after 5 restart cycles.
  BUG-009 (P2) — "Agent <orgname> not found" — likely closed by
    PR #8 + #10. Cycle 1 will confirm by scanning daemon log.
  BUG-026 (P1) — agent-manager path missing org segment — verified
    correct at agent-manager.ts:96 and :536. Both use the right
    `join(frameworkRoot, 'orgs', this.org, 'agents', name)` pattern.
  BUG-027 (P2) — dashboard SQLite filename hardcoded — verified
    correct at dashboard/src/lib/db.ts:8-12. Already uses templated
    `cortextos-${instanceId}.db`, not a hardcoded literal.

Deferred (not in this PR):
  BUG-003 — mystery SIGTERM cascade. Needs the 30-min idle soak from
    Phase 3 of core-stability-test-plan.md. Stays open for a future
    dedicated soak session.

Build: clean. npm test: 414/414 passing (no test additions needed —
all changes are observability or defensive).

Co-authored-by: grandamenium <noreply@anthropic.com>
…UG-040, closes BUG-038) (#15)

BUG-040 — root cause of BUG-038 (pendingRestarts regression detector firing
under cortextos bus soft-restart-all): the `stopping` flag in AgentProcess
gets cleared when stop()'s 5-second Promise.race timeout fires, NOT when the
PTY actually exits. After BUG-032's defensive fix bumped graceful shutdown
from 3s to 5s wait, the total elapsed time before pty.kill() is ~6s. Once
pty.kill() runs, the PTY can take additional time to actually exit. If that
delayed exit fires AFTER stop()'s timeout, handleExit runs with stopping=false
and triggers spurious crash recovery — exactly what PR #11 was supposed to
prevent. This was a partial regression of BUG-011 caused by BUG-032's
defensive fix interacting badly with the 5s safety timeout.

Surfaced live in PR #14's fastloop test 2026-04-09: a single pm2 restart of
cortextos-daemon produced a crash recovery cascade with two crash counts
incrementing during the supposed clean shutdown.

Fix:

1. New `stopRequested: boolean` field that persists ACROSS stop()'s return.
   Set true at the start of stop(), cleared only by handleExit when an
   intentional exit fires, or by start() at the beginning of a new lifecycle.
   This is the safety net for late-arriving exits.

2. New `lifecycleGeneration: number` counter incremented on each successful
   start(). Each PTY's onExit closure captures the generation at spawn time
   and bails out early if the generation doesn't match — i.e. a new PTY has
   been spawned since this old one was created. Prevents an old PTY's late
   exit from triggering crash recovery on the new agent.

3. Bumped Promise.race timeout in stop() from 5s to 15s. The functional
   correctness no longer depends on this (stopRequested handles late exits)
   but a generous timeout reduces "Ignoring late exit" log noise from the
   generation guard.

4. handleExit now checks `stopRequested || stopping` instead of just
   `stopping`. Either flag short-circuits crash recovery. Clears stopRequested
   when consumed.

Verified end-to-end via fastloop:
- Daemon SIGTERM shutdown: clean stop, no crash recovery cascade
- 5x cortextos bus soft-restart-all cycles: 0 crash recovery, 0 REGRESSION
  CHECK warnings, 0 "Ignoring late exit" lines, 5 successful "Restart
  complete" sequences
- Telegram: 5x correct "🔄 commander restarted by user" notifications,
  zero false 🚨 CRASH alarms
- 414/414 tests still passing, no regressions

Closes BUG-038 (its symptom) as a side effect of fixing the root cause.

Note: BUG-032 (PTY exits with code 129 SIGHUP) is still present — agents
still exit with 129 instead of 0 after the graceful shutdown sequence. But
with BUG-040 fixed, the daemon correctly recognizes 129 as intentional and
does not fire crash recovery. BUG-032 is now purely cosmetic log noise and
can be addressed in a separate aggressive-fix follow-up.

Co-authored-by: grandamenium <noreply@anthropic.com>
…41) (#16)

BUG-041 is a P0 validation mismatch that was discovered live when the
CortextDesigner agent (created earlier during a session) tried to use
`cortextos bus send-telegram` and failed with:

  Error: CTX_AGENT_NAME is invalid: Invalid agent name 'CortextDesigner'.
  Must contain only lowercase letters, numbers, underscores, and hyphens.
    at resolveEnv (dist/cli.js:4273:13)

Root cause: `src/cli/add-agent.ts` performed ZERO validation on the
agent name argument. It accepted any string, created the agent directory
on disk, wrote to enabled-agents.json, and the daemon happily spawned
the agent. But at runtime, every `cortextos bus *` command calls
`resolveEnv()` from `src/utils/env.ts`, which strictly validates the
agent name via `validateAgentName()` (regex `/^[a-z0-9_-]+$/`). Names
that add-agent accepted would then fail every bus call.

Result: mixed-case names like 'CortextDesigner' produced half-
functional agents — daemon-managed fine, Telegram receiving worked
(via FastChecker PTY injection), but Telegram SENDING, inbox reading,
task creation, approvals, heartbeats, and every other bus operation
were broken. Affected agents were effectively undeployable for real
work.

Fix: call `validateAgentName()` at the start of the add-agent action,
BEFORE any filesystem operations. Invalid names now get rejected
upfront with a clear error message that explains the rule and shows
valid examples. The validation uses the same `validateAgentName()`
function that resolveEnv already uses, so add-agent and resolveEnv are
now guaranteed to agree on what a valid name is.

Changes:

1. src/cli/add-agent.ts — import validateAgentName, call it as the
   first thing in the action handler. Clear error on failure with
   examples: paul, sentinel, cortext-designer, m2c1-worker, agent_1.

2. tests/unit/utils/validate.test.ts — new regression test for
   mixed-case / PascalCase / CamelCase names. Locks in rejection of
   CortextDesigner, MyAgent, camelCase, Agent1, tally-Bot, snake_Case.

3. tests/unit/cli/add-agent-validation.test.ts (NEW) — integration
   test that calls addAgentCommand.parseAsync with invalid names
   (PascalCase, single uppercase, spaces, path traversal) and asserts
   process.exit(1) is called with the correct error message and the
   validation rule reference.

Verification (via manual dist copy, no pm2 restart needed since the
fix is CLI-only):

- 419/419 tests passing (414 baseline + 5 new tests)
- LIVE: `cortextos add-agent TestMixedCase --template agent --org testorg`
  → fails with clear error, no directory created, exit code 1
- LIVE: `cortextos add-agent test-valid-name --template agent --org testorg`
  → succeeds, normal flow works, agent registered
- LIVE: CTX_AGENT_NAME=test-valid-name cortextos bus list-agents
  → succeeds, proves the fix makes add-agent and resolveEnv consistent
- LIVE: CTX_AGENT_NAME=CortextDesigner cortextos bus list-agents
  → still fails (expected — fix prevents NEW bad agents, existing ones
  stay broken until recreated with a valid name)

Out of scope (hygiene follow-up): inline regex duplicates in
src/cli/setup.ts, src/cli/goals.ts, src/bus/agents.ts, src/bus/system.ts
should all import validateAgentName from the canonical location but
that's a separate cleanup PR. Not required for BUG-041 fix.

Co-authored-by: grandamenium <noreply@anthropic.com>
BUG-043 was a P0 architectural bug that prevented multi-org installs from
working. The daemon's AgentManager had a singleton this.org field set from
startup CTX_ORG and used it for every agent lookup:

  discoverAgents() scanned only orgs/{this.org}/agents/
  startAgent() auto-discovered via join(frameworkRoot, 'orgs', this.org, ...)
  env.org passed to AgentProcess was hardcoded this.org
  resolvePaths() call used this.org
  worker spawning used this.org

Result: a daemon started with CTX_ORG=testorg could never find, spawn, or
operate agents that lived in other orgs (lifeos, cointally, etc). Agents in
those orgs were silently invisible — enabled-agents.json entries pointing
to them would log "Agent directory not found" when cortextos enable fired.

Discovered live during the donna migration: agent 'donna' in orgs/lifeos
could not be enabled because the daemon (CTX_ORG=testorg) searched
orgs/testorg/agents/donna instead of orgs/lifeos/agents/donna.

Fix:

1. discoverAgents() iterates ALL orgs under frameworkRoot/orgs, not just
   this.org. Each discovered entry records its org so downstream code
   knows where to find the agent dir. Return type changed from
   Array<{name, dir, config}> to Array<{name, dir, org, config}>.

2. discoverAndStart() destructures the new org field and passes it as
   the 4th argument to startAgent, so startAgent never needs to fall
   back to this.org on the discovery path.

3. startAgent() signature extended: startAgent(name, agentDir, config?, org?).
   When called from discoverAndStart it receives an explicit org. When
   called from the IPC start-agent handler (which only has the agent
   name), it uses the new resolveAgentOrg() helper to look up the
   correct org from enabled-agents.json, with a filesystem scan
   fallback for legacy entries missing the org field.

4. New private helper resolveAgentOrg(name, explicitOrg?) with four-tier
   resolution:
     a. explicit org arg (from discoverAgents or caller)
     b. enabled-agents.json entry's org field
     c. filesystem scan — walk orgs/*/agents/ looking for a dir named name
     d. legacy fallback: this.org (preserves single-org install behavior)

5. startAgent() uses the resolved org everywhere it previously used
   this.org: the join() path for auto-discovery at line 107, the env.org
   field at line 126, and the resolvePaths() call at line 130.

6. Worker spawning at line 489 deliberately keeps this.org. Workers are
   ephemeral and don't have entries in enabled-agents.json, so there's
   no per-agent org to look up. This is documented in the plan and a
   safe no-op for multi-org support since workers are spawned by their
   parent agent, which has the correct org context.

Tests:

  tests/unit/daemon/agent-manager.test.ts — new describe block for BUG-043
  with 4 tests:
    - discovers agents from ALL orgs, not just the startup org
    - passes the correct per-agent org as the 4th arg to startAgent
    - respects enabled-agents.json disable-flags across multiple orgs
    - returns empty list when orgs/ does not exist (backward compat)

  Existing BUG-028 test updated to expect the new 4th arg.

Verification: 423/423 tests passing (419 baseline + 4 new).

Backward compatibility: single-org installs continue to work unchanged.
resolveAgentOrg falls back to this.org when nothing else resolves,
preserving the legacy behavior for installs that only ever have one org.

Unblocks the in-progress 8-agent migration that was halted when the
first agent (donna, in lifeos) couldn't be spawned.

Co-authored-by: grandamenium <noreply@anthropic.com>
… set

listAgents() was falling back to process.cwd() whenever scanRoots was empty,
even when CTX_FRAMEWORK_ROOT was set to a valid path that just had no orgs/
subdir. This caused 4 test failures in agents.test.ts: vitest runs from the
repo root which has a real orgs/ dir, so deleting CTX_FRAMEWORK_ROOT in
beforeEach still allowed the cwd fallback to find and return real agents.

Fix: only apply the cwd fallback when CTX_FRAMEWORK_ROOT is completely unset
(falsy). If a root is explicitly configured, respect it — an empty framework
root means zero agents, not "fall back to cwd."

Also update the test's beforeEach to set CTX_FRAMEWORK_ROOT to an isolated
subdir of testDir (no orgs/ inside) instead of deleting it, so the guard
condition is tested correctly.

CLAUDE.md: update test count from 381 → 423 (reflects additions in PRs #11-14).

423/423 tests passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The count drifts as tests are added. Removing the specific number
avoids stale documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… rendering (#21)

Two cosmetic fixes from sentinel's bug audit:

1. SparkLine: change wrapper from <span> to <div> and add minWidth/minHeight
   constraints to prevent Recharts ResponsiveContainer from computing negative
   container dimensions.

2. Brand Voice: replace raw <pre> with the existing renderMarkdown() renderer
   so markdown syntax (bold, lists, headings, etc.) displays as formatted HTML
   instead of raw text. Extract renderMarkdown to shared lib/render-markdown.tsx
   for reuse across kb-view and organization-tab.

Co-authored-by: James Goldbach <cortextos@Jamess-Mac-mini.local>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(daemon): auto-verify cron restoration after agent bootstrap

Agents are supposed to restore crons from config.json on session start,
but compliance is unreliable (agents sometimes skip step 6 of the
13-step boot sequence). This adds a framework-level safety net:

After the agent finishes its startup turn (detected via last_idle.flag),
the daemon reads the crons array from config.json and injects a
verification prompt asking the agent to check CronList and restore any
missing recurring crons.

Safe for both fresh and --continue restarts: waits for idle before
injecting, so it never interrupts a mid-conversation agent. Bails out
if the agent stops or restarts during the wait window.

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

* fix(daemon): guard cron injection on timeout + add happy-path test

Issue 1: verifyCronsAfterIdle had a timeout fallthrough bug where the
while-loop exit via timeout (no idle flag detected) fell through into
the injection block, causing a prompt to be injected into an agent
mid-work. Fix adds a `foundIdle` boolean — injection is skipped unless
the loop explicitly broke on a newer idle timestamp.

Issue 2: Add happy-path test for verifyCronsAfterIdle covering the
core flow: existsSync returns true with a boot timestamp, then a newer
timestamp on the first poll, triggering injection with expected cron
names. Uses vi.useFakeTimers() to advance the 15s poll instantly.
Also adds the three guard-clause tests (no-crons, once-only, recurring)
and wires mockInjectMessage for assertion.

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

---------

Co-authored-by: James Goldbach <cortextos@Jamess-Mac-mini.local>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: comprehensive Windows support (14 fixes from PR #6)

Ports all 14 Windows compatibility fixes from Erica's PR #6, with the
password sync behavior made opt-in (requires SYNC_ADMIN_PASSWORD=true)
instead of always-on to prevent silently overwriting dashboard-changed
passwords on restart.

Fixes: claude.exe resolution in node-pty, shell:true for npm/npx/where
on Windows, UTF-8 encoding in PTY, venv Scripts/ vs bin/ paths,
embedding model migration, Python venv in install, npm link registration,
build tools guidance, favicon, enabled-agents.json path, context window
docs, APPDATA gitignore.

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

* fix(auth,pty): fix ADMIN_PASSWORD regression and use claude.cmd on Windows

Issue 1: seedAdminUser validated ADMIN_PASSWORD before checking if users
already exist, breaking all existing deployments without ADMIN_PASSWORD set.
Now returns early when users exist and SYNC_ADMIN_PASSWORD is not true,
only validating the password when it is actually needed for seeding or syncing.

Issue 2: npm global installs on Windows create .cmd wrapper scripts, not
.exe binaries. node-pty's CreateProcess cannot resolve .cmd files from a
.exe name. Changed 'claude.exe' to 'claude.cmd' on win32 so node-pty
can find and launch the Claude Code CLI correctly.

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

* fix(dashboard): guard journal_mode WAL switch against parallel-worker SQLITE_BUSY

Next.js build runs 3 workers that all import db.ts simultaneously. Each
opens the DB, acquires a shared lock, then tries PRAGMA journal_mode = WAL
(needs exclusive lock). With all three holding shared locks, none can
upgrade — busy_timeout eventually expires with SQLITE_BUSY.

Fix: catch SQLITE_BUSY from the WAL switch and verify whether another
worker already succeeded. If journal_mode is already 'wal', continue;
otherwise re-throw. This is a no-op in normal single-process operation.

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

---------

Co-authored-by: James Goldbach <cortextos@Jamess-Mac-mini.local>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Erica <erica@cortextos.com>
* feat: comprehensive Windows support (14 fixes from PR #6)

Ports all 14 Windows compatibility fixes from Erica's PR #6, with the
password sync behavior made opt-in (requires SYNC_ADMIN_PASSWORD=true)
instead of always-on to prevent silently overwriting dashboard-changed
passwords on restart.

Fixes: claude.exe resolution in node-pty, shell:true for npm/npx/where
on Windows, UTF-8 encoding in PTY, venv Scripts/ vs bin/ paths,
embedding model migration, Python venv in install, npm link registration,
build tools guidance, favicon, enabled-agents.json path, context window
docs, APPDATA gitignore.

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

* fix(auth,pty): fix ADMIN_PASSWORD regression and use claude.cmd on Windows

Issue 1: seedAdminUser validated ADMIN_PASSWORD before checking if users
already exist, breaking all existing deployments without ADMIN_PASSWORD set.
Now returns early when users exist and SYNC_ADMIN_PASSWORD is not true,
only validating the password when it is actually needed for seeding or syncing.

Issue 2: npm global installs on Windows create .cmd wrapper scripts, not
.exe binaries. node-pty's CreateProcess cannot resolve .cmd files from a
.exe name. Changed 'claude.exe' to 'claude.cmd' on win32 so node-pty
can find and launch the Claude Code CLI correctly.

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

* fix(dashboard): guard journal_mode WAL switch against parallel-worker SQLITE_BUSY

Next.js build runs 3 workers that all import db.ts simultaneously. Each
opens the DB, acquires a shared lock, then tries PRAGMA journal_mode = WAL
(needs exclusive lock). With all three holding shared locks, none can
upgrade — busy_timeout eventually expires with SQLITE_BUSY.

Fix: catch SQLITE_BUSY from the WAL switch and verify whether another
worker already succeeded. If journal_mode is already 'wal', continue;
otherwise re-throw. This is a no-op in normal single-process operation.

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

* fix(telegram): use relative paths for media files, add reply context for media messages

BUG-046: Claude Code strips absolute file paths from pasted user input,
causing agents to receive empty local_file: fields for Telegram photos,
documents, voice, and video messages. Convert to relative paths (from
agent working dir) before injection so paths survive.

Also adds buildReplyContext() to properly handle reply context for media
messages — previously only .text was checked, so replies to photos/videos
arrived with no indication of what was being replied to.

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

---------

Co-authored-by: James Goldbach <cortextos@Jamess-Mac-mini.local>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Erica <erica@cortextos.com>
…(BUG-003) (#18)

A second SIGTERM arriving while shutdown() was already in flight would
start a parallel stopAll(), causing unpredictable signal cascades across
child PTY processes. Add a shuttingDown flag so subsequent signals are
logged and ignored.

Co-authored-by: James Goldbach <cortextos@Jamess-Mac-mini.local>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…ean exit (BUG-032) (#19)

After sending /exit and waiting 5s, the child process has usually exited
cleanly. Calling pty.kill() unconditionally on an already-exited PTY
tears down the file descriptor and sends SIGHUP (exit code 129). Now we
check pty.isAlive() first and only kill if the process is still running.

Also adds isAlive to the AgentPTY mock in agent-process tests.

Co-authored-by: James Goldbach <cortextos@Jamess-Mac-mini.local>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…d usage API script (grandamenium#37)

- BUG-048: session timer now re-reads config.json on each check so a
  config change after start() takes effect. Prevents fleet-wide
  simultaneous restarts when max_session_seconds is briefly reduced and
  then restored after timers are already set. Rescheduled if remaining
  time > 5s, fires immediately otherwise.
- bus/check-usage-api.sh: new script to check Claude Max API usage via
  OAuth endpoint with 3-minute cache, threshold alerts via Telegram, and
  --warn-7day / --warn-5h flags.
- 441/441 tests passing (2 new BUG-048 regression tests added).

Co-authored-by: James Goldbach <cortextos@Jamess-Mac-mini.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
#25)

Login would hang forever when the dashboard was accessed via a Tailscale
IP, LAN address, or reverse proxy. Root cause was three independent
layers that all had to be fixed together — verified end-to-end in a
headless browser (all three layers have to land for the form to reach
the bcrypt check).

1. next.config.ts — allowedDevOrigins. Next.js 15.2+ rejects /_next/*
   dev-internal requests from non-localhost origins by default. The
   browser receives the SSR HTML but the client bundle never finishes
   hydrating, so useEffect never fires, /api/auth/csrf is never fetched,
   and the form is stuck. Whitelist is now read from
   DASHBOARD_ALLOWED_DEV_ORIGINS (comma-separated; localhost is always
   allowed) so each deployment picks its own hosts.

2. login/page.tsx — CSRF token lifetime. Previous attempts stored the
   token in React state bound with value= or set it imperatively on the
   hidden input. Both failed: React reconciliation resets uncontrolled
   input values between the useEffect completion and the next render,
   so the input never carried the real token at submit time. The token
   is now fetched once, held in a ref, and injected on the request body
   at submit. The hidden input in the JSX stays as a placeholder.

3. login/page.tsx — submit body format. The previous implementation used
   FormData (multipart/form-data), which caused NextAuth to fail CSRF
   validation with MissingCSRF — the multipart parser did not recover
   the csrfToken field. Submit now bypasses signIn() and POSTs the
   credentials callback directly as application/x-www-form-urlencoded,
   which NextAuth parses correctly.

Also removes the now-unused signIn import.

Co-authored-by: Clint Moody <ClintMoody@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a session-independent setInterval in FastChecker that fires
cortextos bus update-heartbeat every 50 minutes. Covers idle sessions
where REPL-bound cron jobs miss the theta wave window.

Clears the timer cleanly on stop(). 3 new unit tests (fires, clears,
pre-bootstrap guard) — all 430 tests pass.

Co-authored-by: Ben Joslin <benjoslin52@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…ndamenium#30)

The previous pollOnce implementation advanced `this.offset` to
`update.update_id + 1` before handlers ran and caught handler errors
silently, so any failure in the PTY inject path (or a crash mid-batch)
would leave the update acknowledged to Telegram but never delivered to
the agent. Messages were silently lost.

New semantics:
- The offset advances only after every registered handler for an
  update returns without throwing.
- If a handler throws, the update is left un-acknowledged (Telegram
  re-delivers on the next getUpdates call) and the rest of the batch
  is deferred to preserve ordering.
- The offset is persisted after each successful update, not just at
  the end of the batch, so a crash mid-batch preserves confirmed state.

Adds tests/unit/telegram/poller.test.ts covering: success path,
message-handler throw, mid-batch failure and ordering, per-update
persistence, callback-handler success, and callback-handler throw.

Co-authored-by: Josh Weiss <joshweiss@Joshs-Mac-mini.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ber stripping (BUG-050) (grandamenium#41)

* fix(daemon): re-read max_session_seconds on timer fire (BUG-048) + add usage API script

- BUG-048: session timer now re-reads config.json on each check so a
  config change after start() takes effect. Prevents fleet-wide
  simultaneous restarts when max_session_seconds is briefly reduced and
  then restored after timers are already set. Rescheduled if remaining
  time > 5s, fires immediately otherwise.
- bus/check-usage-api.sh: new script to check Claude Max API usage via
  OAuth endpoint with 3-minute cache, threshold alerts via Telegram, and
  --warn-7day / --warn-5h flags.
- 441/441 tests passing (2 new BUG-048 regression tests added).

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

* fix(daemon): use single quotes in send-telegram instructions to prevent shell variable expansion (BUG-050)

Dollar signs followed by digits (e.g. $100) were stripped from Telegram
messages because the 'Reply using:' instruction templates used double
quotes around <your reply>. Bash expands $100 as ${1}0 (first positional
param + "0"), leaving just the trailing digits.

Fix: change all five formatTelegram* instruction strings from
double quotes to single quotes, matching the existing pattern used for
send-message (line 188). Single-quoted strings are not subject to shell
variable expansion.

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

---------

Co-authored-by: James Goldbach <cortextos@Jamess-Mac-mini.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
thehoff and others added 24 commits June 5, 2026 12:29
…13/#14) (grandamenium#598)

Task operations interpolated caller-supplied taskId (and task.assigned_to) into
filesystem paths with no validation, so a traversal id/assignee could escape the
task tree (read/write/rename/unlink arbitrary *.json / *.jsonl / *.claim).

- New validateTaskId() in validate.ts (/^[a-z0-9_-]+$/, matching the
  task_<epoch>_<rand> generator) — rejects path separators, dots, traversal.
- findTaskFile() validates taskId (the chokepoint for update/claim/complete/
  check-deps); appendTaskAudit() + readTaskAudit() validate before building
  audit paths; cli/bus.ts checkDeliverableRequirement() validates before its
  pre-update/complete lookup (it runs ahead of findTaskFile's guard).
- saveOutput() validates taskId AND task.assigned_to (the latter comes from the
  task JSON and feeds the deliverables path).
- archiveTasks()/compactTasks() validate task.id (from the JSON body) before
  using it for rename/unlink, and reject a non-YYYY-MM completed_at before it
  feeds the archive filename — both skip the offending task rather than abort.

Tests: validateTaskId accept/reject; findTaskFile + readTaskAudit reject
traversal; saveOutput rejects traversal taskId + tampered assigned_to and still
saves a legit deliverable; archiveTasks skips a traversal-id task without escaping.

Follow-ups (out of this traversal-scope branch): derive archive/compact filenames
from the on-disk task_*.json name rather than trusting task.id (guards against a
valid-but-mismatched id touching the wrong in-tree file); add a log line to the
archiveTasks skip path.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eForPtyInjection (grandamenium#596) (grandamenium#603)

sanitizeForPtyInjection (the grandamenium#592 follow-up's unfenced-context guard) prefixed
forged `=== AGENT MESSAGE` / `=== TELEGRAM` / `Reply using:` headers with
[quoted] only when they were preceded by ASCII space/tab ([ \t]*). A downstream
parser recognizes the same headers via `.trim()`, which strips the full Unicode
White_Space set — so a header led by e.g. NBSP, IDEOGRAPHIC SPACE, or BOM escaped
[quoted] here yet was still parser-recognized after trim (asymmetry reported by
ClintMoody, follow-up to grandamenium#592 / 20583d3).

Widen the leading-whitespace class to the Unicode space chars `.trim()` strips:
NBSP, OGHAM SPACE, the U+2000–200A run, NARROW NBSP, MEDIUM MATH SPACE,
IDEOGRAPHIC SPACE, and BOM/ZWNBSP. Line terminators stay excluded — the /m
anchor already restarts after \n and U+2028/U+2029, \r was folded to \n, and
\v/\f are removed by stripControlChars.

Adds a 7-char gap matrix (each char x AGENT MESSAGE + Reply-using), a mixed
ASCII+Unicode run, and VT/FF + LS/PS regression guards. Verified the old class
missed all 7 chars and the widened class quotes them. Suite green, tsc clean.

Co-authored-by: Boris <noreply@anthropic.com>
…tization (grandamenium#604)

* fix(fast-checker): inject unhandled callbacks to agent instead of dropping

Custom inline button flows were hitting the catch-all log line and being
silently dropped. Now they are injected as a Telegram message so the
agent can handle them.

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

* fix(security): sanitize callback-injection path against PTY injection

The unhandled-callback injection block interpolated the tapping user's
first_name and callback_data raw into the '=== TELEGRAM from [USER: ...]'
message injected into the agent session. This block predates grandamenium#592 and its
sanitizer was never retrofitted, so a forged '=== AGENT MESSAGE' or
fence-breakout in a user-controlled first_name rendered un-neutralized.

Wrap both senderName and callback_data with sanitizeForPtyInjection,
matching the text path. Reachability is gated by ALLOWED_USER (line 562),
so practical exposure is self-inflicted on single-user bots; this closes
the defense-in-depth gap for unset-allowed-user / multi-user contexts.

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

---------

Co-authored-by: Boris <noreply@anthropic.com>
* feat: add voice-agent-factory community skill

Turns any cortextOS agent into a live ElevenLabs voice agent. Six-phase
pipeline: DISCOVER (mine skills/CLIs/MCPs/transcripts) -> ASK -> GENERATE
(all code written dynamically per target, policy-gated gateway, server-side
invariants) -> TEST (probe-shaped fixtures) -> PROVISION (tier-detect with
server-tools fallback) -> VERIFY (real text-only WS conversations, never
simulate-conversation which mocks tools). 14 hard-won lessons encoded from
two live reference builds. Bundled resources: verified EL API reference,
webhook schema + pricing gaps doc, sources.

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

* fix(voice-agent-factory): address review round 1 — operator gate, MCP attach, runtime detection, fresh-user path

- Hard operator gate before Phase 5: no EL provisioning, tunnel, or link
  sharing on defaults; Phase 2 defaults limited to discovery/generation
- MCP path now PATCHes agent prompt mcp_server_ids after POST (append,
  preserve existing), matching the tool_ids discipline
- Runtime detection in Phase 1 with codex-app-server alternate paths and an
  explicit unsupported-runtime stop instead of silent under-mining
- Prerequisites / How to run / What this writes section (incl. unpkg CDN note
  and no-CDN variant)
- GAPS.md: body_params_schema -> request_body_schema (+ response_body_schema
  docs-only note); REPORT section 8 reconciled with GAPS closures
- Neutralized remaining org-voice leaks and research-harness provenance in
  resources; FROM SCRATCH wording reconciled with lesson-13 coexist rule
- catalog.json churn reduced to a minimal +13/-1 diff

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

* fix(voice-agent-factory): review round 2 — webhook schema keys, gap-status consistency

- GAPS webhook sketch: headers_schema -> request_headers; auth_connection /
  auth_resolved_params bullet (per WebhookToolApiSchemaConfig-Input)
- REPORT closing paragraph now lists only remaining key-test checks; schema +
  pricing marked closed-in-GAPS / re-confirm at key-test
- SOURCES server-tools line reworded to resolved-in-GAPS

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

---------

Co-authored-by: Boris <noreply@anthropic.com>
… heartbeat (grandamenium#667)

buildStartupPrompt auto-wrote the .onboarded marker whenever heartbeat.json
existed, on the assumption the agent had completed onboarding and just forgot
the marker. That silently suppressed FIRST BOOT for agents that were manually
scaffolded (heartbeat present) but never actually ran onboarding — the agent
would never be told to read ONBOARDING.md.

Require the marker to be explicit: a heartbeat alone no longer marks an agent
onboarded. An existing .onboarded marker still suppresses FIRST BOOT, so
already-onboarded agents are unaffected. This is general daemon behavior; it
was surfaced via a manually scaffolded agent but applies to any runtime.

Adds two-direction regression coverage in agent-process.test.ts:
- heartbeat-only / no marker -> still routes to FIRST BOOT, and no .onboarded
  is auto-written
- existing .onboarded -> FIRST BOOT suppressed

Co-authored-by: Boris <noreply@anthropic.com>
…op the slash menu (grandamenium#668)

setMyCommands ran once per agent at startup as a single fire-and-forget
attempt. When the daemon bounced mid-onboarding (e.g. reloading to pick up
a newly created agent) the in-flight request was killed and, with no retry,
the slash menu never landed for that bot - registration silently failed.

- registerTelegramCommands now retries on transient failures with a short
  linear backoff (default 3 attempts) so a flaky network or slow API
  response within a single boot no longer loses the menu.
- The daemon caller logs registration failures instead of swallowing them,
  so a missing menu is visible to operators.

Adds retry-path coverage in sprint5-metrics.test.ts (success, retry-then-
succeed, exhausted-attempts, empty-noop).

Co-authored-by: Boris <noreply@anthropic.com>
…from usage cache (grandamenium#669)

* fix(dashboard): auto-populate Max plan usage widget from the live usage cache

The Max Plan Usage widget reads state/usage/latest.json, which is only written
by a manual `cortextos bus scrape-usage` paste. Without that one-time step the
widget shows "Plan usage tracking not configured" indefinitely, even though the
usage-monitor cron already maintains a fresh OAuth usage cache.

getPlanUsage now falls back to state/usage/api-cache.json when latest.json is
absent, mapping its 5h / 7d / 7d-sonnet utilization onto session / week-all-models
/ week-sonnet (with friendly reset dates and the cache file mtime as the
timestamp). latest.json stays primary, so the manual scrape path and the daily
history series are unchanged — this only fixes the empty default.

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

* feat(dashboard): add Codex plan usage to the usage widget

Surfaces Codex (ChatGPT) plan usage alongside Claude on the analytics page,
read from the codex-wham-cache.json that the usage-monitor cron keeps fresh.
primary_window maps to the 5h session limit, secondary_window to the 7d limit;
reset_at is a unix epoch. Renders a "Codex Plan Usage (<plan>)" card with 7d +
5h bars when the cache is present, and is omitted otherwise.

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

---------

Co-authored-by: Boris <noreply@anthropic.com>
…grandamenium#684)

Token auto-refreshes via OAuth; the expiry warning is noise. Removes the
<24h block that fired every usage-monitor cycle regardless of --chat-id.

Co-authored-by: Boris <noreply@anthropic.com>
…rator paths from public repo

Remove 13 dev-artifact reports (WINDOWS_INSTALL_REPORT.md + docs/phase-reports/*)
that exposed the internal agent roster, cron schedules, operator home paths, and
org name; redact hardcoded operator username in two test files; gitignore to
prevent re-tracking.
… (SEC-1 L1/L4) (grandamenium#698)

L1: .github/workflows/leak-guard.yml runs .github/scripts/leak-guard.sh on every
pull_request (fork-safe, no secrets) and push to main — the server-side backstop
a local pre-push hook cannot provide (covers fork PRs and UI-merges). Blocks on
the operational-LEAK SHAPE (operator home paths, agent-roster+cron-schedule
tables, secret-shaped tokens, dev-report artifact paths), NOT on legitimate
framework convention (agent-name placeholders, lifeos test fixtures). Ships with
a falsifiability test proving it FAILS on a planted leak and PASSES on the clean
tree. Intended as a required status check (applied post-merge, L2).

L4: broaden .gitignore so root-level *INSTALL_REPORT.md / PHASE*-REPORT.md /
docs/phase-reports/ dev artifacts cannot re-track.

Remediation follow-up to the 2026-07-01 fleet-metadata leak.

Co-authored-by: James Goldbach <cortextos@Mac.fios-router.home>
…dow (grandamenium#685)

Make the runtime-agnostic context-handoff mechanism ship enabled by default
so any install/pull restarts agents before native compaction, writes a
resume-ready handoff doc, and reboots fresh from it. Previously unset
ctx_handoff_threshold meant observe-only; now it defaults to warn 30% /
handoff 60% of the real model window (opt out with ctx_handoff_threshold <= 0).

- Default-on at 60% of model context window (fast-checker)
- Wire the statusLine context-status writer into the Claude templates so the
  used-percentage is reported for the threshold computation
- Report current (not lifetime) context usage from the codex adapter
- Rate-limit concurrent handoffs with a release-safe lease (cap 2, queue the
  rest; release by name, session-id independent, on fresh session)
- Gate the PTY overflow backstop on real high context to avoid false 100%
- Unit truth-table for the default-ON behavior (fast-checker.test.ts): unset
  threshold => handoff 60 / warn 30, opt-out at <= 0 (observe-only), and an
  explicit threshold still honored — exercises the real checkContextStatus
- Codex handoff restart starts a genuinely NEW thread: only resume a persisted
  codex thread in continue mode, so a context-handoff (fresh) restart falls to
  thread/start instead of resuming the old thread. Resuming retained the full
  context window, so the handoff never lowered usage and the agent immediately
  re-crossed the threshold and re-fired — a restart treadmill caught by the
  PR-A live validation. (personal-main already gated this; upstream did not.)
- Cooperative-restart loop backstop: the circuit breaker now also counts Tier-2
  handoff fires in a persisted 15min window and trips (30min pause + alert) if
  they reach the cap, so any handoff loop self-limits regardless of cause — the
  existing breaker only counted Tier-3 force-restarts, not cooperative handoffs.

Co-authored-by: Boris <noreply@anthropic.com>
…andamenium#699)

* feat(codex): mid-turn message injection via turn/steer

Codex agents previously accepted injected messages only after the active
turn completed (queueTurn gates on _executing, which clears on
turn/completed). Claude agents get instant mid-turn PTY injection, so
codex delivery felt like wait-until-stopped on long turns.

The codex 0.130.0 app-server protocol ships turn/steer: mid-turn input
that is drained into the active turn at the next model step, without
aborting in-flight work. This change uses it for parity:

- Track activeTurnId from turn/started; clear on turn/completed, error
  notifications, and kill().
- queueTurn while executing now attempts turn/steer with expectedTurnId
  as the active-turn precondition. Success means no queue entry; the
  turn continues with the new context.
- Any steer rejection (ExpectedTurnMismatch when the turn just ended,
  ActiveTurnNotSteerable for review/compact turns, NoActiveTurn,
  transport errors) falls back to the existing queue, so no message is
  ever lost. No steer retry on fallback to avoid loops on non-steerable
  turns.
- Idle path unchanged; Claude path untouched.
- CODEX_STEER_DISABLED=1 kill-switch reverts to pure queue behavior.

Tests:
- 6 new unit tests (steer payload, rejection fallback ordering,
  pre-turn/started race, kill-switch, turn-id lifecycle); adapter suite
  71/71.
- New env-gated live integration test (CODEX_STEER_LIVE=1) against a
  real codex app-server: steer accepted mid-turn with same turnId, no
  second turn/started, steered content present in final output, stale
  expectedTurnId rejected. 2/2 passing on codex-cli 0.130.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 9cd1cc2491bd19fa5334deaeffecbd4f9a70a2d7)

* fix(codex): honor fresh app-server thread starts

(cherry picked from commit d3dbde6eb6bada4269203853656fa797e43e3ef0)

* feat(runtime): add native opencode agent adapter

(cherry picked from commit bbc8ea4fdd783a348751a705bd20971f547565a7, internal build artifacts under .agent/ dropped)

* fix(opencode): use object-form permissions in template

(cherry picked from commit 3816bc65ca89c93d6a72261a3602f1bcc23f0227)

* fix(opencode): inject inbound messages as raw TUI input

(cherry picked from commit e58735daae85b04f6d096d4759973db1443245b6)

* fix(daemon): suppress context handoff during fresh-session grace window

A fresh codex app-server thread can briefly report prior prompt-cache
tokens, producing a transient ~100% context reading on a session that is
actually at low context. The fast-checker fired a Tier-2 handoff on that
spike, injecting a prompt telling the agent to run `cortextos bus
hard-restart`; that cooperative restart bypasses the force-path circuit
breaker, yielding a fresh session that reads ~100% again -> restart loop
every ~1-2min (observed on codex-worker after a force-fresh boot).

Add HANDOFF_GRACE_MS (120s) anchored on session_id change: suppress the
Tier-1 warning and Tier-2 handoff while the session is younger than the
grace window. The hard API-overflow regex remains ungated so a genuine
overflow still force-restarts immediately. Tier-3 deadline is inherently
protected (only armed when Tier-2 fires).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 8b5d445cf71bb2bc396a7f9b57f51f0512474c89)

* fix(opencode): wait for real TUI readiness before inbound

(cherry picked from commit 78dc26a72e6c4a2f6660f3e0aa33d67008d57cee)

* fix(opencode): force telegram replies through bus command

(cherry picked from commit 279c76d167e0e09a3791d5873d8b34fde843a544)

* fix(opencode): escape shell mode before every inbound injection

After OpenCode executes the reply-protocol command (cortextos bus
send-telegram) the TUI is left in Shell mode. A subsequent inbound was
typed straight at the zsh prompt (`$ === TELEGRAM ...` -> command not
found) so the second turn produced no reply. Press Esc before every
OpencodePTY injection to exit shell mode / return to chat readiness,
settle 150ms, then type the content (existing 300ms deferred Enter kept
to preserve the proven first-turn timing). Adds a chained multi-turn
regression asserting each inbound gets its own Esc reset. Base AgentPTY
(Claude/Codex) is unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 3d5274e86fae585244951e660730aef8af8c6304)

* fix(opencode): detect zsh shell mode and exit-recover before inject

OpenCode's own heartbeat/check-inbox crons run terminal commands that
leave the TUI at a real zsh prompt. Esc alone does not exit that stuck
state, so the next Telegram inbound lands at the shell (command not
found) and produces no reply. Detect chat vs shell from the output tail:
chat readiness markers => type directly; a bare zsh prompt with no
markers => typed exit + Enter recovery, settle, then type. Conservative
default is chat, so a spurious exit is never submitted into a real chat
box.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 81f3f7add5009d077c9d224de61d042198e8b202)

* fix(daemon): send opencode lifecycle telegram

(cherry picked from commit f7e3d4894eed4011ef46f36f2f19166b3cf92140)

* fix(opencode): mirror restart lifecycle instructions

(cherry picked from commit ba774edfabf4ed0064555616021bfc54f329f343)

* fix(opencode): elevate telegram reply context

(cherry picked from commit b36b599e2508679d6ec03ab512b404218123419e)

* test(daemon): regression for overflow-backstop self-referential false-positive

The PTY overflow backstop matched its banner regex as plain text, so any agent
that read or quoted the overflow/compaction mechanism force-restarted itself at
low context. On 2026-06-26 this cascaded across the fleet (boris -> stephen ->
paul) once several agents were tasked to investigate opencode's compaction.

Tests use the ACTUAL same-line strings pulled from each agent's stdout and prove
teeth per falsifiability: every cascade string trips the OLD (regex-only)
detector, and none trip the guarded detector at low context; genuine overflow
(pct>=85 or exceeds_200k) still force-restarts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit b07f17349ac346bf10aa7b63861709d2a4a798c1)

* feat(opencode): report context status from session tokens

(cherry picked from commit 1aac99a4631e5151d848017d0575efc836201d02)

* fix(opencode): reset context status on fresh start

(cherry picked from commit af6613c8b1922d0b1dda7ca497f7c40ffdfd8b21)

* fix(opencode): execute startup prompts immediately

(cherry picked from commit baad0719e99d0af3dadad481044564080950844e)

* feat(daemon): rate limit context handoffs

(cherry picked from commit fd7cd3d5d960f36c93cb8b4dd93a47f3a23d59db)

* fix(daemon): send opencode lifecycle telegram on context handoff

The opencode (deepseek) runtime does not execute the injected boot-prompt
instruction to self-send a contextual 'back —' message on handoff restarts,
so opencode went silent on every context-handoff (now default-on). codex-app-server
self-sends reliably and is unchanged. The daemon now emits a handoff-flavored
'back online (context handoff)' notification for opencode itself.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit cd8b3677a5bff52526101eaf99b43bf572cb9ce9)

* fix(daemon): emit planned-restart msg1 for codex/opencode on handoff

The two-message handoff pattern is msg1 (lifecycle notif
"🔄 <agent> restarted (planned): context handoff at X%") + msg2 (the
agent's own "back — ..." summary). msg1 is emitted by the Claude Code
hook hook-crash-alert.ts on PTY exit, which only fires for the claude
runtime. codex (codex-app-server) and opencode runtimes do not run
Claude Code hooks, so James only ever saw msg1 for claude agents.

The daemon's maybeSendRuntimeLifecycleNotification now emits msg1 itself
for codex/opencode on a handoff restart, reading the reason from the
.restart-planned marker and matching hook-crash-alert.ts:394-397 format
byte-for-byte. opencode additionally keeps its daemon-emitted back-online
msg2 (deepseek does not self-send); codex self-sends its own msg2 so the
daemon emits msg1 only for it (no double-up).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit fcde85a15121eac2d212fb4bd9e750a7490e8e03)

* fix(daemon): drop redundant opencode back-online substitute on context handoff

opencode now reliably self-sends its own contextual "back — ..." via the
handoff boot prompt, so the daemon's "Agent X is back online (context handoff)"
substitute produced a redundant 3rd message (msg1 + daemon substitute + self-sent
back—). Removing it leaves opencode on the clean 2-message pattern: msg1
(planned-restart lifecycle) + the agent's own "back —", matching codex/claude.

Part of the restart-message standardization (James 2026-06-29). Agent-side donna
+ data-codex AGENTS.md stale-step14 re-sync ships alongside (orgs/, gitignored).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 628324f3cd066b266e1c765c89170926f6f76d92)

* feat(templates): context-handoff lifecycle contract in agent template + parity tests

Documents the daemon-driven context-handoff lifecycle in the agent
template AGENTS.md (status-line bridge, FastChecker polling, injected
lifecycle prompts) and extends runtime parity tests to cover it.

(extracted from internal commit ec126b46; framework files only)

* fix(daemon): restore cooperative-restart loop backstop alongside handoff grace window

The grace-window and lease changes were resolved against a branch that
predated the ctxHandoffFires cooperative-restart loop backstop shipped
in grandamenium#685; restore it so repeated Tier-2 handoff fires still trip the
circuit breaker. Also drop the opencode production-validation docs test:
it validates internal build-planning artifacts (.agent/) that are not
part of the shipped adapter.

* test(daemon): herd lease test crosses the 60% default handoff threshold

The six-agent herd test relied on createChecker's 50% default usage,
written when the default handoff threshold was 40%. After the 40->60
threshold change no checker crossed the threshold and zero handoff
prompts fired. Pin the herd at 70% so the test exercises the lease
cap as intended.

---------

Co-authored-by: Boris <noreply@anthropic.com>
grandamenium#702)

The `opencode` runtime and context-handoff lifecycle shipped in grandamenium#685/grandamenium#699
(CLI accepts `--runtime opencode`, auto-maps `--template agent` to the
agent-opencode bootstrap), but the behavioral/discoverability layer never
caught up:

- The agent-management skill (which teaches agents how to scaffold other
  agents) hard-coded a 2-way runtime choice (claude-code vs codex-app-server)
  across all 8 mirrors, leaving `--runtime opencode` orphaned.
- README.md and CLAUDE.md never mentioned the opencode runtime or the
  context-handoff lifecycle, so neither was discoverable from the docs.

Changes:
- agent-management SKILL.md (8 mirrors): STEP 0 now teaches the 3-runtime
  choice incl opencode + the `--runtime opencode` flow; runtime rule and
  summary-table row updated (security mirror gets the STEP 0 block only, as
  it lacks the other two fragments).
- README.md: opencode added to the multi-runtime bullet, templates table,
  and runtime config table; new paragraph documenting the opencode PTY and
  the context-handoff lifecycle (ctx_handoff_threshold default 60%).
- CLAUDE.md: templates line lists agent-codex + agent-opencode.

Docs-only; verified `--runtime`/auto-map/threshold facts against
src/cli/add-agent.ts and templates/agent-opencode. No OPENAI_API_KEY or
sqlite3 claims (neither is a cortextOS dependency).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…grandamenium#703)

The agentic-crm-assistant setup gathered preferences well but hand-waved the
actual tool connection: tool-discovery only DETECTED installed tools, and the
setup skill's Tools step dead-ended in a generic HUMAN task. So an agent that
knew the user's stack still could not get email/calendar/messaging authed.

This reworks the connect UX to a research-driven, CLI-first, per-domain loop:
- TOOL_CONNECTIONS.md: explicit preference order CLI > connector/MCP > browser
  for every domain; setup order now names the connect + verify steps; adds a
  Messaging domain (iMessage local reads, outbound gated by approval rules).
- tool-discovery/SKILL.md: rewrites detect-only into the full connect loop —
  once the user names a service for a domain, prefer a CLI, research it live if
  unknown, walk the user through install + auth in-conversation (user runs the
  auth command so no secret hits chat), then verify with a real read before
  moving on. Worked examples for Google (gog) and iMessage.
- agentic-crm-setup/SKILL.md: Tools step now drives that per-domain connect
  loop; removes the dead-end generic HUMAN task.

Docs/skill-only. Frontmatter intact; gog login / gog auth status confirmed to
exist (the skill now actually calls them).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
… caller's (grandamenium#636)

manage-cycle <action> <agent> resolved its working directory from the
CALLER's environment and ignored the target agent argument. A cycle
"created for skoolio" by an orchestrating agent landed in the caller's
own experiments/config.json — a second registry the target agent's
autoresearch loop never reads. Result: "created" cycles with zero
entries in the file the skill loops on, a silent no-op with no error.

Add resolveTargetAgentDir(env, target): resolves the target as a
sibling of the caller's agentDir first, then via the projectRoot
conventions resolveEnv uses; validates the agent name (path-traversal
guard); returns null when no candidate exists so manage-cycle fails
loudly on typos instead of writing a registry nobody reads. Self-
targeting keeps the previous cwd fallback for bare invocations.

Co-authored-by: Boris <noreply@anthropic.com>
…k-guard (SEC-1 L3) (grandamenium#704)

* feat(security): auto-install pre-push hook + windowed roster/cron leak-guard (SEC-1 L3)

Two defense-in-depth hardenings on top of the SEC-1 leak-guard work
(L1 CI scan, L2 branch protection, L4 gitignore):

L3(a) — Auto-install the tracked pre-push build+test gate.
`scripts/hooks/pre-push` and `scripts/setup-hooks.sh` already ship, but
nothing wired the installer up, so fresh clones never got the local gate.
- setup-hooks.sh is now NON-CLOBBERING: it installs only when no pre-push
  hook exists (or the existing one is byte-identical); if a different hook
  is already present it is left untouched. Safe/idempotent to re-run and
  never overwrites a user's own hook.
- install.mjs (new section 9b) and `cortextos init` both call the installer
  best-effort and NON-FATAL — a hook-install failure never aborts install
  or init. Gated on non-Windows + repo presence + installer presence.

L3(b) — Windowed roster+cron heuristic in leak-guard.sh.
The same-line ROSTER_CRON_RE misses a leaked ops table that splits an agent
name and its cron expression across adjacent rows. Added an awk windowed
scan (WINDOW=3) that flags a roster name and a cron expression co-occurring
within 3 lines, using the exact cron alternatives from the existing RE. It
fires only when the same-line check did not, so the class reports at most
once per file, and inherits the existing test/fixture skip. Verified no new
false positives against the full tree (leak-guard --tree HEAD stays clean).

Adds tests/leak-guard.test.sh cases: a multi-line roster+cron table must be
flagged; a far-apart name+cron control must stay clean.

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

* fix(security): non-clobber guard also catches a broken symlink at hook dest

`[[ -e "$dest" ]]` is false for a broken symlink (it follows the link and the
target is missing), so a broken-symlink pre-push would fall through the guard
and get cp-overwritten — a small hole in the never-overwrite guarantee. Add
`|| -L "$dest"` so a broken symlink is treated as an existing hook and left in
place. Verified: no-hook installs, identical reports already-installed,
different is skipped and preserved, broken symlink is skipped and preserved.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…adless crash-loop)

Claude Code 2.1.x shows an interactive "Bypass Permissions mode" acceptance
screen on first launch with --dangerously-skip-permissions, defaulting to
"1. No, exit". The prior auto-accept sent a bare Enter whenever recent output
contained 'trust' OR 'Yes' — and the bypass screen's "Yes, I accept" matched
'Yes', so headless agents selected "No, exit" and exited (code 1), crash-looping
and never onboarding.

Replace the two fixed-delay bare-Enter sends with a bounded poll that:
- distinguishes the bypass screen (Down-arrow + Enter to select "Yes, I accept")
  from the trust-folder screen (bare Enter), matching co-occurring ANSI-stripped
  tokens so normal output cannot trigger a stray keystroke;
- handles each screen at most once (one-shot guards);
- stops on isBootstrapped() and an unconditional 20s backstop.

Hardening beyond the original fix:
- bootstrap-guard the deferred confirm Enter so a late session bootstrap cannot
  swallow the CR into the live session;
- clear the poll immediately after the bypass Down+Enter is consumed, so the only
  hazardous injection cannot reach a live session without relying on the
  case-sensitive 'permissions' status-bar halt (which is fragile to CC TUI text
  changes — the exact failure mode this incident is about).

Extracted as a minimal single-file change from fork PR grandamenium#611
(neoturns/cortextos:fix/headless-agent-startup). The remainder of that PR
(bootstrapPattern, Windows binary probing, Telegram-optional onboarding) is
already present on main via independently merged changes, so only the
bypass-screen handling is new. Original work by neoturns.

Co-Authored-By: neoturns <neoturns@users.noreply.github.com>
Migrate any cortextOS agent from the claude-code runtime to the codex-app-server
runtime (non-destructive, dry-run by default). Includes detect/convert/verify
scripts, references, and a discriminating test suite.

Review hardening (PR grandamenium#849):
- MCP re-run is idempotent: a second --apply overwrites this agent's own prior
  migrated config.toml block instead of self-colliding on it.
- verify asserts every migrated-cron skills path EXISTS in the target (not just
  absence of the source substring) — catches a needs-human skill left dangling.
- cron enabled-state survives the union: a live-disabled cron is never resurrected
  by the config-wins merge.

Instance-generality (public artifact): the routing guardrail is parameterized, not
baked to any fleet. Principal chat id / principal name / orchestrator are DISCOVERED
from the source deployment (or passed via --principal-chat-id / --principal-name /
--orchestrator); when unknown the reroute SAFE-DEGRADES (crons flagged for human
review) rather than inventing an identity. APPROVAL guardrail always injected;
ROUTING guardrail only when principal+orchestrator known. No hardcoded personal or
fleet identifiers remain (regression-guarded in the test suite).

Tests: 83/83, each new fix convicts the old behavior and acquits the fix.
…codex-migration-skill

feat(community): claude-to-codex agent migration skill
Registers the claude-to-codex-migration skill (merged in grandamenium#849) in the
community catalog so members can discover and install it via
`cortextos bus install-community-item claude-to-codex-migration`.
Catalog entry only; the skill files landed in grandamenium#849. Completes the publish.
…laude-to-codex-migration

chore(community): register claude-to-codex-migration in catalog

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cadcfdb1fb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/pty/agent-pty.ts
} catch { /* leave unset if context.json is missing or malformed */ }
}

<<<<<<< HEAD

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Resolve the committed merge conflicts

When this commit is built, this unresolved diff3 marker is parsed as TypeScript and stops tsup/tsc before the daemon or CLI can be produced. The same unresolved-marker pattern remains across the changed runtime sources (including agent-manager.ts, agent-process.ts, and bus.ts), so resolve all conflict blocks and retain only the intended implementation before shipping the commit.

Useful? React with 👍 / 👎.

@loganbronstein

Copy link
Copy Markdown
Owner Author

⛔ DO NOT MERGE AS-IS — automated safeguard (boss agent, 2026-08-02).

This weekly-upstream-sync PR was committed WITH unresolved conflict markers (141 markers across 62 files) and, per codex assessment, contains NONE of the local round-6 auth-wedge (stuck-agent) fix symbols/modules. Merging it as-is would silently REMOVE the armed reliability protection currently live on the daemon (build cff418b5).

Correct path (in progress, gated): reject this conflict artifact, reconstruct the upstream integration in an isolated worktree, transplant the full round-6 series (through 7e92b87), run full tests, get Logan approval, then re-verify the scribe-only canary before any live pickup. Full assessment: outputs/pr25-auth-wedge-conflict-assessment-2026-08-02.md.

@loganbronstein

Copy link
Copy Markdown
Owner Author

Superseded by #26 (Weekly upstream sync 2026-08-09, 255 commits). Closing as superseded per the rolling weekly-sync supersession pattern (cf. #24 to #25). The upstream-reconstruction decision carries forward to #26, still held behind Logan's priority gate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet