This repository has no description
1import assert from "node:assert/strict"
2import test from "node:test"
3import { __loopTest } from "./loop"
4import type { Message } from "../types"
5import type { LoopState } from "./types"
6
7function makeState(): LoopState {
8 return {
9 conversation: [],
10 pendingInputs: [],
11 tokenCount: 0,
12 contextSize: 0,
13 toolInFlight: false,
14 memoryRecallCooldowns: {},
15 memoryRecallTurn: 0,
16 memoryRecallPending: false,
17 }
18}
19
20test("applyDiscordSendNudge appends a follow-up user nudge for unsent Discord replies", () => {
21 const state = makeState()
22 const turnMessages: Message[] = [
23 {
24 role: "user",
25 content: "[user/discord] [discord/dm] hey are you there",
26 },
27 {
28 role: "assistant",
29 content: "yeah, i'm here",
30 },
31 ]
32
33 const nudged = __loopTest.applyDiscordSendNudge(state, turnMessages)
34
35 assert.equal(nudged, true)
36 assert.equal(state.conversation.length, 1)
37 assert.match(String(state.conversation[0]?.content), /did not call discord_send/i)
38})
39
40test("applyDiscordSendNudge fires for [discord batch] envelopes (space, not slash)", () => {
41 const state = makeState()
42 const turnMessages: Message[] = [
43 {
44 role: "user",
45 content: "[user/discord] [discord batch] 2026-05-01T03:10:50.162Z -> 2026-05-01T03:11:22.198Z\n@meowskullz: hi\n@meowskullz: u there",
46 },
47 {
48 role: "assistant",
49 content: "yeah hi",
50 },
51 ]
52
53 const nudged = __loopTest.applyDiscordSendNudge(state, turnMessages)
54
55 assert.equal(nudged, true)
56 assert.equal(state.conversation.length, 1)
57 assert.match(String(state.conversation[0]?.content), /did not call discord_send/i)
58})
59
60test("applyDiscordSendNudge fires after a harness restart when the discord event is pre-turn context", () => {
61 const state = makeState()
62 state.conversation.push(
63 {
64 role: "user",
65 content: "[harness restarted — discord @ 2026-05-01T04:30:00.000Z]\n\n[discord/dm] hi starfish",
66 },
67 {
68 role: "assistant",
69 content: "i keep getting bounced by harness restarts sorry ^^ still here though! what's up?",
70 },
71 )
72
73 const turnStart = 1
74 const turnMessages: Message[] = [state.conversation[1]!]
75
76 const nudged = __loopTest.applyDiscordSendNudge(state, turnMessages, turnStart)
77
78 assert.equal(nudged, true)
79 assert.equal(state.conversation.length, 3)
80 assert.match(String(state.conversation[2]?.content), /did not call discord_send/i)
81})
82
83test("applyDiscordSendNudge does not fire when discord_send was already called", () => {
84 const state = makeState()
85 const turnMessages: Message[] = [
86 {
87 role: "user",
88 content: "[user/discord] [discord/channel] can you reply",
89 },
90 {
91 role: "assistant",
92 content: "sending now",
93 tool_calls: [
94 {
95 id: "call_123",
96 type: "function",
97 function: {
98 name: "discord_send",
99 arguments: "{\"channel_id\":\"1\",\"content\":\"sending now\"}",
100 },
101 },
102 ],
103 },
104 ]
105
106 const nudged = __loopTest.applyDiscordSendNudge(state, turnMessages)
107
108 assert.equal(nudged, false)
109 assert.equal(state.conversation.length, 0)
110})
111
112test("applyLoopGuardNudge appends an in-band user nudge and saves", async () => {
113 const state = makeState()
114 let saved = false
115
116 await __loopTest.applyLoopGuardNudge(
117 state,
118 {
119 waitForEvent: async () => {
120 throw new Error("unexpected wait")
121 },
122 waitForEventWithTimeout: async () => null,
123 injectIncomingEvent: () => {},
124 flushDeferredEvents: () => {},
125 clearSession: async () => {},
126 saveSession: async () => {
127 saved = true
128 },
129 },
130 "loop guard tripped after 120 turns",
131 )
132
133 assert.equal(saved, true)
134 assert.equal(state.conversation.length, 1)
135 assert.equal(state.conversation[0]?.role, "user")
136 assert.match(String(state.conversation[0]?.content), /loop guard tripped after 120 turns/i)
137})
138
139test("waitForNextEvent waits for and injects the next external event", async () => {
140 const calls: string[] = []
141 const event = {
142 source: "chat" as const,
143 triggeredAt: "2026-05-01T04:40:00.000Z",
144 content: "still here",
145 raw: {},
146 }
147
148 await __loopTest.waitForNextEvent(42, {
149 waitForEvent: async () => {
150 calls.push("wait")
151 return event
152 },
153 waitForEventWithTimeout: async () => null,
154 injectIncomingEvent: (_convId, incoming) => {
155 calls.push(`inject:${incoming.content}`)
156 assert.equal(_convId, 42)
157 assert.equal(incoming, event)
158 },
159 flushDeferredEvents: () => {},
160 clearSession: async () => {},
161 saveSession: async () => {},
162 })
163
164 assert.deepEqual(calls, ["wait", "inject:still here"])
165})