my harness for niri
1
fork

Configure Feed

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

meow

+38 -13
+2
.gitignore
··· 13 13 # agent home — ignore everything except the example file 14 14 home/* 15 15 !home/soul.example.md 16 + 17 + .codex
+13
bun.lock
··· 13 13 "fastify": "^5.8.4", 14 14 "node-pty": "^1.1.0", 15 15 "openai": "^6.33.0", 16 + "sqlite-vec": "^0.1.9", 16 17 }, 17 18 "devDependencies": { 18 19 "@types/better-sqlite3": "^7.6.13", ··· 693 694 "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], 694 695 695 696 "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], 697 + 698 + "sqlite-vec": ["sqlite-vec@0.1.9", "", { "optionalDependencies": { "sqlite-vec-darwin-arm64": "0.1.9", "sqlite-vec-darwin-x64": "0.1.9", "sqlite-vec-linux-arm64": "0.1.9", "sqlite-vec-linux-x64": "0.1.9", "sqlite-vec-windows-x64": "0.1.9" } }, "sha512-L7XJWRIBNvR9O5+vh1FQ+IGkh/3D2AzVksW5gdtk28m78Hy8skFD0pqReKH1Yp0/BUKRGcffgKvyO/EON5JXpA=="], 699 + 700 + "sqlite-vec-darwin-arm64": ["sqlite-vec-darwin-arm64@0.1.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-jSsZpE42OfBkGL/ItyJTVCUwl6o6Ka3U5rc4j+UBDIQzC1ulSSKMEhQLthsOnF/MdAf1MuAkYhkdKmmcjaIZQg=="], 701 + 702 + "sqlite-vec-darwin-x64": ["sqlite-vec-darwin-x64@0.1.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-KDlVyqQT7pnOhU1ymB9gs7dMbSoVmKHitT+k1/xkjarcX8bBqPxWrGlK/R+C5WmWkfvWwyq5FfXfiBYCBs6PlA=="], 703 + 704 + "sqlite-vec-linux-arm64": ["sqlite-vec-linux-arm64@0.1.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-5wXVJ9c9kR4CHm/wVqXb/R+XUHTdpZ4nWbPHlS+gc9qQFVHs92Km4bPnCKX4rtcPMzvNis+SIzMJR1SCEwpuUw=="], 705 + 706 + "sqlite-vec-linux-x64": ["sqlite-vec-linux-x64@0.1.9", "", { "os": "linux", "cpu": "x64" }, "sha512-w3tCH8xK2finW8fQJ/m8uqKodXUZ9KAuAar2UIhz4BHILfpE0WM/MTGCRfa7RjYbrYim5Luk3guvMOGI7T7JQA=="], 707 + 708 + "sqlite-vec-windows-x64": ["sqlite-vec-windows-x64@0.1.9", "", { "os": "win32", "cpu": "x64" }, "sha512-y3gEIyy/17bq2QFPQOWLE68TYWcRZkBQVA2XLrTPHNTOp55xJi/BBBmOm40tVMDMjtP+Elpk6UBUXdaq+46b0Q=="], 696 709 697 710 "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], 698 711
+8 -4
scripts/chat.ts
··· 59 59 60 60 const STREAM_SETTLE_CHECK_MS = 250 61 61 62 + const restorePrompt = () => { 63 + rl.resume() 64 + process.stdin.resume() 65 + rl.prompt() 66 + } 67 + 62 68 const print = (line: string) => { 63 69 endActiveStream() 64 70 rl.pause() 65 71 process.stdout.write(`\r\x1b[K${line}\n`) 66 - rl.resume() 67 - rl.prompt(true) 72 + restorePrompt() 68 73 } 69 74 70 75 const startActiveStream = (kind: "text" | "thinking") => { ··· 104 109 process.stdout.write("\n") 105 110 activeStreamKind = null 106 111 activeStreamMuted = false 107 - rl.resume() 108 - rl.prompt(true) 112 + restorePrompt() 109 113 } 110 114 111 115 const scheduleStreamSettleCheck = () => {
+3 -2
src/runner/loop.test.ts
··· 2 2 import test from "node:test" 3 3 import { __loopTest } from "./loop.js" 4 4 import type { LoopState } from "./types.js" 5 + import type { Message } from "../types.js" 5 6 6 7 function makeState(): LoopState { 7 8 return { ··· 17 18 18 19 test("applyDiscordSendNudge appends a follow-up user nudge for unsent Discord replies", () => { 19 20 const state = makeState() 20 - const turnMessages = [ 21 + const turnMessages: Message[] = [ 21 22 { 22 23 role: "user", 23 24 content: "[user/discord] [discord/dm] hey are you there", ··· 60 61 61 62 test("applyDiscordSendNudge does not fire when discord_send was already called", () => { 62 63 const state = makeState() 63 - const turnMessages = [ 64 + const turnMessages: Message[] = [ 64 65 { 65 66 role: "user", 66 67 content: "[user/discord] [discord/channel] can you reply",
+6 -6
src/runner/loop.ts
··· 1 - import type OpenAI from "openai" 2 1 import { recordMetric } from "../metrics.js" 3 2 import { emit } from "../stream.js" 3 + import type { Message } from "../types.js" 4 4 import { 5 5 CONTEXT_COMPACT_TRIGGER_TOKENS, 6 6 ENABLE_THINKING, ··· 82 82 * conversational text but did not call discord_send. Injects a system 83 83 * nudge so the next turn actually delivers the message. 84 84 */ 85 - function isDiscordInputMessage(message: OpenAI.Chat.ChatCompletionMessage | OpenAI.Chat.ChatCompletionMessageParam): boolean { 85 + function isDiscordInputMessage(message: Message): boolean { 86 86 return message.role === "user" && typeof message.content === "string" && /\[discord\/(?:dm|batch|channel)\]/i.test(message.content) 87 87 } 88 88 89 89 function hasDiscordInputForTurn( 90 - conversation: OpenAI.Chat.ChatCompletionMessageParam[], 91 - turnMessages: OpenAI.Chat.ChatCompletionMessage[], 90 + conversation: Message[], 91 + turnMessages: Message[], 92 92 turnStart: number, 93 93 ): boolean { 94 94 if (turnMessages.some(isDiscordInputMessage)) return true ··· 108 108 109 109 function applyDiscordSendNudge( 110 110 state: LoopState, 111 - turnMessages: OpenAI.Chat.ChatCompletionMessage[], 112 - turnStart: number, 111 + turnMessages: Message[], 112 + turnStart = state.conversation.length, 113 113 ): boolean { 114 114 // Check if the assistant is responding to active Discord input, including 115 115 // the post-restart case where the triggering user message is already in the
+6 -1
src/runner/util.test.ts
··· 1 1 import assert from "node:assert/strict" 2 2 import test from "node:test" 3 + import type OpenAI from "openai" 3 4 import { isTransientTransportError, sanitizeMessages, shouldFallback } from "./util.js" 5 + 6 + type AssistantMessageWithReasoning = OpenAI.Chat.ChatCompletionAssistantMessageParam & { 7 + reasoning_content?: string 8 + } 4 9 5 10 test("sanitizeMessages backfills empty reasoning_content for assistant history", () => { 6 11 const messages = sanitizeMessages([ ··· 14 19 content: "reply with reasoning", 15 20 refusal: null, 16 21 reasoning_content: "thinking...", 17 - }, 22 + } as AssistantMessageWithReasoning, 18 23 ]) 19 24 20 25 const assistant = messages[0] as (typeof messages)[number] & { reasoning_content?: string }