BeforeTool-hook + kort-vy (strukturerade godkännanden) + release på master-push
Feature: strukturerade verktygsgodkännanden
- shared: protokoll för approval (daemon:approval, server:approval[-resolved],
web:approval-decision, server:approval-decision)
- daemon: hook-läge ('agent-helm hook' — samma binär), lokal 127.0.0.1-endpoint
som hooken POST:ar till, long-poll tills beslut; graceful default (allow) när
ingen UI; timeout-fallback; skickar AGENT_HELM_HOOK_PORT till agentens env
- server: routar daemon:approval -> prenumeranter, beslut -> daemon + döljer kort
- web: reaktiv approvals-lista + kort-overlay (Tillåt/Neka), mobilanpassad
CI:
- release.yaml triggar nu även på master-push -> rullande 'latest'-release
(versionerade releaser kvar på v*-taggar); install.sh hämtar 'latest'
Docs: hook-konfig (gemini settings.json) + hook-env i .env.example + DEPLOY.md.
Säkerhet: hook-endpoint binder endast 127.0.0.1; inga hemligheter i repot.
Verifierat lokalt: hook->daemon->server->kort->beslut->hook ger {"decision":"allow"};
alla paket typecheckar; web bygger.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,11 +8,20 @@ export interface HelmHandlers {
|
||||
onExit?: (sessionId: string, code: number) => void;
|
||||
}
|
||||
|
||||
/** En väntande hook-förfrågan (verktygsgodkännande) -> visas som kort. */
|
||||
export interface ApprovalCard {
|
||||
sessionId: string;
|
||||
approvalId: string;
|
||||
toolName: string;
|
||||
toolInput: unknown;
|
||||
}
|
||||
|
||||
export function createHelmClient() {
|
||||
const connected = ref(false);
|
||||
const connecting = ref(false);
|
||||
const error = ref<string | null>(null);
|
||||
const sessions = ref<SessionMeta[]>([]);
|
||||
const approvals = ref<ApprovalCard[]>([]);
|
||||
const handlers: HelmHandlers = {};
|
||||
|
||||
let ws: WebSocket | null = null;
|
||||
@@ -55,6 +64,15 @@ export function createHelmClient() {
|
||||
);
|
||||
handlers.onExit?.(msg.sessionId, msg.code);
|
||||
break;
|
||||
case "server:approval":
|
||||
approvals.value = [
|
||||
...approvals.value,
|
||||
{ sessionId: msg.sessionId, approvalId: msg.approvalId, toolName: msg.toolName, toolInput: msg.toolInput },
|
||||
];
|
||||
break;
|
||||
case "server:approval-resolved":
|
||||
approvals.value = approvals.value.filter((a) => a.approvalId !== msg.approvalId);
|
||||
break;
|
||||
case "server:error":
|
||||
error.value = msg.message;
|
||||
connecting.value = false;
|
||||
@@ -83,8 +101,15 @@ export function createHelmClient() {
|
||||
ws = null;
|
||||
connected.value = false;
|
||||
}
|
||||
function approve(sessionId: string, approvalId: string, decision: "allow" | "deny"): void {
|
||||
send({ type: "web:approval-decision", sessionId, approvalId, decision });
|
||||
approvals.value = approvals.value.filter((a) => a.approvalId !== approvalId);
|
||||
}
|
||||
|
||||
return { connected, connecting, error, sessions, handlers, connect, disconnect, subscribe, sendInput };
|
||||
return {
|
||||
connected, connecting, error, sessions, approvals, handlers,
|
||||
connect, disconnect, subscribe, sendInput, approve,
|
||||
};
|
||||
}
|
||||
|
||||
export type HelmClient = ReturnType<typeof createHelmClient>;
|
||||
|
||||
Reference in New Issue
Block a user