mirror of
https://github.com/github/spec-kit.git
synced 2026-08-15 14:02:04 +02:00
* feat(events): context injection for opencode and JSON-envelope agent hooks Adds first-class context injection to agent runtime events: 1. opencode: maps session_start to experimental.chat.system.transform (injects into system prompt) and user_prompt_submit to chat.message (injects synthetic TextPart). TS plugin captures runEvent stdout (stdio pipe, encoding utf-8) and pushes into output objects. Part IDs derive from output.parts[last].id to preserve OpenCode's prt_ brand and prevent session schema crashes. 2. JSON-envelope hook wrapping: adds events_context_envelope to IntegrationBase so agents that require JSON on stdout receive their target envelope via the dispatcher's 5th argument: - gemini, tabnine, qwen, devin: hookSpecificOutput.additionalContext on session_start/user_prompt_submit; suppress on non-injectable events (prevents systemMessage user-facing noise) - copilot: top-level additionalContext on session_start - cursor: top-level additional_context on session_start; suppress elsewhere - claude, codex: plain stdout passthrough (already injected) 3. Dispatcher template and resolve_and_run_event_command parse the 5th envelope arg and wrap stdout accordingly. Tests added for opencode TextPart schema, part ID derivation, envelope command generation, and dispatcher output wrapping. All 162 events/integration tests pass. * fix(events): address code review on #3934 - Qwen/Gemini/Tabnine/Devin: include native hookEventName inside hookSpecificOutput envelope (required by Qwen's hooks spec). Thread the native event name from the integration's CANONICAL_TO_NATIVE through _dispatcher_command as a 6th dispatcher argument, through the dispatcher template's main()/_run_inline()/_emit(), and through resolve_and_run_event_command()/_emit_event_stdout(). - Copilot: map user_prompt_submit to additionalContext (previously unmapped, breaking per-prompt context injection despite Copilot CLI supporting it via userPromptSubmitted). - OpenCode: guard experimental.chat.system.transform so canonical session_start handlers only run when input.sessionID is present — OpenCode fires this hook for non-session operations (e.g. agent generation) with no sessionID. Assisted-by: opencode (model: glm-5.2, supervised) * fix(events): address second Copilot review round on #3934 - Positional arg alignment: always emit default timeout (60s) as the 4th dispatcher argument even when timeout_seconds is omitted, so the envelope (5th) and native_event (6th) land in the correct argv slots. Previously, omitting timeout_seconds caused the envelope to be parsed as an invalid timeout, silently falling back to plain stdout. - OpenCode session_start caching: cache handler output per sessionID in the generated TS plugin so non-idempotent handlers (setup, telemetry, file-mutating scripts) run once per session instead of on every LLM request. Cache is evicted on session.deleted. - Updated PR description to reflect Copilot user_prompt_submit now maps to additionalContext (was documented as plain/unprocessed). Assisted-by: opencode (model: glm-5.2, supervised)
92 lines
3.1 KiB
Python
92 lines
3.1 KiB
Python
"""Devin for Terminal integration — skills-based agent.
|
|
|
|
Devin uses the ``.devin/skills/speckit-<name>/SKILL.md`` layout and
|
|
reads project context from ``AGENTS.md`` at the repo root. The CLI
|
|
binary is ``devin`` and skills are invoked via ``/<name>`` inside an
|
|
interactive ``devin`` session.
|
|
|
|
See: https://cli.devin.ai/docs/extensibility/skills/overview
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from ..base import IntegrationOption, SkillsIntegration
|
|
|
|
|
|
class DevinIntegration(SkillsIntegration):
|
|
"""Integration for Cognition AI's Devin for Terminal."""
|
|
|
|
key = "devin"
|
|
config = {
|
|
"name": "Devin for Terminal",
|
|
"folder": ".devin/",
|
|
"commands_subdir": "skills",
|
|
"install_url": "https://cli.devin.ai/docs",
|
|
"requires_cli": True,
|
|
}
|
|
registrar_config = {
|
|
"dir": ".devin/skills",
|
|
"format": "markdown",
|
|
"args": "$ARGUMENTS",
|
|
"extension": "/SKILL.md",
|
|
}
|
|
|
|
CANONICAL_TO_NATIVE = {
|
|
"session_start": "SessionStart",
|
|
"pre_tool_use": "PreToolUse",
|
|
"post_tool_use": "PostToolUse",
|
|
"session_end": "SessionEnd",
|
|
"user_prompt_submit": "UserPromptSubmit",
|
|
"stop": "Stop",
|
|
}
|
|
events_config_file = ".devin/hooks.v1.json"
|
|
# Devin's hooks.v1.json is a root event map ({"PreToolUse": [...]}) with no
|
|
# top-level "hooks" wrapper (U2), unlike the settings.json formats. The
|
|
# json-root-nested writer/remover operate directly on the root event keys.
|
|
events_format = "json-root-nested"
|
|
# Devin's hooks protocol is JSON-stdout; additionalContext is the
|
|
# documented injection field for SessionStart/UserPromptSubmit (C13).
|
|
events_context_envelope = {
|
|
"*": "suppress",
|
|
"session_start": "hookSpecificOutput",
|
|
"user_prompt_submit": "hookSpecificOutput",
|
|
}
|
|
|
|
def build_exec_args(
|
|
self,
|
|
prompt: str,
|
|
*,
|
|
model: str | None = None,
|
|
output_json: bool = True,
|
|
) -> list[str] | None:
|
|
"""Build non-interactive CLI args for Devin for Terminal.
|
|
|
|
Devin supports ``devin -p <prompt>`` for single-turn execution
|
|
and ``--model`` for model selection, but its CLI has no flag
|
|
for structured JSON output. When ``output_json`` is requested,
|
|
Devin is still dispatched normally and returns plain-text
|
|
stdout instead of structured JSON. ``requires_cli=True`` is
|
|
kept on the integration for tool detection.
|
|
"""
|
|
args = [self._resolve_executable(), "-p", prompt]
|
|
self._apply_extra_args_env_var(args)
|
|
if model:
|
|
args.extend(["--model", model])
|
|
return args
|
|
|
|
@classmethod
|
|
def options(cls) -> list[IntegrationOption]:
|
|
# Compose with super() so the base class declares --events for this
|
|
# event-capable integration; otherwise --integration-options
|
|
# "--events false" is rejected as unknown (#8).
|
|
opts = super().options()
|
|
opts.append(
|
|
IntegrationOption(
|
|
"--skills",
|
|
is_flag=True,
|
|
default=True,
|
|
help="Install as agent skills (default for Devin)",
|
|
),
|
|
)
|
|
return opts
|