mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-21 11:15:36 +00:00
9bc51a8a35
Commit A of the coordinated proposal slice (pulse-internal; the aicontracts interface replacement and enterprise migration follow as one window because pulse-enterprise builds against this tree via a replace directive). patrol_propose_action is a side-effect-free, mutation-none capture whose schema carries only resource_id, capability_name, params, and reason. Registry policy rejects it outside the Patrol investigation profile before the handler runs, and the same check keeps it out of every other profile's projected manifest. Correlation identity (proposal, finding, investigation, evidence) is injected from trusted orchestration context through the request-local ProposalCapture sink, which executor clones share so one run has exactly one capture. Tool calls now carry an explicit invocation envelope (tool-use ID, name, arguments) through ExecuteInvocation; the ID rides the context because per-turn tool calls execute concurrently. The sink keys on call identity plus payload fingerprint: idempotent replay re-succeeds, the same ID with a different payload latches a terminal integrity error, and a second distinct valid proposal latches terminal ambiguity - both terminal states invalidate the captured proposal, since concurrency makes first-wins nondeterministic. Proposals count only after catalog validation: advertised capability, declared/required/enum parameters, and sensitive parameters rejected before success with no value echo in any output. ExecuteInvestigationStream returns proposal cardinality as a structured result with typed errors for ambiguity, integrity violations, and the failed-attempts-only case (never collapsed into the valid zero-proposal conclusion); ListInvestigationTools projects through the identical profile path. Proposal parameter values exist only transiently for provider continuation and validation: the canonical exposure projector redacts them from the durable transcript and every tool_start/progress/end stream event, proven end-to-end with a scripted provider run that also verifies the provider continuation keeps raw values. Essential proof included: two concurrent valid proposal calls produce ErrProposalAmbiguous and a nil proposal regardless of execution order.
48 lines
1.4 KiB
Go
48 lines
1.4 KiB
Go
package chat
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/agentcapabilities"
|
|
)
|
|
|
|
func formatToolInputForFrontend(toolName string, input map[string]interface{}, emptyIfNil bool) (inputStr string, rawInput string) {
|
|
// Canonical exposure projection: every user-visible or durable view
|
|
// of tool-call arguments routes through here, so restricted values
|
|
// (proposal params) never leave the transient provider/validation
|
|
// path.
|
|
input = agentcapabilities.RedactToolCallArgumentsForExposure(toolName, input)
|
|
if input == nil {
|
|
if emptyIfNil {
|
|
return "", ""
|
|
}
|
|
return "{}", ""
|
|
}
|
|
|
|
if inputBytes, err := json.Marshal(input); err == nil {
|
|
rawInput = string(inputBytes)
|
|
}
|
|
|
|
// Special handling for command execution tools to avoid showing raw JSON.
|
|
if toolName == agentcapabilities.PulseControlToolName || toolName == agentcapabilities.PulseRunCommandToolName || toolName == "control" {
|
|
if cmd, ok := input["command"].(string); ok {
|
|
return fmt.Sprintf("Running: %s", cmd), rawInput
|
|
}
|
|
if action, ok := input["action"].(string); ok {
|
|
target := ""
|
|
if t, ok := input["guest_id"].(string); ok {
|
|
target = t
|
|
} else if t, ok := input["container"].(string); ok {
|
|
target = t
|
|
}
|
|
return fmt.Sprintf("%s %s", action, target), rawInput
|
|
}
|
|
}
|
|
|
|
if inputBytes, err := json.Marshal(input); err == nil {
|
|
return string(inputBytes), rawInput
|
|
}
|
|
return "{}", rawInput
|
|
}
|