ring/platforms/opencode/plugin/lifecycle/router.ts
Gandalf f85aa61440
feat: add OpenCode platform integration (consolidate ring-for-opencode)
Move OpenCode runtime plugin and installer into Ring monorepo under platforms/opencode/.
The installer reads skills, agents, and commands directly from the Ring monorepo's
canonical directories (default/, dev-team/, pm-team/, etc.) — zero asset duplication.

What's included:
- installer.sh: reads from Ring dirs, applies frontmatter/tool transforms, installs to ~/.config/opencode/
- plugin/: TypeScript runtime (RingUnifiedPlugin) with hooks, lifecycle, loaders
- src/: CLI (doctor, config-manager)
- prompts/: session-start and context-injection templates
- standards/: coding standards (from dev-team/docs/)
- ring.jsonc: default config with full 86-skill/35-agent/33-command inventory

What's NOT included (intentionally):
- assets/ directory: eliminated, content comes from Ring monorepo
- scripts/codereview/: eliminated, replaced by mithril
- using-ring-opencode skill: uses canonical using-ring instead

Transforms applied by installer:
- Agent: type→mode, strip version/changelog/output_schema/input_schema
- Skill: keep name+description frontmatter, body unchanged
- Command: strip argument-hint (unsupported by OpenCode)
- All: normalize tool names (Bash→bash, Read→read, etc.)
- All: strip Model Requirement sections from agents

Replaces: LerianStudio/ring-for-opencode repository
Generated-by: Gandalf
AI-Model: claude-opus-4
2026-03-07 22:46:47 -03:00

96 lines
2.6 KiB
TypeScript

/**
* Ring Lifecycle Router
*
* Routes OpenCode lifecycle events to Ring's plugin handlers.
* Maps OpenCode events to Ring hook registry.
*/
import type { RingConfig } from "../config/index.js"
import { cleanupOldState, deleteState, getSessionId } from "../utils/state.js"
/**
* OpenCode event structure.
*/
export interface OpenCodeEvent {
type: string
properties?: Record<string, unknown>
}
/**
* Dependencies for lifecycle router.
*/
export interface LifecycleRouterDeps {
projectRoot: string
ringConfig: RingConfig
}
/**
* Create the event handler that routes lifecycle events.
*
* Event mappings:
* - session.created -> Reset context state, cleanup old files
* - session.idle -> Hook execution (via RingUnifiedPlugin)
* - session.error -> Hook execution (via RingUnifiedPlugin)
* - todo.updated -> Task completion hook
* - experimental.session.compacting -> Context injection
*/
export function createLifecycleRouter(deps: LifecycleRouterDeps) {
const { projectRoot, ringConfig } = deps
const debug = process.env.RING_DEBUG === "true"
if (debug) {
console.debug("[ring] Lifecycle router initialized", {
disabledAgents: ringConfig.disabled_agents?.length ?? 0,
disabledSkills: ringConfig.disabled_skills?.length ?? 0,
})
}
return async (input: { event: OpenCodeEvent }): Promise<void> => {
const { event } = input
const eventType = event.type
if (debug) {
console.debug(`[ring] Event: ${eventType}`)
}
// session.created - Initialize session
if (eventType === "session.created") {
const props = event.properties as Record<string, unknown> | undefined
const eventSessionId = (props?.sessionID as string | undefined) ?? getSessionId()
// Reset context usage state for new session
deleteState(projectRoot, "context-usage", eventSessionId)
// Clean up old state files (> 7 days)
cleanupOldState(projectRoot, 7)
if (debug) {
console.debug("[ring] Session initialized, state reset")
}
return
}
// session.idle / session.error / todo.updated are handled via hook execution
// in RingUnifiedPlugin (this router only manages state side effects).
if (
eventType === "session.idle" ||
eventType === "session.error" ||
eventType === "todo.updated"
) {
return
}
// Other events - no action needed
}
}
/**
* Event type constants for type safety.
*/
export const EVENTS = {
SESSION_CREATED: "session.created",
SESSION_IDLE: "session.idle",
SESSION_ERROR: "session.error",
SESSION_DELETED: "session.deleted",
TODO_UPDATED: "todo.updated",
MESSAGE_PART_UPDATED: "message.part.updated",
} as const