Files
spec-kit/src/specify_cli
kanfilandGitHub 99970560db feat(events): context injection for opencode and JSON-envelope agent hooks (#3934)
* 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)
2026-08-04 07:32:30 -05:00
..