Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
151 lines
5.2 KiB
Go
151 lines
5.2 KiB
Go
package agentruntime
|
|
|
|
import (
|
|
"encoding/json"
|
|
"log/slog"
|
|
"strings"
|
|
|
|
activityshared "github.com/tutti-os/tutti/packages/agent/daemon/activity/events"
|
|
)
|
|
|
|
const cursorACPMethodTask = "cursor/task"
|
|
|
|
type cursorACPTaskExtensionDiagnostic struct {
|
|
ToolCallID string
|
|
AgentID string
|
|
Model string
|
|
SubagentType string
|
|
PromptLength int
|
|
DescriptionLength int
|
|
DurationMS any
|
|
HasPrompt bool
|
|
HasDescription bool
|
|
HasAgentID bool
|
|
HasDuration bool
|
|
}
|
|
|
|
func parseCursorACPTaskExtensionDiagnostic(raw json.RawMessage) (cursorACPTaskExtensionDiagnostic, bool) {
|
|
var params map[string]any
|
|
if err := json.Unmarshal(raw, ¶ms); err != nil || params == nil {
|
|
return cursorACPTaskExtensionDiagnostic{}, false
|
|
}
|
|
prompt, promptPresent := params["prompt"].(string)
|
|
description, descriptionPresent := params["description"].(string)
|
|
agentID := strings.TrimSpace(asString(params["agentId"]))
|
|
durationMS, durationPresent := int64Value(params["durationMs"])
|
|
diagnostic := cursorACPTaskExtensionDiagnostic{
|
|
ToolCallID: strings.TrimSpace(asString(params["toolCallId"])),
|
|
AgentID: agentID,
|
|
Model: strings.TrimSpace(asString(params["model"])),
|
|
SubagentType: cursorACPTaskSubagentTypeLogValue(params["subagentType"]),
|
|
PromptLength: len(prompt),
|
|
DescriptionLength: len(description),
|
|
DurationMS: nil,
|
|
HasPrompt: promptPresent,
|
|
HasDescription: descriptionPresent,
|
|
HasAgentID: agentID != "",
|
|
HasDuration: durationPresent,
|
|
}
|
|
if durationPresent {
|
|
diagnostic.DurationMS = durationMS
|
|
}
|
|
return diagnostic, true
|
|
}
|
|
|
|
func cursorACPTaskSubagentTypeLogValue(value any) string {
|
|
if value := strings.TrimSpace(asString(value)); value != "" {
|
|
return value
|
|
}
|
|
if custom, ok := value.(map[string]any); ok && len(custom) > 0 {
|
|
return "custom"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func logCursorACPTaskExtension(
|
|
config standardACPConfig,
|
|
session Session,
|
|
turnID string,
|
|
message acpMessage,
|
|
normalizer *acpTurnNormalizer,
|
|
) {
|
|
diagnostic, parsed := parseCursorACPTaskExtensionDiagnostic(message.Params)
|
|
slog.Info("agent session Cursor ACP task extension observed",
|
|
"event", "agent_session.cursor.task_extension",
|
|
"provider", config.provider,
|
|
"adapter", config.adapterName,
|
|
"room_id", session.RoomID,
|
|
"agent_session_id", session.AgentSessionID,
|
|
"provider_session_id", session.ProviderSessionID,
|
|
"turn_id", strings.TrimSpace(turnID),
|
|
"message_id", rawMessageLogValue(message.ID),
|
|
"inside_active_prompt", normalizer != nil && strings.TrimSpace(turnID) != "",
|
|
"params_parsed", parsed,
|
|
"tool_call_id", diagnostic.ToolCallID,
|
|
"has_agent_id", diagnostic.HasAgentID,
|
|
"agent_id", diagnostic.AgentID,
|
|
"subagent_type", diagnostic.SubagentType,
|
|
"model", diagnostic.Model,
|
|
"has_prompt", diagnostic.HasPrompt,
|
|
"prompt_length", diagnostic.PromptLength,
|
|
"has_description", diagnostic.HasDescription,
|
|
"description_length", diagnostic.DescriptionLength,
|
|
"has_duration_ms", diagnostic.HasDuration,
|
|
"duration_ms", diagnostic.DurationMS,
|
|
)
|
|
}
|
|
|
|
func logCursorACPTaskToolUpdate(
|
|
config standardACPConfig,
|
|
session Session,
|
|
turnID string,
|
|
updateType string,
|
|
update map[string]any,
|
|
) {
|
|
if !isCursorACPTaskToolUpdate(update) {
|
|
return
|
|
}
|
|
input, _ := acpToolCallRawInput(update).(map[string]any)
|
|
output, _ := acpToolCallRawOutput(update).(map[string]any)
|
|
slog.Info("agent session Cursor ACP task tool update observed",
|
|
"event", "agent_session.cursor.task_tool_update",
|
|
"provider", config.provider,
|
|
"adapter", config.adapterName,
|
|
"room_id", session.RoomID,
|
|
"agent_session_id", session.AgentSessionID,
|
|
"provider_session_id", session.ProviderSessionID,
|
|
"turn_id", strings.TrimSpace(turnID),
|
|
"update_type", updateType,
|
|
"tool_call_id", firstNonEmpty(asString(update["toolCallId"]), asString(update["callId"]), asString(update["id"])),
|
|
"raw_status", strings.TrimSpace(asString(update["status"])),
|
|
"resolved_status", acpResolvedToolCallStatus(update, string(activityshared.ActivityStatusRunning)),
|
|
"has_raw_input", len(input) > 0,
|
|
"input_has_agent_id", strings.TrimSpace(asString(input["agentId"])) != "",
|
|
"input_agent_id", strings.TrimSpace(asString(input["agentId"])),
|
|
"input_subagent_type", cursorACPTaskSubagentTypeLogValue(input["subagentType"]),
|
|
"input_model", strings.TrimSpace(asString(input["model"])),
|
|
"input_run_in_background", firstCursorACPBoolLogValue(input, "run_in_background", "runInBackground"),
|
|
"has_raw_output", len(output) > 0,
|
|
"output_is_background", firstCursorACPBoolLogValue(output, "isBackground", "is_background"),
|
|
"output_duration_ms", firstInt64ValueLogValue(output, "durationMs", "duration_ms"),
|
|
)
|
|
}
|
|
|
|
func isCursorACPTaskToolUpdate(update map[string]any) bool {
|
|
input, _ := acpToolCallRawInput(update).(map[string]any)
|
|
if strings.EqualFold(strings.TrimSpace(asString(input["_toolName"])), "task") {
|
|
return true
|
|
}
|
|
title := strings.ToLower(strings.TrimSpace(firstNonEmpty(asString(update["title"]), asString(update["name"]))))
|
|
return title == "task" || strings.HasPrefix(title, "task:")
|
|
}
|
|
|
|
func firstCursorACPBoolLogValue(source map[string]any, keys ...string) any {
|
|
for _, key := range keys {
|
|
if value, ok := source[key].(bool); ok {
|
|
return value
|
|
}
|
|
}
|
|
return nil
|
|
}
|