Allow legitimate repeated slash commands in long sessions by replacing session-lifetime dedup with a short-lived TTL cache.
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Refine continuation agent resolution to prefer session-state agent fallback while keeping compaction-specific protection. Replace sticky boolean compaction flag with a short-lived timestamp guard so unresolved agents are blocked only during the immediate post-compaction window, avoiding long-lived suppression and preserving existing continuation behavior.
After compaction, message history is truncated and the original agent
(e.g. Prometheus) can no longer be resolved from messages. The todo
continuation enforcer would then inject a continuation prompt with
agent=undefined, causing the host to default to General -- which has
write permissions Prometheus should never have.
Root cause chain:
1. handler.ts had no session.compacted handler (unlike Atlas)
2. idle-event.ts relied on finding a compaction marker in truncated
message history -- the marker disappears after real compaction
3. continuation-injection.ts proceeded when agentName was undefined
because the skipAgents check only matched truthy agent names
4. prometheus-md-only/agent-resolution.ts did not filter compaction
agent from message history fallback results
Fixes:
- Add session.compacted handler that sets hasRecentCompaction state flag
- Replace fragile history-based compaction detection with state flag
- Block continuation injection when agent is unknown post-compaction
- Filter compaction agent in Prometheus agent resolution fallback
Use namespace import pattern (import * as sender) to prevent cross-file
spy leakage in Bun's shared module state. Move restoreAllMocks to
beforeEach for proper cleanup ordering.
🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode) assistance
runBunInstallWithDetails() defaulted to outputMode:"inherit", causing
bun install stdout/stderr to leak into the TUI when callers omitted the
option. Changed default to "pipe" so output is captured silently.
Also fixed stale mock in background-update-check.test.ts: the test was
mocking runBunInstall (unused) instead of runBunInstallWithDetails, and
returning boolean instead of BunInstallResult.
Introduce git worktree list --porcelain parsing following upstream opencode patterns. Exports listWorktrees() for full worktree enumeration with branch info alongside existing detectWorktreePath().
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
background-update-check.ts was using runBunInstall() which defaults to outputMode:"inherit", leaking bun install stdout/stderr into the background session. Reverted to runBunInstallWithDetails({ outputMode: "pipe" }) and explicitly logs result.error on failure.
Restores the accidentally deleted test case asserting that sibling dependencies (e.g. other:"1.0.0") are preserved in package.json after a plugin version sync.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
The previous pattern `(-[\w.]+)?` used `\w` which excludes hyphens, causing versions like `1.2.3-alpha-1` and `1.2.3-rc-test` to be misclassified as unpinned tags. Updated both plugin-entry.ts and sync-package-json.ts (which share the definition) to the spec-compliant pattern that allows dot-separated identifiers using [0-9A-Za-z-] and optional build metadata.
Also adds String() coercion before .trim() in sync-package-json.ts to guard against a TypeError if the parsed JSON value for currentVersion is non-string at runtime.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Prune interval created inside hook was not exposed for disposal,
preventing cleanup on plugin unload.
- Add dispose() method that clears the prune interval
- Export dispose in hook return type
Tests: 2 pass, 6 expects
setInterval for model availability monitoring was never cleared,
keeping the hook alive indefinitely with no dispose mechanism.
- Add dispose() method to RuntimeFallbackHook that clears interval
- Track intervalId in hook state for cleanup
- Export dispose in hook return type
Tests: 3 pass, 10 expects
processedCommands and recentResults Sets grew infinitely because
Date.now() in dedup keys made deduplication impossible and no
session.deleted cleanup existed.
- Extract ProcessedCommandStore with maxSize cap and TTL-based eviction
- Add session cleanup on session.deleted event
- Remove Date.now() from dedup keys for effective deduplication
- Add dispose() for interval cleanup
Tests: 3 pass, 9 expects
When users switch from pinned version to tag in opencode.json (e.g.,
3.10.0 -> @latest), the cache package.json still contains the resolved
version. This causes bun install to reinstall the old version instead
of resolving the new tag.
This adds syncCachePackageJsonToIntent() which updates the cache
package.json to match user intent before running bun install. Uses
atomic writes (temp file + rename) with UUID-based temp names for
concurrent safety.
Critical changes:
- Treat all sync errors as abort conditions (file_not_found,
plugin_not_in_deps, parse_error, write_error) to prevent corrupting
a bad cache state further
- Remove dead code (unreachable revert branch for pinned versions)
- Add tests for all error paths and atomic write cleanup