hosts/copilot/host.psd1
|
@{ # Identity Kind = 'copilot' DisplayName = 'GitHub Copilot CLI' # Feature 202 iteration 002, T029 (B4F-131): Copilot's question box. It is named so the guidance can keep it out of # workshop questions; it is not a StructuredQuestionPrimitive, because a box answer is never captured. QuestionTool = 'ask_user' CrewRuntimeDisplayName = 'Squad' Status = 'supported' SchemaVersion = 1 MenuPriority = 3 # Interactive host-selection menu priority (iter-011). Note: `--host` flag non-interactive default REMAINS `copilot` (specrew-start.ps1) for predictability in CI/automation. # Binary detection Binary = 'copilot' InstallUrl = 'https://docs.github.com/en/copilot/how-tos/copilot-cli' InstallGuidance = 'GitHub Copilot CLI not found on PATH. Install: https://docs.github.com/en/copilot/how-tos/copilot-cli' # Runtime layout SkillRoot = '.github/skills' LegacySkillRoots = @('.copilot/skills') HasUserSlashCommandSurface = $true InstructionsFile = '.github/copilot-instructions.md' AgentDir = '.squad/agents/' # Spec-kit + Squad coupling SpeckitAiFlag = 'copilot' PreferredAgent = 'copilot' # Phase B handler file (not yet present) HandlersFile = 'handlers.ps1' CoordinatorRulesFile = 'coordinator-rules.psd1' # Feature 171 (FR-013, T014): refocus hook bindings. Copilot hooks went GA # 2026-02-25 (research-matrix.md — the earlier no-surface finding is # obsolete). B2 via sessionStart (additionalContext verified); B1 pending # local source-value verification; B3 and the T070 turn baseline use the # documented userPromptSubmitted event (per-tool-call delivery remains latency-rejected). RefocusHookBindings = @{ BoundTriggers = @('b2', 'b3') Events = @('sessionStart', 'userPromptSubmitted') TurnStartCapability = @{ Mode = 'exact'; NativeEvent = 'userPromptSubmitted'; DispatcherEvent = 'UserPromptSubmit' } SettingsFile = '~/.copilot/hooks/specrew-refocus.json' # hooks-dir model: wholly Specrew-owned file OptOutMarkerFile = '.specrew/runtime/refocus-hooks-optout-copilot' DispatcherPath = '.specify/extensions/specrew-speckit/scripts/specrew-hook-dispatcher.ps1' ConfigShape = 'event-map' CommandMode = 'launcher-file' SettingsVersion = 1 OwnsSettingsFile = $true DispatcherRuntime = @{ # Feature 202 iteration 002, T016 (FR-028, DA-1): the payload fields ONLY this host sends, on both of the channels # it uses; a payload matching either alternative is Copilot's. Copilot runs Claude's hooks too (B4F-129), and # this is what lets those hooks stand down. # 1. Copilot's own hooks: camelCase sessionId, never session_id or hook_event_name (every fixture under # tests/fixtures/hook-payloads/copilot and probe/copilot). # 2. Claude's hooks, as Copilot runs them (B4F-144): snake_case like Claude's, but always with a timestamp, # which no Claude or Codex payload carries, and never Claude's prompt_id or permission_mode, nor Codex's # turn_id or model. Measured by the layer-1 probe on Copilot 1.0.101 (probe/copilot-claude-hooks), every # event, sub-agents included. PayloadSignature = @{ Default = @( @{ AllOf = @('sessionId'); NoneOf = @('session_id', 'hook_event_name') }, @{ AllOf = @('hook_event_name', 'session_id', 'timestamp'); NoneOf = @('prompt_id', 'permission_mode', 'turn_id', 'model', 'sessionId') } ) } BootstrapDeliveryEvents = @('SessionStart') B3DeliveryEvents = @('PostToolUse', 'UserPromptSubmit') RefocusTriggerByEvent = @{ PostToolUse = 'b3'; UserPromptSubmit = 'b3' } SuppressedRefocusEvents = @() OutputShape = 'additionalContext' DecisionOnlyEvents = @() # FR-004 (185) stop-block lever (verified, research/stop-block-capability-matrix.md): Copilot agentStop # {"decision":"block","reason":...} forces another agent turn. CAVEATS: fail-open (a non-zero exit lets # the turn end packet-less -> best-effort, not bulletproof) and NO built-in loop guard -> the provider's # own consecutive-block cap is the loop guard here. StopBlockShape = 'decision-block' BootstrapDeliveryMode = 'inline' # Feature 202 T002 (DA-1, option B): this host's INPUT contract is data. The dispatcher's single # normalizer (ConvertTo-SpecrewTurnRecord) reads it; shared code names no host. Field paths may be # dotted (a.b.c). Measured payloads: tests/fixtures/hook-payloads/<host>/ (202 research.md, T001). InputFieldMap = @{ SessionId = @('sessionId') Prompt = @('prompt') TranscriptPath = @('transcriptPath') Source = @('source') ToolName = @('toolName') ToolOutcome = @('toolResult.toolTelemetry.properties.outcome') ToolQuestion = @('toolResult.toolTelemetry.restrictedProperties.question') } # Evaluated in order; the first matching rule decides. A prompt that matches no host-injected rule # is typed-user, and only typed-user can produce a receipt (FR-006). hook-output-journal: the prompt # equals text this dispatcher emitted (a replayed Stop reason). prompt-pattern: a host envelope or a # replayed Specrew directive. OriginRules = @( # Feature 202 iteration 002, T028 (B4F-147): a sub-agent's task text reaches the prompt hook as a prompt. A payload # carrying a sub-agent field was not typed by the human, whatever its text, so it can never be an approval. # Copilot marks its own channel's sub-agent payloads agentId and parentAgentId (probe/copilot, 1.0.101). @{ Origin = 'host-injected'; Rule = 'payload-field'; Fields = @('agentId', 'parentAgentId', 'agent_id', 'parent_agent_id') }, @{ Origin = 'host-injected'; Rule = 'hook-output-journal' }, @{ Origin = 'host-injected'; Rule = 'prompt-pattern'; Pattern = '(?is)^\s*<(?:task-notification|hook_prompt|turn_aborted|system-reminder|environment_context)\b' }, @{ Origin = 'host-injected'; Rule = 'prompt-pattern'; Pattern = '(?is)^\s*(?:Specrew:|Specrew\s+review\s+[-\u2014]|\[specrew-|AWAITING YOUR VERDICT:|BOUNDARY NOT READY FOR A VERDICT:|Campaign review authority\b)' }, @{ Origin = 'typed-user'; Rule = 'prompt-present' } ) # Named transform from the dispatcher's shared registry: a dismissed ask_user form is an absence, # never an answer (B4F-113; 1.0.96 emits no hook event for it, the legacy string is kept under test). InputTransform = 'ask-user-dismissal-text' } Registrations = @( @{ Event = 'sessionStart'; DispatcherEvent = 'SessionStart'; HandlerShape = 'dual-shell-entry'; TimeoutSec = 30 }, @{ Event = 'userPromptSubmitted'; DispatcherEvent = 'UserPromptSubmit'; HandlerShape = 'dual-shell-entry'; TimeoutSec = 30 }, @{ Event = 'agentStop'; DispatcherEvent = 'agentStop'; HandlerShape = 'dual-shell-entry'; TimeoutSec = 30 } ) } } |