mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
feat(cli): add GitHub Copilot CLI agent support via ACP (#1245)
* feat(cli): add GitHub Copilot CLI agent support via ACP Wrap `copilot --acp --stdio` for remote sessions and spawn the native TUI locally, with full hub/web integration for spawn, resume, and permissions. Fixes tiann/hapi#362 Co-Authored-By: HAPI <noreply@hapi.run> Co-authored-by: Cursor <cursoragent@cursor.com> * feat(copilot): agent modes, models, slash/file UX, local session sync Add Interactive/Plan/Autopilot (fleet is slash-only), subscription-aware model discovery, web StatusBar/permission UX, @ file mentions, and fix local Safe Yolo plus session-id locator for handoff/resume. Co-authored-by: Cursor <cursoragent@cursor.com> * chore: re-trigger Codex PR review after auth outage Co-authored-by: Cursor <cursoragent@cursor.com> * chore: retry Codex PR review Co-authored-by: Cursor <cursoragent@cursor.com> * fix(copilot): preserve agent mode on resume and apply via ACP set_mode Resume was dropping copilotAgentMode so Plan/Autopilot reset to interactive. Also switch local/remote mode application to --mode / session set_mode instead of slash prompts. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(copilot): wake remote loop when agent mode changes Empty isolated queue tick lets setMode apply without inventing a user prompt. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(copilot): confirm mode changes before persisting Await Copilot mode changes and expose discovered models so session state reflects backend acceptance. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(copilot): guard mode discovery and slash updates Keep model probes within runner roots and preserve active sessions when mode switching is unavailable or rejected. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(copilot): preserve resume and auto semantics Deduplicate Copilot resume rows, apply Auto explicitly, and fail closed on denied permissions. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(copilot): close permission and model discovery gaps Keep write-capable commands pending in read-only mode, extend model probe RPCs, and preserve explicit model validation before session creation. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(copilot): persist runtime model and agent mode Fallback to ACP model options when direct model switching is unavailable and retain Copilot agent mode across hub restarts. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(copilot): normalize composer auto selection Use the null session sentinel for Copilot Auto so the composer selects and resets default models consistently. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(copilot): reject local permission mode changes * style(copilot): remove trailing blank line * fix(copilot): secure local config handoffs * fix(copilot): reject local agent mode slashes * fix(copilot): reject mode changes during turns * fix(copilot): consume rejected slash updates * fix(copilot): preserve thinking across slash handling * fix(copilot): stabilize async config changes * fix(copilot): roll back rejected startup model * fix(copilot): preserve cancellation and file mentions * fix(copilot): hide local permission controls * fix(deps): support clean workspace installs * test(copilot): account for spawn mode argument * fix(copilot): attribute usage to active model --------- Co-authored-by: HAPI <noreply@hapi.run> Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
HAPI
parent
b67f4e56e5
commit
f10fbc7496
@@ -0,0 +1,19 @@
|
||||
import type { AgentFlavor } from '@hapi/protocol'
|
||||
|
||||
/** Agents that share codex-family permission modes (default / read-only / safe-yolo / yolo). */
|
||||
export const CODEX_FAMILY_PERMISSION_AGENTS = [
|
||||
'codex',
|
||||
'gemini',
|
||||
'kimi',
|
||||
'copilot',
|
||||
'opencode'
|
||||
] as const satisfies readonly AgentFlavor[]
|
||||
|
||||
export type CodexFamilyPermissionAgent = typeof CODEX_FAMILY_PERMISSION_AGENTS[number]
|
||||
|
||||
export function usesCodexFamilyPermissionModes(
|
||||
flavor: string | null | undefined
|
||||
): flavor is CodexFamilyPermissionAgent {
|
||||
return typeof flavor === 'string'
|
||||
&& (CODEX_FAMILY_PERMISSION_AGENTS as readonly string[]).includes(flavor)
|
||||
}
|
||||
@@ -288,6 +288,7 @@ export default {
|
||||
'newSession.opencodeModel.default': 'Default',
|
||||
'newSession.reasoningEffort': 'Reasoning effort',
|
||||
'newSession.collaborationMode': 'Collaboration mode',
|
||||
'newSession.copilotAgentMode': 'Agent mode',
|
||||
'newSession.fastMode': 'Fast mode',
|
||||
'newSession.yolo': 'YOLO mode',
|
||||
'newSession.yolo.title': 'Bypass approvals and sandbox',
|
||||
@@ -937,6 +938,7 @@ export default {
|
||||
'misc.noMachines': 'No machines available',
|
||||
'misc.machine': 'Machine',
|
||||
'misc.collaborationMode': 'Collaboration Mode',
|
||||
'misc.copilotAgentMode': 'Agent Mode',
|
||||
'misc.permissionMode': 'Permission Mode',
|
||||
'misc.model': 'Model',
|
||||
'misc.reasoningEffort': 'Reasoning Effort',
|
||||
|
||||
@@ -292,6 +292,7 @@ export default {
|
||||
'newSession.opencodeModel.default': '默认',
|
||||
'newSession.reasoningEffort': '推理强度',
|
||||
'newSession.collaborationMode': '协作模式',
|
||||
'newSession.copilotAgentMode': '代理模式',
|
||||
'newSession.fastMode': '快速模式',
|
||||
'newSession.yolo': 'YOLO 模式',
|
||||
'newSession.yolo.title': '跳过审批和沙箱',
|
||||
@@ -941,6 +942,7 @@ export default {
|
||||
'misc.noMachines': '无可用机器',
|
||||
'misc.machine': '机器',
|
||||
'misc.collaborationMode': '协作模式',
|
||||
'misc.copilotAgentMode': '代理模式',
|
||||
'misc.permissionMode': '权限模式',
|
||||
'misc.model': '模型',
|
||||
'misc.reasoningEffort': '推理强度',
|
||||
|
||||
@@ -26,6 +26,8 @@ export const queryKeys = {
|
||||
machineOpencodeModelsForCwd: (machineId: string, cwd: string) => ['machine-opencode-models', machineId, cwd] as const,
|
||||
machineGrokModelsForCwd: (machineId: string, cwd: string) => ['machine-grok-models', machineId, cwd] as const,
|
||||
sessionGrokModels: (sessionId: string) => ['session-grok-models', sessionId] as const,
|
||||
sessionCopilotModels: (sessionId: string) => ['session-copilot-models', sessionId] as const,
|
||||
machineCopilotModelsForCwd: (machineId: string, cwd: string) => ['machine-copilot-models', machineId, cwd] as const,
|
||||
sessionGrokReasoningEffortOptions: (sessionId: string) => ['session-grok-reasoning-effort-options', sessionId] as const,
|
||||
skills: (sessionId: string) => ['skills', sessionId] as const,
|
||||
scratchlist: (sessionId: string) => ['scratchlist', sessionId] as const,
|
||||
|
||||
@@ -286,6 +286,12 @@ describe('sessionResume — regression for all other flavor ids', () => {
|
||||
path: '/p', host: 'h', flavor: 'kimi', kimiSessionId: 'ki-1',
|
||||
})).toBe('ki-1')
|
||||
})
|
||||
|
||||
it('copilot', () => {
|
||||
expect(resolveAgentSessionIdFromMetadata({
|
||||
path: '/p', host: 'h', flavor: 'copilot', copilotSessionId: 'cp-1',
|
||||
})).toBe('cp-1')
|
||||
})
|
||||
it('claude (default branch)', () => {
|
||||
expect(resolveAgentSessionIdFromMetadata({
|
||||
path: '/p', host: 'h', flavor: 'claude', claudeSessionId: 'cl-1',
|
||||
|
||||
@@ -19,6 +19,7 @@ export function resolveAgentSessionIdFromMetadata(
|
||||
case 'grok': return metadata.grokSessionId ?? undefined
|
||||
case 'cursor': return metadata.cursorSessionId ?? undefined
|
||||
case 'kimi': return metadata.kimiSessionId ?? undefined
|
||||
case 'copilot': return metadata.copilotSessionId ?? undefined
|
||||
case 'pi': return metadata.piSessionId ?? undefined
|
||||
default: return metadata.claudeSessionId ?? undefined
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user