mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-01 15:32:11 +08:00
test(cli): cover approval-provenance metadata preservation
This commit is contained in:
@@ -35,6 +35,21 @@ export namespace PermissionProvenance {
|
||||
return "agent"
|
||||
}
|
||||
|
||||
/**
|
||||
* Preserve an existing `approval` marker when a tool part's metadata is replaced.
|
||||
*
|
||||
* The approval is written once during `ask()`, but tools freely overwrite `state.metadata`
|
||||
* during execution and on completion. Carry the prior `approval` onto the replacement unless
|
||||
* the replacement sets its own.
|
||||
*/
|
||||
export function carryApproval(
|
||||
prev: Record<string, unknown> | undefined,
|
||||
next: Record<string, unknown> | undefined,
|
||||
) {
|
||||
if (!next || !prev?.approval || "approval" in next) return next
|
||||
return { ...next, approval: prev.approval }
|
||||
}
|
||||
|
||||
/** Classify the winning rule of an auto-approval into an Approval payload. */
|
||||
export function classify(input: { rule?: Permission.Rule; agent: string; origins: Origins }): Approval {
|
||||
const rule = input.rule
|
||||
|
||||
@@ -22,6 +22,7 @@ import type { Provider } from "@/provider/provider"
|
||||
import { Question } from "@/question"
|
||||
// kilocode_change start
|
||||
import { KiloSessionProcessor, type ReviewTelemetry } from "@/kilocode/session/processor"
|
||||
import { PermissionProvenance } from "@/kilocode/permission/provenance" // kilocode_change
|
||||
import { KiloSessionOverflow } from "@/kilocode/session/overflow"
|
||||
import { KiloRoutedModel } from "@/kilocode/session/routed-model"
|
||||
import { Suggestion } from "@/kilocode/suggestion"
|
||||
@@ -270,13 +271,13 @@ export const layer = Layer.effect(
|
||||
) {
|
||||
const match = yield* readToolCall(toolCallID)
|
||||
// approval provenance is written once during ask() and must survive later tool metadata writes
|
||||
const carryApproval = (prev: Record<string, any> | undefined, next: Record<string, any> | undefined) => {
|
||||
if (!next || !prev?.approval || "approval" in next) return next
|
||||
return { ...next, approval: prev.approval }
|
||||
}
|
||||
if (!match || match.part.state.status !== "running") {
|
||||
const prev = ctx.toolmeta[toolCallID]
|
||||
ctx.toolmeta[toolCallID] = { ...prev, ...input, metadata: carryApproval(prev?.metadata, input.metadata) }
|
||||
ctx.toolmeta[toolCallID] = {
|
||||
...prev,
|
||||
...input,
|
||||
metadata: PermissionProvenance.carryApproval(prev?.metadata, input.metadata),
|
||||
}
|
||||
return
|
||||
}
|
||||
yield* updateToolCall(toolCallID, (part) => {
|
||||
@@ -286,7 +287,7 @@ export const layer = Layer.effect(
|
||||
state: {
|
||||
...part.state,
|
||||
title: input.title ?? part.state.title,
|
||||
metadata: carryApproval(part.state.metadata, input.metadata) ?? part.state.metadata,
|
||||
metadata: PermissionProvenance.carryApproval(part.state.metadata, input.metadata) ?? part.state.metadata,
|
||||
},
|
||||
}
|
||||
})
|
||||
@@ -306,7 +307,7 @@ export const layer = Layer.effect(
|
||||
if (!match || match.part.state.status !== "running") return
|
||||
// kilocode_change start - preserve approval provenance recorded during permission checks
|
||||
const prior = isRecord(match.part.state.metadata) ? match.part.state.metadata : undefined
|
||||
const metadata = prior?.approval ? { ...output.metadata, approval: prior.approval } : output.metadata
|
||||
const metadata = PermissionProvenance.carryApproval(prior, output.metadata) ?? output.metadata
|
||||
// kilocode_change end
|
||||
yield* session.updatePart({
|
||||
...match.part,
|
||||
|
||||
@@ -56,6 +56,32 @@ describe("PermissionProvenance", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("PermissionProvenance.carryApproval", () => {
|
||||
const approval = { source: "agent" as const, agent: "build" }
|
||||
|
||||
test("carries a prior approval onto a replacement that omits it", () => {
|
||||
// The tool overwrites metadata during execution; the approval written during ask() must survive.
|
||||
expect(PermissionProvenance.carryApproval({ approval }, { command: "echo hi" })).toEqual({
|
||||
command: "echo hi",
|
||||
approval,
|
||||
})
|
||||
})
|
||||
|
||||
test("does not override an approval the replacement sets itself", () => {
|
||||
const next = { approval: { source: "yolo" as const } }
|
||||
expect(PermissionProvenance.carryApproval({ approval }, next)).toBe(next)
|
||||
})
|
||||
|
||||
test("leaves the replacement untouched when there is no prior approval", () => {
|
||||
const next = { command: "echo hi" }
|
||||
expect(PermissionProvenance.carryApproval({ command: "old" }, next)).toBe(next)
|
||||
})
|
||||
|
||||
test("returns the replacement as-is when it is undefined", () => {
|
||||
expect(PermissionProvenance.carryApproval({ approval }, undefined)).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe("askPermission returns provenance", () => {
|
||||
const sessionID = SessionID.make("ses_prov")
|
||||
const agent: Agent.Info = {
|
||||
|
||||
Reference in New Issue
Block a user