my harness for niri
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

remove noise from summarizer

+153 -21
+153 -21
src/runner/util.ts
··· 878 878 const CONTEXT_SUMMARY_NOTE = 879 879 "Compressed notes of older conversation turns. If anything conflicts, trust newer raw messages." 880 880 const CONTEXT_SUMMARY_SEGMENTS_MARKER = "[segments]" 881 - const SUMMARY_LINE_MAX_CHARS = 180 881 + const SUMMARY_LINE_MAX_CHARS = 320 882 882 const SUMMARY_LINE_DEFAULT_EMPTY = "(no text)" 883 + const TOOL_ACK_RESULT = "(ok)" 884 + const WAIT_TOOL_RESULT = "Waiting for next event." 883 885 884 886 function asRecord(value: unknown): Record<string, unknown> | null { 885 887 return value && typeof value === "object" ? (value as Record<string, unknown>) : null ··· 921 923 return `${value.slice(0, maxChars - 3).trimEnd()}...` 922 924 } 923 925 924 - function assistantToolNames(message: Message): string[] { 926 + function assistantToolCalls(message: Message): { name: string; args: Record<string, unknown> }[] { 925 927 const record = asRecord(message) 926 928 const calls = record?.tool_calls 927 929 if (!Array.isArray(calls)) return [] 928 930 929 - const names: string[] = [] 931 + const out: { name: string; args: Record<string, unknown> }[] = [] 930 932 for (const call of calls) { 931 933 const callRecord = asRecord(call) 932 934 const fn = asRecord(callRecord?.function) 933 - if (typeof fn?.name === "string" && fn.name.trim()) names.push(fn.name.trim()) 935 + const name = typeof fn?.name === "string" ? fn.name.trim() : "" 936 + if (!name) continue 937 + let args: Record<string, unknown> = {} 938 + const rawArgs = fn?.arguments 939 + if (typeof rawArgs === "string" && rawArgs.trim()) { 940 + try { 941 + const parsed = JSON.parse(rawArgs) 942 + if (parsed && typeof parsed === "object") args = parsed as Record<string, unknown> 943 + } catch { 944 + // ignore malformed arg json 945 + } 946 + } else if (rawArgs && typeof rawArgs === "object") { 947 + args = rawArgs as Record<string, unknown> 948 + } 949 + out.push({ name, args }) 934 950 } 935 - return names 951 + return out 936 952 } 937 953 938 - function toolCallId(message: Message): string | null { 939 - const record = asRecord(message) 940 - return typeof record?.tool_call_id === "string" && record.tool_call_id.trim() ? record.tool_call_id.trim() : null 954 + function describeToolCall(call: { name: string; args: Record<string, unknown> }): string | null { 955 + const { name, args } = call 956 + if (name === "wait") return null 957 + if (name === "discord_send") { 958 + const content = typeof args.content === "string" ? args.content : "" 959 + const channelId = typeof args.channel_id === "string" ? args.channel_id : "" 960 + const channelTag = channelId ? `ch/${channelId.slice(-6)}` : "ch?" 961 + if (!content) return `discord_send -> ${channelTag}` 962 + return `discord_send -> ${channelTag}: ${normalizeSummaryText(content)}` 963 + } 964 + if (name === "discord_mark") { 965 + const itemId = typeof args.item_id === "string" ? args.item_id : "" 966 + const action = typeof args.action === "string" ? args.action : "" 967 + return `discord_mark ${action || "?"} ${itemId}`.trim() 968 + } 969 + if (name === "shell") { 970 + const cmd = typeof args.command === "string" ? args.command : "" 971 + return cmd ? `shell: ${normalizeSummaryText(cmd)}` : "shell" 972 + } 973 + if (name === "image_tool") { 974 + const p = typeof args.path === "string" ? args.path : "" 975 + return p ? `image_tool ${p}` : "image_tool" 976 + } 977 + if (name === "discord_backread" || name === "discord_inbox" || name === "discord_channels") { 978 + const channelId = typeof args.channel_id === "string" ? args.channel_id : "" 979 + return channelId ? `${name} ch/${channelId.slice(-6)}` : name 980 + } 981 + // Fallback: compact arg snippet 982 + const argKeys = Object.keys(args) 983 + if (argKeys.length === 0) return name 984 + const snippet = argKeys 985 + .slice(0, 3) 986 + .map((k) => `${k}=${truncateSummaryText(normalizeSummaryText(String(args[k] ?? "")), 40)}`) 987 + .join(" ") 988 + return `${name} ${snippet}`.trim() 941 989 } 942 990 943 - function summarizeMessageLine(message: Message): string { 991 + const DISCORD_BATCH_SKIP_PREFIXES = [ 992 + "[discord batch]", 993 + "new_messages=", 994 + "auto_seen_timeout=", 995 + "channel_flag_repairs=", 996 + "channel messages are context", 997 + "you can reply if useful", 998 + "pending preview:", 999 + ] 1000 + 1001 + function compactDiscordBatch(content: string): string { 1002 + const lines = content.split("\n") 1003 + const kept: string[] = [] 1004 + let inPendingPreview = false 1005 + for (const rawLine of lines) { 1006 + const line = rawLine.trim() 1007 + if (!line) continue 1008 + if (line === "pending preview:") { 1009 + inPendingPreview = true 1010 + continue 1011 + } 1012 + if (inPendingPreview) { 1013 + // pending preview block continues until we hit a non-bullet line 1014 + if (line.startsWith("- ")) continue 1015 + inPendingPreview = false 1016 + } 1017 + if (DISCORD_BATCH_SKIP_PREFIXES.some((p) => line.startsWith(p))) continue 1018 + kept.push(line) 1019 + } 1020 + return kept.join(" ") 1021 + } 1022 + 1023 + function compactToolResult(content: string): string | null { 1024 + const trimmed = content.trim() 1025 + if (!trimmed) return null 1026 + if (trimmed === WAIT_TOOL_RESULT) return null 1027 + // Compact discord_send / discord_mark ok JSON to a short ack 1028 + if (trimmed.startsWith("{")) { 1029 + try { 1030 + const parsed = JSON.parse(trimmed) 1031 + if (parsed && typeof parsed === "object") { 1032 + const rec = parsed as Record<string, unknown> 1033 + if (rec.ok === true) { 1034 + const sentId = typeof rec.sent_message_id === "string" ? rec.sent_message_id : null 1035 + if (sentId) return `${TOOL_ACK_RESULT} sent ${sentId.slice(-6)}` 1036 + const itemId = typeof rec.item_id === "string" ? rec.item_id : null 1037 + if (itemId) return `${TOOL_ACK_RESULT} ${itemId.slice(-6)}` 1038 + return TOOL_ACK_RESULT 1039 + } 1040 + if (rec.ok === false || typeof rec.error === "string") { 1041 + const err = typeof rec.error === "string" ? rec.error : "error" 1042 + return `error: ${err}` 1043 + } 1044 + } 1045 + } catch { 1046 + // fall through to default handling 1047 + } 1048 + } 1049 + return normalizeSummaryText(trimmed) 1050 + } 1051 + 1052 + function summarizeMessageLine(message: Message): string | null { 944 1053 const role = messageRole(message) 945 - const content = truncateSummaryText(normalizeSummaryText(messageStringContent(message)), SUMMARY_LINE_MAX_CHARS) 946 - const safeContent = content || SUMMARY_LINE_DEFAULT_EMPTY 1054 + const rawContent = messageStringContent(message) 947 1055 948 1056 if (role === "assistant") { 949 - const toolNames = assistantToolNames(message) 950 - if (toolNames.length > 0 && content) return `- assistant: ${safeContent} | tools: ${toolNames.join(", ")}` 951 - if (toolNames.length > 0) return `- assistant: tools: ${toolNames.join(", ")}` 952 - return `- assistant: ${safeContent}` 1057 + const calls = assistantToolCalls(message) 1058 + const callDescs = calls.map(describeToolCall).filter((d): d is string => d !== null) 1059 + const text = normalizeSummaryText(rawContent) 1060 + // Drop pure wait-only assistant turns (no text, only filtered out wait calls) 1061 + if (!text && callDescs.length === 0) return null 1062 + const parts: string[] = [] 1063 + if (text) parts.push(text) 1064 + if (callDescs.length > 0) parts.push(`[${callDescs.join(" | ")}]`) 1065 + return `- assistant: ${truncateSummaryText(parts.join(" "), SUMMARY_LINE_MAX_CHARS)}` 953 1066 } 1067 + 954 1068 if (role === "tool") { 955 - const id = toolCallId(message) ?? "unknown" 956 - return `- tool(${id}): ${safeContent}` 1069 + const compact = compactToolResult(rawContent) 1070 + if (compact === null) return null 1071 + return `- tool: ${truncateSummaryText(compact, SUMMARY_LINE_MAX_CHARS)}` 1072 + } 1073 + 1074 + if (role === "user") { 1075 + const stripped = rawContent.startsWith("[incoming — discord]") 1076 + ? compactDiscordBatch(rawContent) 1077 + : normalizeSummaryText(rawContent) 1078 + const safe = stripped || SUMMARY_LINE_DEFAULT_EMPTY 1079 + return `- user: ${truncateSummaryText(safe, SUMMARY_LINE_MAX_CHARS)}` 957 1080 } 958 - if (role === "user") return `- user: ${safeContent}` 959 - if (role === "system") return `- system: ${safeContent}` 960 - return `- ${role || "message"}: ${safeContent}` 1081 + 1082 + if (role === "system") { 1083 + const text = truncateSummaryText(normalizeSummaryText(rawContent), SUMMARY_LINE_MAX_CHARS) || SUMMARY_LINE_DEFAULT_EMPTY 1084 + return `- system: ${text}` 1085 + } 1086 + 1087 + const text = truncateSummaryText(normalizeSummaryText(rawContent), SUMMARY_LINE_MAX_CHARS) || SUMMARY_LINE_DEFAULT_EMPTY 1088 + return `- ${role || "message"}: ${text}` 961 1089 } 962 1090 963 1091 function countLeadingSystemMessages(messages: Message[]): number { ··· 1014 1142 const tail = messages.slice(tailStart) 1015 1143 if (middle.length === 0) return null 1016 1144 1017 - const transcript = middle.map((m) => summarizeMessageLine(m)).join("\n").slice(0, maxTranscriptChars) 1145 + const transcript = middle 1146 + .map((m) => summarizeMessageLine(m)) 1147 + .filter((line): line is string => line !== null) 1148 + .join("\n") 1149 + .slice(0, maxTranscriptChars) 1018 1150 const summaryPrompt: OpenAI.Chat.ChatCompletionMessageParam[] = [ 1019 1151 { 1020 1152 role: "system",