test(hooks): remove unsafe test assertions
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -37,7 +37,7 @@ function createFakeTimeouts(): FakeTimeouts {
|
|||||||
callback,
|
callback,
|
||||||
args,
|
args,
|
||||||
})
|
})
|
||||||
return id as unknown as ReturnType<typeof setTimeout>
|
return testCoerce<ReturnType<typeof setTimeout>>(id)
|
||||||
}) as typeof setTimeout
|
}) as typeof setTimeout
|
||||||
|
|
||||||
globalThis.clearTimeout = ((id?: number) => {
|
globalThis.clearTimeout = ((id?: number) => {
|
||||||
@@ -243,7 +243,7 @@ describe("executeCompact lock management", () => {
|
|||||||
await executeCompact(sessionID, msg, autoCompactState, mockClient, directory, pluginConfig)
|
await executeCompact(sessionID, msg, autoCompactState, mockClient, directory, pluginConfig)
|
||||||
|
|
||||||
// then: Toast should be shown
|
// then: Toast should be shown
|
||||||
const toastCalls = (mockClient.tui.showToast as any).mock.calls
|
const toastCalls = (testCoerce(mockClient.tui.showToast)).mock.calls
|
||||||
const blockedToast = toastCalls.find(
|
const blockedToast = toastCalls.find(
|
||||||
(call: any) => call[0]?.body?.title === "Compact In Progress",
|
(call: any) => call[0]?.body?.title === "Compact In Progress",
|
||||||
)
|
)
|
||||||
@@ -276,7 +276,7 @@ describe("executeCompact lock management", () => {
|
|||||||
await executeCompact(sessionID, msg, autoCompactState, mockClient, directory, pluginConfig)
|
await executeCompact(sessionID, msg, autoCompactState, mockClient, directory, pluginConfig)
|
||||||
|
|
||||||
// then: Should show failure toast
|
// then: Should show failure toast
|
||||||
const toastCalls = (mockClient.tui.showToast as any).mock.calls
|
const toastCalls = (testCoerce(mockClient.tui.showToast)).mock.calls
|
||||||
const failureToast = toastCalls.find(
|
const failureToast = toastCalls.find(
|
||||||
(call: any) => call[0]?.body?.title === "Auto Compact Failed",
|
(call: any) => call[0]?.body?.title === "Auto Compact Failed",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ function createImmediateTimeouts(): () => void {
|
|||||||
|
|
||||||
globalThis.setTimeout = ((callback: (...args: unknown[]) => void, _delay?: number, ...args: unknown[]) => {
|
globalThis.setTimeout = ((callback: (...args: unknown[]) => void, _delay?: number, ...args: unknown[]) => {
|
||||||
callback(...args)
|
callback(...args)
|
||||||
return 0 as unknown as ReturnType<typeof setTimeout>
|
return testCoerce<ReturnType<typeof setTimeout>>(0)
|
||||||
}) as typeof setTimeout
|
}) as typeof setTimeout
|
||||||
|
|
||||||
globalThis.clearTimeout = ((_: ReturnType<typeof setTimeout>) => {}) as typeof clearTimeout
|
globalThis.clearTimeout = ((_: ReturnType<typeof setTimeout>) => {}) as typeof clearTimeout
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ describe("runSummarizeRetryStrategy", () => {
|
|||||||
//#given
|
//#given
|
||||||
const timeoutCalls: TimeoutCall[] = []
|
const timeoutCalls: TimeoutCall[] = []
|
||||||
globalThis.setTimeout = ((_: (...args: unknown[]) => void, delay?: number) => {
|
globalThis.setTimeout = ((_: (...args: unknown[]) => void, delay?: number) => {
|
||||||
const handle = timeoutCalls.length + 1 as unknown as ReturnType<typeof setTimeout>
|
const handle = testCoerce<ReturnType<typeof setTimeout>>(timeoutCalls.length + 1)
|
||||||
timeoutCalls.push({ handle, delay: delay ?? 0 })
|
timeoutCalls.push({ handle, delay: delay ?? 0 })
|
||||||
return handle
|
return handle
|
||||||
}) as typeof setTimeout
|
}) as typeof setTimeout
|
||||||
@@ -132,7 +132,7 @@ describe("runSummarizeRetryStrategy", () => {
|
|||||||
let scheduledCallback: (() => void) | undefined
|
let scheduledCallback: (() => void) | undefined
|
||||||
globalThis.setTimeout = ((callback: (...args: unknown[]) => void, _delay?: number) => {
|
globalThis.setTimeout = ((callback: (...args: unknown[]) => void, _delay?: number) => {
|
||||||
scheduledCallback = () => callback()
|
scheduledCallback = () => callback()
|
||||||
return 1 as unknown as ReturnType<typeof setTimeout>
|
return testCoerce<ReturnType<typeof setTimeout>>(1)
|
||||||
}) as typeof setTimeout
|
}) as typeof setTimeout
|
||||||
|
|
||||||
autoCompactState.pendingCompact.add(sessionID)
|
autoCompactState.pendingCompact.add(sessionID)
|
||||||
@@ -176,7 +176,7 @@ describe("runSummarizeRetryStrategy", () => {
|
|||||||
autoCompactState.emptyContentAttemptBySession.set(sessionID, 3)
|
autoCompactState.emptyContentAttemptBySession.set(sessionID, 3)
|
||||||
autoCompactState.retryTimerBySession.set(
|
autoCompactState.retryTimerBySession.set(
|
||||||
sessionID,
|
sessionID,
|
||||||
1 as unknown as ReturnType<typeof setTimeout>,
|
testCoerce<ReturnType<typeof setTimeout>>(1),
|
||||||
)
|
)
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ describe("atlas background task retry", () => {
|
|||||||
callback: () => (callback as LongTimerCallback)(...args),
|
callback: () => (callback as LongTimerCallback)(...args),
|
||||||
cleared: false,
|
cleared: false,
|
||||||
})
|
})
|
||||||
return id as unknown as ReturnType<typeof setTimeout>
|
return testCoerce<ReturnType<typeof setTimeout>>(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
return originalSetTimeout(callback, delay, ...args)
|
return originalSetTimeout(callback, delay, ...args)
|
||||||
@@ -120,7 +120,7 @@ describe("atlas background task retry", () => {
|
|||||||
|
|
||||||
let backgroundRunning = true
|
let backgroundRunning = true
|
||||||
const promptMock = mock(async () => ({}))
|
const promptMock = mock(async () => ({}))
|
||||||
const hook = createAtlasHook({
|
const hook = createAtlasHook(testCoerce<PluginInput>({
|
||||||
directory: testDir,
|
directory: testDir,
|
||||||
client: {
|
client: {
|
||||||
session: {
|
session: {
|
||||||
@@ -128,13 +128,13 @@ describe("atlas background task retry", () => {
|
|||||||
messages: async () => ({ data: [] }),
|
messages: async () => ({ data: [] }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput, {
|
}), {
|
||||||
directory: testDir,
|
directory: testDir,
|
||||||
backgroundManager: {
|
backgroundManager: testCoerce<NonNullable<Parameters<typeof createAtlasHook>[1]>["backgroundManager"] & {
|
||||||
getTasksByParentSession: () => backgroundRunning ? [{ status: "running" }] : [],
|
|
||||||
} as unknown as NonNullable<Parameters<typeof createAtlasHook>[1]>["backgroundManager"] & {
|
|
||||||
getTasksByParentSession: (sessionID: string) => Array<{ status: string }>
|
getTasksByParentSession: (sessionID: string) => Array<{ status: string }>
|
||||||
},
|
}>({
|
||||||
|
getTasksByParentSession: () => backgroundRunning ? [{ status: "running" }] : [],
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@@ -161,7 +161,7 @@ describe("atlas background task retry", () => {
|
|||||||
|
|
||||||
let backgroundRunning = true
|
let backgroundRunning = true
|
||||||
const promptMock = mock(async () => ({}))
|
const promptMock = mock(async () => ({}))
|
||||||
const hook = createAtlasHook({
|
const hook = createAtlasHook(testCoerce<PluginInput>({
|
||||||
directory: testDir,
|
directory: testDir,
|
||||||
client: {
|
client: {
|
||||||
session: {
|
session: {
|
||||||
@@ -169,13 +169,13 @@ describe("atlas background task retry", () => {
|
|||||||
messages: async () => ({ data: [] }),
|
messages: async () => ({ data: [] }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput, {
|
}), {
|
||||||
directory: testDir,
|
directory: testDir,
|
||||||
backgroundManager: {
|
backgroundManager: testCoerce<NonNullable<Parameters<typeof createAtlasHook>[1]>["backgroundManager"] & {
|
||||||
getTasksByParentSession: () => backgroundRunning ? [{ status: "running" }] : [],
|
|
||||||
} as unknown as NonNullable<Parameters<typeof createAtlasHook>[1]>["backgroundManager"] & {
|
|
||||||
getTasksByParentSession: (sessionID: string) => Array<{ status: string }>
|
getTasksByParentSession: (sessionID: string) => Array<{ status: string }>
|
||||||
},
|
}>({
|
||||||
|
getTasksByParentSession: () => backgroundRunning ? [{ status: "running" }] : [],
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@@ -204,7 +204,7 @@ describe("atlas background task retry", () => {
|
|||||||
|
|
||||||
let remainingRunningRetries = 2
|
let remainingRunningRetries = 2
|
||||||
const promptMock = mock(async () => ({}))
|
const promptMock = mock(async () => ({}))
|
||||||
const hook = createAtlasHook({
|
const hook = createAtlasHook(testCoerce<PluginInput>({
|
||||||
directory: testDir,
|
directory: testDir,
|
||||||
client: {
|
client: {
|
||||||
session: {
|
session: {
|
||||||
@@ -212,9 +212,11 @@ describe("atlas background task retry", () => {
|
|||||||
messages: async () => ({ data: [] }),
|
messages: async () => ({ data: [] }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput, {
|
}), {
|
||||||
directory: testDir,
|
directory: testDir,
|
||||||
backgroundManager: {
|
backgroundManager: testCoerce<NonNullable<Parameters<typeof createAtlasHook>[1]>["backgroundManager"] & {
|
||||||
|
getTasksByParentSession: (sessionID: string) => Array<{ status: string }>
|
||||||
|
}>({
|
||||||
getTasksByParentSession: () => {
|
getTasksByParentSession: () => {
|
||||||
if (remainingRunningRetries > 0) {
|
if (remainingRunningRetries > 0) {
|
||||||
remainingRunningRetries -= 1
|
remainingRunningRetries -= 1
|
||||||
@@ -223,9 +225,7 @@ describe("atlas background task retry", () => {
|
|||||||
|
|
||||||
return []
|
return []
|
||||||
},
|
},
|
||||||
} as unknown as NonNullable<Parameters<typeof createAtlasHook>[1]>["backgroundManager"] & {
|
}),
|
||||||
getTasksByParentSession: (sessionID: string) => Array<{ status: string }>
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@@ -258,7 +258,7 @@ describe("atlas background task retry", () => {
|
|||||||
const promptAsyncMock = mock(async () => ({}))
|
const promptAsyncMock = mock(async () => ({}))
|
||||||
let backgroundCheckCount = 0
|
let backgroundCheckCount = 0
|
||||||
|
|
||||||
const hook = createAtlasHook({
|
const hook = createAtlasHook(testCoerce<PluginInput>({
|
||||||
directory: testDir,
|
directory: testDir,
|
||||||
client: {
|
client: {
|
||||||
session: {
|
session: {
|
||||||
@@ -266,9 +266,11 @@ describe("atlas background task retry", () => {
|
|||||||
messages: async () => ({ data: [] }),
|
messages: async () => ({ data: [] }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput, {
|
}), {
|
||||||
directory: testDir,
|
directory: testDir,
|
||||||
backgroundManager: {
|
backgroundManager: testCoerce<NonNullable<Parameters<typeof createAtlasHook>[1]>["backgroundManager"] & {
|
||||||
|
getTasksByParentSession: (sessionID: string) => Array<{ status: string }>
|
||||||
|
}>({
|
||||||
getTasksByParentSession: () => {
|
getTasksByParentSession: () => {
|
||||||
backgroundCheckCount += 1
|
backgroundCheckCount += 1
|
||||||
if (backgroundCheckCount === 1) {
|
if (backgroundCheckCount === 1) {
|
||||||
@@ -281,9 +283,7 @@ describe("atlas background task retry", () => {
|
|||||||
|
|
||||||
return []
|
return []
|
||||||
},
|
},
|
||||||
} as unknown as NonNullable<Parameters<typeof createAtlasHook>[1]>["backgroundManager"] & {
|
}),
|
||||||
getTasksByParentSession: (sessionID: string) => Array<{ status: string }>
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@@ -313,7 +313,7 @@ describe("atlas background task retry", () => {
|
|||||||
|
|
||||||
let backgroundRunning = true
|
let backgroundRunning = true
|
||||||
const promptAsyncMock = mock(async () => ({}))
|
const promptAsyncMock = mock(async () => ({}))
|
||||||
const hook = createAtlasHook({
|
const hook = createAtlasHook(testCoerce<PluginInput>({
|
||||||
directory: testDir,
|
directory: testDir,
|
||||||
client: {
|
client: {
|
||||||
session: {
|
session: {
|
||||||
@@ -321,13 +321,13 @@ describe("atlas background task retry", () => {
|
|||||||
messages: async () => ({ data: [] }),
|
messages: async () => ({ data: [] }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput, {
|
}), {
|
||||||
directory: testDir,
|
directory: testDir,
|
||||||
backgroundManager: {
|
backgroundManager: testCoerce<NonNullable<Parameters<typeof createAtlasHook>[1]>["backgroundManager"] & {
|
||||||
getTasksByParentSession: () => backgroundRunning ? [{ status: "running" }] : [],
|
|
||||||
} as unknown as NonNullable<Parameters<typeof createAtlasHook>[1]>["backgroundManager"] & {
|
|
||||||
getTasksByParentSession: (sessionID: string) => Array<{ status: string }>
|
getTasksByParentSession: (sessionID: string) => Array<{ status: string }>
|
||||||
},
|
}>({
|
||||||
|
getTasksByParentSession: () => backgroundRunning ? [{ status: "running" }] : [],
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@@ -366,7 +366,7 @@ describe("atlas background task retry", () => {
|
|||||||
let backgroundRunning = true
|
let backgroundRunning = true
|
||||||
let descendantAgent = "atlas"
|
let descendantAgent = "atlas"
|
||||||
const promptAsyncMock = mock(async () => ({}))
|
const promptAsyncMock = mock(async () => ({}))
|
||||||
const hook = createAtlasHook({
|
const hook = createAtlasHook(testCoerce<PluginInput>({
|
||||||
directory: testDir,
|
directory: testDir,
|
||||||
client: {
|
client: {
|
||||||
session: {
|
session: {
|
||||||
@@ -384,18 +384,18 @@ describe("atlas background task retry", () => {
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput, {
|
}), {
|
||||||
directory: testDir,
|
directory: testDir,
|
||||||
backgroundManager: {
|
backgroundManager: testCoerce<NonNullable<Parameters<typeof createAtlasHook>[1]>["backgroundManager"] & {
|
||||||
|
getTasksByParentSession: (sessionID: string) => Array<{ status: string }>
|
||||||
|
}>({
|
||||||
getTasksByParentSession: (currentSessionID: string) => {
|
getTasksByParentSession: (currentSessionID: string) => {
|
||||||
if (currentSessionID !== descendantSessionID) {
|
if (currentSessionID !== descendantSessionID) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
return backgroundRunning ? [{ status: "running" }] : []
|
return backgroundRunning ? [{ status: "running" }] : []
|
||||||
},
|
},
|
||||||
} as unknown as NonNullable<Parameters<typeof createAtlasHook>[1]>["backgroundManager"] & {
|
}),
|
||||||
getTasksByParentSession: (sessionID: string) => Array<{ status: string }>
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@@ -424,7 +424,7 @@ describe("atlas background task retry", () => {
|
|||||||
|
|
||||||
const deferredPrompt = createDeferred<{}>()
|
const deferredPrompt = createDeferred<{}>()
|
||||||
const promptAsyncMock = mock(() => deferredPrompt.promise)
|
const promptAsyncMock = mock(() => deferredPrompt.promise)
|
||||||
const hook = createAtlasHook({
|
const hook = createAtlasHook(testCoerce<PluginInput>({
|
||||||
directory: testDir,
|
directory: testDir,
|
||||||
client: {
|
client: {
|
||||||
session: {
|
session: {
|
||||||
@@ -432,7 +432,7 @@ describe("atlas background task retry", () => {
|
|||||||
messages: async () => ({ data: [] }),
|
messages: async () => ({ data: [] }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput)
|
}))
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const firstIdle = hook.handler({ event: { type: "session.idle", properties: { sessionID } } })
|
const firstIdle = hook.handler({ event: { type: "session.idle", properties: { sessionID } } })
|
||||||
@@ -462,7 +462,7 @@ describe("atlas background task retry", () => {
|
|||||||
promptAsyncMock.mockImplementationOnce(() => deferredPrompt.promise)
|
promptAsyncMock.mockImplementationOnce(() => deferredPrompt.promise)
|
||||||
promptAsyncMock.mockImplementationOnce(async () => ({}))
|
promptAsyncMock.mockImplementationOnce(async () => ({}))
|
||||||
|
|
||||||
const hook = createAtlasHook({
|
const hook = createAtlasHook(testCoerce<PluginInput>({
|
||||||
directory: testDir,
|
directory: testDir,
|
||||||
client: {
|
client: {
|
||||||
session: {
|
session: {
|
||||||
@@ -470,13 +470,13 @@ describe("atlas background task retry", () => {
|
|||||||
messages: async () => ({ data: [] }),
|
messages: async () => ({ data: [] }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput, {
|
}), {
|
||||||
directory: testDir,
|
directory: testDir,
|
||||||
backgroundManager: {
|
backgroundManager: testCoerce<NonNullable<Parameters<typeof createAtlasHook>[1]>["backgroundManager"] & {
|
||||||
getTasksByParentSession: () => [],
|
|
||||||
} as unknown as NonNullable<Parameters<typeof createAtlasHook>[1]>["backgroundManager"] & {
|
|
||||||
getTasksByParentSession: (sessionID: string) => Array<{ status: string }>
|
getTasksByParentSession: (sessionID: string) => Array<{ status: string }>
|
||||||
},
|
}>({
|
||||||
|
getTasksByParentSession: () => [],
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@@ -515,7 +515,7 @@ describe("atlas background task retry", () => {
|
|||||||
})
|
})
|
||||||
promptAsyncMock.mockImplementationOnce(async () => ({}))
|
promptAsyncMock.mockImplementationOnce(async () => ({}))
|
||||||
|
|
||||||
const hook = createAtlasHook({
|
const hook = createAtlasHook(testCoerce<PluginInput>({
|
||||||
directory: testDir,
|
directory: testDir,
|
||||||
client: {
|
client: {
|
||||||
session: {
|
session: {
|
||||||
@@ -523,13 +523,13 @@ describe("atlas background task retry", () => {
|
|||||||
messages: async () => ({ data: [] }),
|
messages: async () => ({ data: [] }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput, {
|
}), {
|
||||||
directory: testDir,
|
directory: testDir,
|
||||||
backgroundManager: {
|
backgroundManager: testCoerce<NonNullable<Parameters<typeof createAtlasHook>[1]>["backgroundManager"] & {
|
||||||
getTasksByParentSession: () => backgroundRunning ? [{ status: "running" }] : [],
|
|
||||||
} as unknown as NonNullable<Parameters<typeof createAtlasHook>[1]>["backgroundManager"] & {
|
|
||||||
getTasksByParentSession: (sessionID: string) => Array<{ status: string }>
|
getTasksByParentSession: (sessionID: string) => Array<{ status: string }>
|
||||||
},
|
}>({
|
||||||
|
getTasksByParentSession: () => backgroundRunning ? [{ status: "running" }] : [],
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
// when
|
// when
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ describe("injectBoulderContinuation", () => {
|
|||||||
const promptAsyncMock = mock(async (_request: unknown) => undefined)
|
const promptAsyncMock = mock(async (_request: unknown) => undefined)
|
||||||
const messagesMock = mock(async () => ({ data: [] }))
|
const messagesMock = mock(async () => ({ data: [] }))
|
||||||
|
|
||||||
const ctx = {
|
const ctx = testCoerce<PluginInput>({
|
||||||
directory: "/tmp",
|
directory: "/tmp",
|
||||||
client: {
|
client: {
|
||||||
session: {
|
session: {
|
||||||
@@ -28,7 +28,7 @@ describe("injectBoulderContinuation", () => {
|
|||||||
promptAsync: promptAsyncMock,
|
promptAsync: promptAsyncMock,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput
|
})
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result = await injectBoulderContinuation({
|
const result = await injectBoulderContinuation({
|
||||||
@@ -60,7 +60,7 @@ describe("injectBoulderContinuation", () => {
|
|||||||
const messagesMock = mock(async () => ({ data: [] }))
|
const messagesMock = mock(async () => ({ data: [] }))
|
||||||
const sessionState = { promptFailureCount: 2, lastContinuationInjectedAt: 123 }
|
const sessionState = { promptFailureCount: 2, lastContinuationInjectedAt: 123 }
|
||||||
|
|
||||||
const ctx = {
|
const ctx = testCoerce<PluginInput>({
|
||||||
directory: "/tmp",
|
directory: "/tmp",
|
||||||
client: {
|
client: {
|
||||||
session: {
|
session: {
|
||||||
@@ -68,7 +68,7 @@ describe("injectBoulderContinuation", () => {
|
|||||||
promptAsync: promptAsyncMock,
|
promptAsync: promptAsyncMock,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput
|
})
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result = await injectBoulderContinuation({
|
const result = await injectBoulderContinuation({
|
||||||
@@ -78,9 +78,9 @@ describe("injectBoulderContinuation", () => {
|
|||||||
remaining: 1,
|
remaining: 1,
|
||||||
total: 2,
|
total: 2,
|
||||||
agent: "atlas",
|
agent: "atlas",
|
||||||
backgroundManager: {
|
backgroundManager: testCoerce<Parameters<typeof injectBoulderContinuation>[0]["backgroundManager"]>({
|
||||||
getTasksByParentSession: () => [{ status: "running" }],
|
getTasksByParentSession: () => [{ status: "running" }],
|
||||||
} as unknown as Parameters<typeof injectBoulderContinuation>[0]["backgroundManager"],
|
}),
|
||||||
sessionState,
|
sessionState,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ describe("injectBoulderContinuation", () => {
|
|||||||
const messagesMock = mock(async () => ({ data: [] }))
|
const messagesMock = mock(async () => ({ data: [] }))
|
||||||
const sessionState = { promptFailureCount: 1, lastContinuationInjectedAt: 456 }
|
const sessionState = { promptFailureCount: 1, lastContinuationInjectedAt: 456 }
|
||||||
|
|
||||||
const ctx = {
|
const ctx = testCoerce<PluginInput>({
|
||||||
directory: "/tmp",
|
directory: "/tmp",
|
||||||
client: {
|
client: {
|
||||||
session: {
|
session: {
|
||||||
@@ -106,7 +106,7 @@ describe("injectBoulderContinuation", () => {
|
|||||||
promptAsync: promptAsyncMock,
|
promptAsync: promptAsyncMock,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput
|
})
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result = await injectBoulderContinuation({
|
const result = await injectBoulderContinuation({
|
||||||
@@ -116,9 +116,9 @@ describe("injectBoulderContinuation", () => {
|
|||||||
remaining: 1,
|
remaining: 1,
|
||||||
total: 2,
|
total: 2,
|
||||||
agent: "atlas",
|
agent: "atlas",
|
||||||
backgroundManager: {
|
backgroundManager: testCoerce<Parameters<typeof injectBoulderContinuation>[0]["backgroundManager"]>({
|
||||||
getTasksByParentSession: () => [{ status: "pending" }],
|
getTasksByParentSession: () => [{ status: "pending" }],
|
||||||
} as unknown as Parameters<typeof injectBoulderContinuation>[0]["backgroundManager"],
|
}),
|
||||||
sessionState,
|
sessionState,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -134,7 +134,7 @@ describe("injectBoulderContinuation", () => {
|
|||||||
const promptAsyncMock = mock(async (_request: unknown) => undefined)
|
const promptAsyncMock = mock(async (_request: unknown) => undefined)
|
||||||
const messagesMock = mock(async () => ({ data: [] }))
|
const messagesMock = mock(async () => ({ data: [] }))
|
||||||
|
|
||||||
const ctx = {
|
const ctx = testCoerce<PluginInput>({
|
||||||
directory: "/tmp",
|
directory: "/tmp",
|
||||||
client: {
|
client: {
|
||||||
session: {
|
session: {
|
||||||
@@ -142,7 +142,7 @@ describe("injectBoulderContinuation", () => {
|
|||||||
promptAsync: promptAsyncMock,
|
promptAsync: promptAsyncMock,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput
|
})
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result = await injectBoulderContinuation({
|
const result = await injectBoulderContinuation({
|
||||||
@@ -189,7 +189,7 @@ describe("injectBoulderContinuation", () => {
|
|||||||
}],
|
}],
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const ctx = {
|
const ctx = testCoerce<PluginInput>({
|
||||||
directory: "/tmp",
|
directory: "/tmp",
|
||||||
client: {
|
client: {
|
||||||
session: {
|
session: {
|
||||||
@@ -197,7 +197,7 @@ describe("injectBoulderContinuation", () => {
|
|||||||
promptAsync: promptAsyncMock,
|
promptAsync: promptAsyncMock,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput
|
})
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result = await injectBoulderContinuation({
|
const result = await injectBoulderContinuation({
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ describe("atlas hook idle-event complete boulder", () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const hook = createAtlasHook({
|
const hook = createAtlasHook(testCoerce<Parameters<typeof createAtlasHook>[0]>({
|
||||||
directory: testDirectory,
|
directory: testDirectory,
|
||||||
client: {
|
client: {
|
||||||
session: {
|
session: {
|
||||||
@@ -59,7 +59,7 @@ describe("atlas hook idle-event complete boulder", () => {
|
|||||||
promptAsync: async () => ({ data: {} }),
|
promptAsync: async () => ({ data: {} }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as unknown as Parameters<typeof createAtlasHook>[0])
|
}))
|
||||||
|
|
||||||
// when
|
// when
|
||||||
await hook.handler({
|
await hook.handler({
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ describe("atlas hook idle-event session lineage", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createHook(parentSessionIDs?: Record<string, string | undefined>) {
|
function createHook(parentSessionIDs?: Record<string, string | undefined>) {
|
||||||
return createAtlasHook({
|
return createAtlasHook(testCoerce<Parameters<typeof createAtlasHook>[0]>({
|
||||||
directory: testDirectory,
|
directory: testDirectory,
|
||||||
client: {
|
client: {
|
||||||
session: {
|
session: {
|
||||||
@@ -52,7 +52,7 @@ describe("atlas hook idle-event session lineage", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as unknown as Parameters<typeof createAtlasHook>[0])
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ describe("atlas hook idle-event persisted lineage", () => {
|
|||||||
parentSessionIDs?: Record<string, string | undefined>,
|
parentSessionIDs?: Record<string, string | undefined>,
|
||||||
messagesBySession?: Record<string, Array<{ info: { agent: string; providerID: string; modelID: string } }>>,
|
messagesBySession?: Record<string, Array<{ info: { agent: string; providerID: string; modelID: string } }>>,
|
||||||
) {
|
) {
|
||||||
return createAtlasHook({
|
return createAtlasHook(testCoerce<Parameters<typeof createAtlasHook>[0]>({
|
||||||
directory: testDirectory,
|
directory: testDirectory,
|
||||||
client: {
|
client: {
|
||||||
session: {
|
session: {
|
||||||
@@ -79,7 +79,7 @@ describe("atlas hook idle-event persisted lineage", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as unknown as Parameters<typeof createAtlasHook>[0])
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -173,7 +173,7 @@ describe("atlas hook idle-event persisted lineage", () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const hook = createAtlasHook({
|
const hook = createAtlasHook(testCoerce<Parameters<typeof createAtlasHook>[0]>({
|
||||||
directory: testDirectory,
|
directory: testDirectory,
|
||||||
client: {
|
client: {
|
||||||
session: {
|
session: {
|
||||||
@@ -193,7 +193,7 @@ describe("atlas hook idle-event persisted lineage", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as unknown as Parameters<typeof createAtlasHook>[0])
|
}))
|
||||||
|
|
||||||
// when
|
// when
|
||||||
await hook.handler({
|
await hook.handler({
|
||||||
|
|||||||
@@ -76,14 +76,14 @@ describe("handleAtlasSessionIdle completion nudge", () => {
|
|||||||
return { data: {} }
|
return { data: {} }
|
||||||
})
|
})
|
||||||
|
|
||||||
const ctx = {
|
const ctx = testCoerce<PluginInput>({
|
||||||
directory: testDirectory,
|
directory: testDirectory,
|
||||||
client: {
|
client: {
|
||||||
session: {
|
session: {
|
||||||
promptAsync: promptAsyncMock,
|
promptAsync: promptAsyncMock,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput
|
})
|
||||||
|
|
||||||
const sessionStateById = new Map<string, SessionState>()
|
const sessionStateById = new Map<string, SessionState>()
|
||||||
const getState = (sessionId: string): SessionState => {
|
const getState = (sessionId: string): SessionState => {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { resolveRecentPromptContextForSession } from "./recent-model-resolver"
|
|||||||
describe("resolveRecentPromptContextForSession", () => {
|
describe("resolveRecentPromptContextForSession", () => {
|
||||||
test("uses message time.created rather than SDK array order for recent prompt context", async () => {
|
test("uses message time.created rather than SDK array order for recent prompt context", async () => {
|
||||||
// given
|
// given
|
||||||
const ctx = {
|
const ctx = testCoerce<PluginInput>({
|
||||||
client: {
|
client: {
|
||||||
session: {
|
session: {
|
||||||
messages: mock(async () => ({
|
messages: mock(async () => ({
|
||||||
@@ -32,7 +32,7 @@ describe("resolveRecentPromptContextForSession", () => {
|
|||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput
|
})
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result = await resolveRecentPromptContextForSession(ctx, "ses_123")
|
const result = await resolveRecentPromptContextForSession(ctx, "ses_123")
|
||||||
|
|||||||
@@ -80,11 +80,11 @@ describe("createToolExecuteAfterHandler background launch detection", () => {
|
|||||||
|
|
||||||
function createHandler(parentSessionIDs?: Record<string, string | undefined>) {
|
function createHandler(parentSessionIDs?: Record<string, string | undefined>) {
|
||||||
const project = createProject()
|
const project = createProject()
|
||||||
const client = {
|
const client = testCoerce<PluginInput["client"]>({
|
||||||
session: {
|
session: {
|
||||||
get: async (input: SessionGetInput) => createSessionGetResult(parentSessionIDs?.[input.path.id]),
|
get: async (input: SessionGetInput) => createSessionGetResult(parentSessionIDs?.[input.path.id]),
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput["client"]
|
})
|
||||||
|
|
||||||
if (parentSessionIDs) {
|
if (parentSessionIDs) {
|
||||||
spyOn(client.session, "get").mockImplementation((input) => Promise.resolve(
|
spyOn(client.session, "get").mockImplementation((input) => Promise.resolve(
|
||||||
@@ -141,11 +141,11 @@ describe("createToolExecuteAfterHandler background launch detection", () => {
|
|||||||
const childSessionID = "ses_child123"
|
const childSessionID = "ses_child123"
|
||||||
const planPath = join(testDirectory, "background-launch-plan.md")
|
const planPath = join(testDirectory, "background-launch-plan.md")
|
||||||
const project = createProject()
|
const project = createProject()
|
||||||
const client = {
|
const client = testCoerce<PluginInput["client"]>({
|
||||||
session: {
|
session: {
|
||||||
get: async () => createSessionGetResult(undefined),
|
get: async () => createSessionGetResult(undefined),
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput["client"]
|
})
|
||||||
|
|
||||||
spyOn(client.session, "get").mockImplementation((input) => Promise.resolve(
|
spyOn(client.session, "get").mockImplementation((input) => Promise.resolve(
|
||||||
createSessionGetResult(input?.path?.id === childSessionID ? sessionID : undefined),
|
createSessionGetResult(input?.path?.id === childSessionID ? sessionID : undefined),
|
||||||
@@ -215,11 +215,11 @@ describe("createToolExecuteAfterHandler background launch detection", () => {
|
|||||||
const childSessionID = "ses_child_lookup_failure"
|
const childSessionID = "ses_child_lookup_failure"
|
||||||
const planPath = join(testDirectory, "background-launch-plan.md")
|
const planPath = join(testDirectory, "background-launch-plan.md")
|
||||||
const project = createProject()
|
const project = createProject()
|
||||||
const client = {
|
const client = testCoerce<PluginInput["client"]>({
|
||||||
session: {
|
session: {
|
||||||
get: async () => createSessionGetResult(undefined),
|
get: async () => createSessionGetResult(undefined),
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput["client"]
|
})
|
||||||
|
|
||||||
spyOn(client.session, "get").mockImplementation((input) => {
|
spyOn(client.session, "get").mockImplementation((input) => {
|
||||||
if (input?.path?.id === childSessionID) {
|
if (input?.path?.id === childSessionID) {
|
||||||
@@ -288,11 +288,11 @@ describe("createToolExecuteAfterHandler background launch detection", () => {
|
|||||||
const childSessionID = "ses_outside_lineage"
|
const childSessionID = "ses_outside_lineage"
|
||||||
const planPath = join(testDirectory, "background-launch-plan.md")
|
const planPath = join(testDirectory, "background-launch-plan.md")
|
||||||
const project = createProject()
|
const project = createProject()
|
||||||
const client = {
|
const client = testCoerce<PluginInput["client"]>({
|
||||||
session: {
|
session: {
|
||||||
get: async () => createSessionGetResult(undefined),
|
get: async () => createSessionGetResult(undefined),
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput["client"]
|
})
|
||||||
|
|
||||||
spyOn(client.session, "get").mockImplementation((input) => Promise.resolve(
|
spyOn(client.session, "get").mockImplementation((input) => Promise.resolve(
|
||||||
createSessionGetResult(input?.path?.id === childSessionID ? "ses_unrelated_parent" : undefined),
|
createSessionGetResult(input?.path?.id === childSessionID ? "ses_unrelated_parent" : undefined),
|
||||||
@@ -358,11 +358,11 @@ describe("createToolExecuteAfterHandler background launch detection", () => {
|
|||||||
const childSessionID = "ses_unrelated_child"
|
const childSessionID = "ses_unrelated_child"
|
||||||
const planPath = join(testDirectory, "background-launch-plan.md")
|
const planPath = join(testDirectory, "background-launch-plan.md")
|
||||||
const project = createProject()
|
const project = createProject()
|
||||||
const client = {
|
const client = testCoerce<PluginInput["client"]>({
|
||||||
session: {
|
session: {
|
||||||
get: async () => createSessionGetResult(undefined),
|
get: async () => createSessionGetResult(undefined),
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput["client"]
|
})
|
||||||
|
|
||||||
spyOn(client.session, "get").mockImplementation((input) => Promise.resolve(
|
spyOn(client.session, "get").mockImplementation((input) => Promise.resolve(
|
||||||
createSessionGetResult(input?.path?.id === childSessionID ? sessionID : undefined),
|
createSessionGetResult(input?.path?.id === childSessionID ? sessionID : undefined),
|
||||||
@@ -431,11 +431,11 @@ describe("createToolExecuteAfterHandler background launch detection", () => {
|
|||||||
const planPathA = join(testDirectory, "background-launch-work-a.md")
|
const planPathA = join(testDirectory, "background-launch-work-a.md")
|
||||||
const planPathB = join(testDirectory, "background-launch-work-b.md")
|
const planPathB = join(testDirectory, "background-launch-work-b.md")
|
||||||
const project = createProject()
|
const project = createProject()
|
||||||
const client = {
|
const client = testCoerce<PluginInput["client"]>({
|
||||||
session: {
|
session: {
|
||||||
get: async () => createSessionGetResult(undefined),
|
get: async () => createSessionGetResult(undefined),
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput["client"]
|
})
|
||||||
|
|
||||||
spyOn(client.session, "get").mockImplementation((input) => Promise.resolve(
|
spyOn(client.session, "get").mockImplementation((input) => Promise.resolve(
|
||||||
createSessionGetResult(input?.path?.id === childSessionID ? parentSessionID : undefined),
|
createSessionGetResult(input?.path?.id === childSessionID ? parentSessionID : undefined),
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ mock.module("../constants", () => ({
|
|||||||
const current = mockState.candidates
|
const current = mockState.candidates
|
||||||
// Forward array methods/properties to the mutable candidates list
|
// Forward array methods/properties to the mutable candidates list
|
||||||
// so getCachedVersion's `for (... of ...)` sees fresh data per test.
|
// so getCachedVersion's `for (... of ...)` sees fresh data per test.
|
||||||
const value = (current as unknown as Record<PropertyKey, unknown>)[prop]
|
const value = (testCoerce<Record<PropertyKey, unknown>>(current))[prop]
|
||||||
if (typeof value === "function") {
|
if (typeof value === "function") {
|
||||||
return (value as (...args: unknown[]) => unknown).bind(current)
|
return (value as (...args: unknown[]) => unknown).bind(current)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ describe("category-skill-reminder hook", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
function createMockPluginInput() {
|
function createMockPluginInput() {
|
||||||
return {
|
return testCoerce({
|
||||||
client: {
|
client: {
|
||||||
tui: {
|
tui: {
|
||||||
showToast: async () => {},
|
showToast: async () => {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as any
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function createHook(availableSkills: AvailableSkill[] = []) {
|
function createHook(availableSkills: AvailableSkill[] = []) {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ describe("executeHttpHook TLS security", () => {
|
|||||||
let logCalls: Array<{ message: string; data?: unknown }>
|
let logCalls: Array<{ message: string; data?: unknown }>
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
globalThis.fetch = mockFetch as unknown as typeof fetch
|
globalThis.fetch = testCoerce<typeof fetch>(mockFetch)
|
||||||
mockFetch.mockReset()
|
mockFetch.mockReset()
|
||||||
mockFetch.mockImplementation(() =>
|
mockFetch.mockImplementation(() =>
|
||||||
Promise.resolve(new Response(JSON.stringify({}), { status: 200 }))
|
Promise.resolve(new Response(JSON.stringify({}), { status: 200 }))
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const originalFetch = globalThis.fetch
|
|||||||
|
|
||||||
describe("executeHttpHook", () => {
|
describe("executeHttpHook", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
globalThis.fetch = mockFetch as unknown as typeof fetch
|
globalThis.fetch = testCoerce<typeof fetch>(mockFetch)
|
||||||
mockFetch.mockReset()
|
mockFetch.mockReset()
|
||||||
mockFetch.mockImplementation(() =>
|
mockFetch.mockImplementation(() =>
|
||||||
Promise.resolve(new Response(JSON.stringify({}), { status: 200 }))
|
Promise.resolve(new Response(JSON.stringify({}), { status: 200 }))
|
||||||
@@ -33,7 +33,7 @@ describe("executeHttpHook", () => {
|
|||||||
await executeHttpHook(hook, stdinData)
|
await executeHttpHook(hook, stdinData)
|
||||||
|
|
||||||
expect(mockFetch).toHaveBeenCalledTimes(1)
|
expect(mockFetch).toHaveBeenCalledTimes(1)
|
||||||
const [url, options] = mockFetch.mock.calls[0] as unknown as [string, RequestInit]
|
const [url, options] = testCoerce<[string, RequestInit]>(mockFetch.mock.calls[0])
|
||||||
expect(url).toBe("http://localhost:8080/hooks/pre-tool-use")
|
expect(url).toBe("http://localhost:8080/hooks/pre-tool-use")
|
||||||
expect(options.method).toBe("POST")
|
expect(options.method).toBe("POST")
|
||||||
expect(options.body).toBe(stdinData)
|
expect(options.body).toBe(stdinData)
|
||||||
@@ -44,7 +44,7 @@ describe("executeHttpHook", () => {
|
|||||||
|
|
||||||
await executeHttpHook(hook, stdinData)
|
await executeHttpHook(hook, stdinData)
|
||||||
|
|
||||||
const [, options] = mockFetch.mock.calls[0] as unknown as [string, RequestInit]
|
const [, options] = testCoerce<[string, RequestInit]>(mockFetch.mock.calls[0])
|
||||||
const headers = options.headers as Record<string, string>
|
const headers = options.headers as Record<string, string>
|
||||||
expect(headers["Content-Type"]).toBe("application/json")
|
expect(headers["Content-Type"]).toBe("application/json")
|
||||||
})
|
})
|
||||||
@@ -72,7 +72,7 @@ describe("executeHttpHook", () => {
|
|||||||
|
|
||||||
await executeHttpHook(hook, "{}")
|
await executeHttpHook(hook, "{}")
|
||||||
|
|
||||||
const [, options] = mockFetch.mock.calls[0] as unknown as [string, RequestInit]
|
const [, options] = testCoerce<[string, RequestInit]>(mockFetch.mock.calls[0])
|
||||||
const headers = options.headers as Record<string, string>
|
const headers = options.headers as Record<string, string>
|
||||||
expect(headers["Authorization"]).toBe("Bearer secret-123")
|
expect(headers["Authorization"]).toBe("Bearer secret-123")
|
||||||
})
|
})
|
||||||
@@ -88,7 +88,7 @@ describe("executeHttpHook", () => {
|
|||||||
|
|
||||||
await executeHttpHook(hook, "{}")
|
await executeHttpHook(hook, "{}")
|
||||||
|
|
||||||
const [, options] = mockFetch.mock.calls[0] as unknown as [string, RequestInit]
|
const [, options] = testCoerce<[string, RequestInit]>(mockFetch.mock.calls[0])
|
||||||
const headers = options.headers as Record<string, string>
|
const headers = options.headers as Record<string, string>
|
||||||
expect(headers["Authorization"]).toBe("Bearer secret-123")
|
expect(headers["Authorization"]).toBe("Bearer secret-123")
|
||||||
})
|
})
|
||||||
@@ -104,7 +104,7 @@ describe("executeHttpHook", () => {
|
|||||||
|
|
||||||
await executeHttpHook(hook, "{}")
|
await executeHttpHook(hook, "{}")
|
||||||
|
|
||||||
const [, options] = mockFetch.mock.calls[0] as unknown as [string, RequestInit]
|
const [, options] = testCoerce<[string, RequestInit]>(mockFetch.mock.calls[0])
|
||||||
const headers = options.headers as Record<string, string>
|
const headers = options.headers as Record<string, string>
|
||||||
expect(headers["Authorization"]).toBe("Bearer ")
|
expect(headers["Authorization"]).toBe("Bearer ")
|
||||||
})
|
})
|
||||||
@@ -121,7 +121,7 @@ describe("executeHttpHook", () => {
|
|||||||
|
|
||||||
await executeHttpHook(hook, "{}")
|
await executeHttpHook(hook, "{}")
|
||||||
|
|
||||||
const [, options] = mockFetch.mock.calls[0] as unknown as [string, RequestInit]
|
const [, options] = testCoerce<[string, RequestInit]>(mockFetch.mock.calls[0])
|
||||||
expect(options.signal).toBeDefined()
|
expect(options.signal).toBeDefined()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -33,11 +33,11 @@ describe("tool-input-cache", () => {
|
|||||||
|
|
||||||
test("#given cleanup timer started #when stop cleanup runs #then interval is cleared and cache is emptied", async () => {
|
test("#given cleanup timer started #when stop cleanup runs #then interval is cleared and cache is emptied", async () => {
|
||||||
//#given
|
//#given
|
||||||
const intervalHandle = { unref: mock(() => {}) } as unknown as ReturnType<typeof setInterval>
|
const intervalHandle = testCoerce<ReturnType<typeof setInterval>>({ unref: mock(() => {}) })
|
||||||
const setIntervalMock = mock(() => intervalHandle)
|
const setIntervalMock = mock(() => intervalHandle)
|
||||||
const clearIntervalMock = mock(() => {})
|
const clearIntervalMock = mock(() => {})
|
||||||
globalThis.setInterval = setIntervalMock as unknown as typeof setInterval
|
globalThis.setInterval = testCoerce<typeof setInterval>(setIntervalMock)
|
||||||
globalThis.clearInterval = clearIntervalMock as unknown as typeof clearInterval
|
globalThis.clearInterval = testCoerce<typeof clearInterval>(clearIntervalMock)
|
||||||
|
|
||||||
const modulePath = new URL("./tool-input-cache.ts", import.meta.url).pathname
|
const modulePath = new URL("./tool-input-cache.ts", import.meta.url).pathname
|
||||||
const cacheModule = await import(`${modulePath}?stop-clear`)
|
const cacheModule = await import(`${modulePath}?stop-clear`)
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ done
|
|||||||
const originalSetTimeout = globalThis.setTimeout
|
const originalSetTimeout = globalThis.setTimeout
|
||||||
globalThis.setTimeout = ((fn: (...args: unknown[]) => void, _ms?: number) => {
|
globalThis.setTimeout = ((fn: (...args: unknown[]) => void, _ms?: number) => {
|
||||||
fn()
|
fn()
|
||||||
return 0 as unknown as ReturnType<typeof setTimeout>
|
return testCoerce<ReturnType<typeof setTimeout>>(0)
|
||||||
}) as typeof setTimeout
|
}) as typeof setTimeout
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -102,7 +102,7 @@ done
|
|||||||
const originalSetTimeout = globalThis.setTimeout
|
const originalSetTimeout = globalThis.setTimeout
|
||||||
globalThis.setTimeout = ((fn: (...args: unknown[]) => void, _ms?: number) => {
|
globalThis.setTimeout = ((fn: (...args: unknown[]) => void, _ms?: number) => {
|
||||||
fn()
|
fn()
|
||||||
return 0 as unknown as ReturnType<typeof setTimeout>
|
return testCoerce<ReturnType<typeof setTimeout>>(0)
|
||||||
}) as typeof setTimeout
|
}) as typeof setTimeout
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -7,18 +7,18 @@ describe("pending-calls cleanup interval", () => {
|
|||||||
const setIntervalCalls: number[] = []
|
const setIntervalCalls: number[] = []
|
||||||
let unrefCalled = 0
|
let unrefCalled = 0
|
||||||
|
|
||||||
globalThis.setInterval = ((
|
globalThis.setInterval = testCoerce<typeof setInterval>(((
|
||||||
_handler: TimerHandler,
|
_handler: TimerHandler,
|
||||||
timeout?: number,
|
timeout?: number,
|
||||||
..._args: any[]
|
..._args: unknown[]
|
||||||
) => {
|
) => {
|
||||||
setIntervalCalls.push(timeout as number)
|
setIntervalCalls.push(timeout as number)
|
||||||
return {
|
return testCoerce<ReturnType<typeof setInterval>>({
|
||||||
unref: () => {
|
unref: () => {
|
||||||
unrefCalled += 1
|
unrefCalled += 1
|
||||||
},
|
},
|
||||||
} as unknown as ReturnType<typeof setInterval>
|
})
|
||||||
}) as unknown as typeof setInterval
|
}))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const modulePath = new URL("./pending-calls.ts", import.meta.url).pathname
|
const modulePath = new URL("./pending-calls.ts", import.meta.url).pathname
|
||||||
@@ -43,20 +43,20 @@ describe("pending-calls cleanup interval", () => {
|
|||||||
let intervalHandle: ReturnType<typeof setInterval> | undefined
|
let intervalHandle: ReturnType<typeof setInterval> | undefined
|
||||||
let clearCalls = 0
|
let clearCalls = 0
|
||||||
|
|
||||||
globalThis.setInterval = ((
|
globalThis.setInterval = testCoerce<typeof setInterval>(((
|
||||||
_handler: TimerHandler,
|
_handler: TimerHandler,
|
||||||
_timeout?: number,
|
_timeout?: number,
|
||||||
..._args: any[]
|
..._args: unknown[]
|
||||||
) => {
|
) => {
|
||||||
intervalHandle = { unref: () => {} } as unknown as ReturnType<typeof setInterval>
|
intervalHandle = testCoerce<ReturnType<typeof setInterval>>({ unref: () => {} })
|
||||||
return intervalHandle
|
return intervalHandle
|
||||||
}) as unknown as typeof setInterval
|
}))
|
||||||
|
|
||||||
globalThis.clearInterval = ((handle?: ReturnType<typeof setInterval>) => {
|
globalThis.clearInterval = testCoerce<typeof clearInterval>(((handle?: ReturnType<typeof setInterval>) => {
|
||||||
if (handle === intervalHandle) {
|
if (handle === intervalHandle) {
|
||||||
clearCalls += 1
|
clearCalls += 1
|
||||||
}
|
}
|
||||||
}) as unknown as typeof clearInterval
|
}))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const modulePath = new URL("./pending-calls.ts", import.meta.url).pathname
|
const modulePath = new URL("./pending-calls.ts", import.meta.url).pathname
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ describe("createEditErrorRecoveryHook", () => {
|
|||||||
let hook: ReturnType<typeof createEditErrorRecoveryHook>
|
let hook: ReturnType<typeof createEditErrorRecoveryHook>
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
hook = createEditErrorRecoveryHook({} as any)
|
hook = createEditErrorRecoveryHook(testCoerce({}))
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("tool.execute.after", () => {
|
describe("tool.execute.after", () => {
|
||||||
@@ -108,7 +108,7 @@ describe("createEditErrorRecoveryHook", () => {
|
|||||||
const input = createInput("Edit")
|
const input = createInput("Edit")
|
||||||
const output = {
|
const output = {
|
||||||
title: "Edit",
|
title: "Edit",
|
||||||
output: undefined as unknown as string,
|
output: testCoerce<string>(undefined),
|
||||||
metadata: {},
|
metadata: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ type StartLoopCall = {
|
|||||||
type CancelLoopCall = { sessionID: string }
|
type CancelLoopCall = { sessionID: string }
|
||||||
|
|
||||||
function createMockPluginInput() {
|
function createMockPluginInput() {
|
||||||
return {
|
return testCoerce({
|
||||||
client: {
|
client: {
|
||||||
tui: {
|
tui: {
|
||||||
showToast: async () => {},
|
showToast: async () => {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as any
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMockRalphLoop(startLoopCalls: StartLoopCall[], cancelLoopCalls: CancelLoopCall[] = []) {
|
function createMockRalphLoop(startLoopCalls: StartLoopCall[], cancelLoopCalls: CancelLoopCall[] = []) {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ describe("keyword-detector hyperplan-ultrawork combo", () => {
|
|||||||
|
|
||||||
function createMockPluginInput(options: { toastCalls?: string[] } = {}) {
|
function createMockPluginInput(options: { toastCalls?: string[] } = {}) {
|
||||||
const toastCalls = options.toastCalls ?? []
|
const toastCalls = options.toastCalls ?? []
|
||||||
return {
|
return testCoerce<PluginInput>({
|
||||||
client: {
|
client: {
|
||||||
tui: {
|
tui: {
|
||||||
showToast: async (opts: { body: { title: string } }) => {
|
showToast: async (opts: { body: { title: string } }) => {
|
||||||
@@ -30,7 +30,7 @@ describe("keyword-detector hyperplan-ultrawork combo", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
test("should inject combo message when user types 'hpp ulw' (forward order)", async () => {
|
test("should inject combo message when user types 'hpp ulw' (forward order)", async () => {
|
||||||
|
|||||||
@@ -881,13 +881,13 @@ describe("keyword-detector team mode", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
function createMockPluginInput() {
|
function createMockPluginInput() {
|
||||||
return {
|
return testCoerce<PluginInput>({
|
||||||
client: {
|
client: {
|
||||||
tui: {
|
tui: {
|
||||||
showToast: async () => {},
|
showToast: async () => {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
test("should inject team-mode message when user types 'team mode'", async () => {
|
test("should inject team-mode message when user types 'team mode'", async () => {
|
||||||
@@ -1063,7 +1063,7 @@ describe("keyword-detector disabled_keywords config", () => {
|
|||||||
|
|
||||||
function createMockPluginInput(options: { toastCalls?: string[] } = {}) {
|
function createMockPluginInput(options: { toastCalls?: string[] } = {}) {
|
||||||
const toastCalls = options.toastCalls ?? []
|
const toastCalls = options.toastCalls ?? []
|
||||||
return {
|
return testCoerce<PluginInput>({
|
||||||
client: {
|
client: {
|
||||||
tui: {
|
tui: {
|
||||||
showToast: async (opts: { body: { title: string } }) => {
|
showToast: async (opts: { body: { title: string } }) => {
|
||||||
@@ -1071,7 +1071,7 @@ describe("keyword-detector disabled_keywords config", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
test("should NOT inject search-mode when disabled_keywords includes 'search'", async () => {
|
test("should NOT inject search-mode when disabled_keywords includes 'search'", async () => {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ type StartLoopCall = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createMockPluginInput(toastCalls: string[] = []) {
|
function createMockPluginInput(toastCalls: string[] = []) {
|
||||||
return {
|
return testCoerce<PluginInput>({
|
||||||
client: {
|
client: {
|
||||||
tui: {
|
tui: {
|
||||||
showToast: async (opts: { body: { title: string } }) => {
|
showToast: async (opts: { body: { title: string } }) => {
|
||||||
@@ -19,7 +19,7 @@ function createMockPluginInput(toastCalls: string[] = []) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMockRalphLoop(startLoopCalls: StartLoopCall[]) {
|
function createMockRalphLoop(startLoopCalls: StartLoopCall[]) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { createKeywordDetectorHook } from "./index"
|
|||||||
import { _resetForTesting, setMainSession } from "../../features/claude-code-session-state"
|
import { _resetForTesting, setMainSession } from "../../features/claude-code-session-state"
|
||||||
|
|
||||||
function createMockPluginInput(toastMessages: string[]) {
|
function createMockPluginInput(toastMessages: string[]) {
|
||||||
return {
|
return testCoerce({
|
||||||
client: {
|
client: {
|
||||||
tui: {
|
tui: {
|
||||||
showToast: async (opts: { body: { message: string } }) => {
|
showToast: async (opts: { body: { message: string } }) => {
|
||||||
@@ -11,7 +11,7 @@ function createMockPluginInput(toastMessages: string[]) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as any
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("keyword-detector ultrawork runtime variant gating", () => {
|
describe("keyword-detector ultrawork runtime variant gating", () => {
|
||||||
|
|||||||
@@ -86,12 +86,12 @@ describe("model fallback hook", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test("applies pending fallback on chat.message by overriding model", async () => {
|
test("applies pending fallback on chat.message by overriding model", async () => {
|
||||||
const hook = modelFallback as unknown as {
|
const hook = testCoerce<{
|
||||||
"chat.message"?: (
|
"chat.message"?: (
|
||||||
input: { sessionID: string },
|
input: { sessionID: string },
|
||||||
output: { message: Record<string, unknown>; parts: Array<{ type: string; text?: string }> },
|
output: { message: Record<string, unknown>; parts: Array<{ type: string; text?: string }> },
|
||||||
) => Promise<void>
|
) => Promise<void>
|
||||||
}
|
}>(modelFallback)
|
||||||
|
|
||||||
const set = setPendingModelFallback(
|
const set = setPendingModelFallback(
|
||||||
modelFallback,
|
modelFallback,
|
||||||
@@ -122,12 +122,12 @@ describe("model fallback hook", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test("preserves fallback progression across repeated session.error retries", async () => {
|
test("preserves fallback progression across repeated session.error retries", async () => {
|
||||||
const hook = modelFallback as unknown as {
|
const hook = testCoerce<{
|
||||||
"chat.message"?: (
|
"chat.message"?: (
|
||||||
input: { sessionID: string },
|
input: { sessionID: string },
|
||||||
output: { message: Record<string, unknown>; parts: Array<{ type: string; text?: string }> },
|
output: { message: Record<string, unknown>; parts: Array<{ type: string; text?: string }> },
|
||||||
) => Promise<void>
|
) => Promise<void>
|
||||||
}
|
}>(modelFallback)
|
||||||
const sessionID = "ses_model_fallback_main"
|
const sessionID = "ses_model_fallback_main"
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
@@ -212,12 +212,12 @@ describe("model fallback hook", () => {
|
|||||||
const sessionID = "ses_model_fallback_noop_skip"
|
const sessionID = "ses_model_fallback_noop_skip"
|
||||||
clearPendingModelFallback(modelFallback, sessionID)
|
clearPendingModelFallback(modelFallback, sessionID)
|
||||||
|
|
||||||
const hook = modelFallback as unknown as {
|
const hook = testCoerce<{
|
||||||
"chat.message"?: (
|
"chat.message"?: (
|
||||||
input: { sessionID: string },
|
input: { sessionID: string },
|
||||||
output: { message: Record<string, unknown>; parts: Array<{ type: string; text?: string }> },
|
output: { message: Record<string, unknown>; parts: Array<{ type: string; text?: string }> },
|
||||||
) => Promise<void>
|
) => Promise<void>
|
||||||
}
|
}>(modelFallback)
|
||||||
|
|
||||||
setSessionFallbackChain(modelFallback, sessionID, [
|
setSessionFallbackChain(modelFallback, sessionID, [
|
||||||
{ providers: ["anthropic"], model: "claude-opus-4-7" },
|
{ providers: ["anthropic"], model: "claude-opus-4-7" },
|
||||||
@@ -254,12 +254,12 @@ describe("model fallback hook", () => {
|
|||||||
const sessionID = "ses_model_fallback_noop_variant_skip"
|
const sessionID = "ses_model_fallback_noop_variant_skip"
|
||||||
clearPendingModelFallback(modelFallback, sessionID)
|
clearPendingModelFallback(modelFallback, sessionID)
|
||||||
|
|
||||||
const hook = modelFallback as unknown as {
|
const hook = testCoerce<{
|
||||||
"chat.message"?: (
|
"chat.message"?: (
|
||||||
input: { sessionID: string },
|
input: { sessionID: string },
|
||||||
output: { message: Record<string, unknown>; parts: Array<{ type: string; text?: string }> },
|
output: { message: Record<string, unknown>; parts: Array<{ type: string; text?: string }> },
|
||||||
) => Promise<void>
|
) => Promise<void>
|
||||||
}
|
}>(modelFallback)
|
||||||
|
|
||||||
setSessionFallbackChain(modelFallback, sessionID, [
|
setSessionFallbackChain(modelFallback, sessionID, [
|
||||||
{ providers: ["quotio"], model: "claude-opus-4-7", variant: "max" },
|
{ providers: ["quotio"], model: "claude-opus-4-7", variant: "max" },
|
||||||
@@ -299,12 +299,12 @@ describe("model fallback hook", () => {
|
|||||||
clearPendingModelFallback(modelFallback, sessionID)
|
clearPendingModelFallback(modelFallback, sessionID)
|
||||||
readConnectedProvidersCacheMock.mockReturnValue(["provider-x"])
|
readConnectedProvidersCacheMock.mockReturnValue(["provider-x"])
|
||||||
|
|
||||||
const hook = modelFallback as unknown as {
|
const hook = testCoerce<{
|
||||||
"chat.message"?: (
|
"chat.message"?: (
|
||||||
input: { sessionID: string },
|
input: { sessionID: string },
|
||||||
output: { message: Record<string, unknown>; parts: Array<{ type: string; text?: string }> },
|
output: { message: Record<string, unknown>; parts: Array<{ type: string; text?: string }> },
|
||||||
) => Promise<void>
|
) => Promise<void>
|
||||||
}
|
}>(modelFallback)
|
||||||
|
|
||||||
setSessionFallbackChain(modelFallback, sessionID, [
|
setSessionFallbackChain(modelFallback, sessionID, [
|
||||||
{ providers: ["provider-y"], model: "fallback-model" },
|
{ providers: ["provider-y"], model: "fallback-model" },
|
||||||
@@ -355,16 +355,16 @@ describe("model fallback hook", () => {
|
|||||||
|
|
||||||
test("shows toast when fallback is applied", async () => {
|
test("shows toast when fallback is applied", async () => {
|
||||||
const toastCalls: Array<{ title: string; message: string }> = []
|
const toastCalls: Array<{ title: string; message: string }> = []
|
||||||
const hook = createModelFallbackHook({
|
const hook = testCoerce<{
|
||||||
toast: async ({ title, message }) => {
|
|
||||||
toastCalls.push({ title, message })
|
|
||||||
},
|
|
||||||
}) as unknown as {
|
|
||||||
"chat.message"?: (
|
"chat.message"?: (
|
||||||
input: { sessionID: string },
|
input: { sessionID: string },
|
||||||
output: { message: Record<string, unknown>; parts: Array<{ type: string; text?: string }> },
|
output: { message: Record<string, unknown>; parts: Array<{ type: string; text?: string }> },
|
||||||
) => Promise<void>
|
) => Promise<void>
|
||||||
}
|
}>(createModelFallbackHook({
|
||||||
|
toast: async ({ title, message }) => {
|
||||||
|
toastCalls.push({ title, message })
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
const set = setPendingModelFallback(
|
const set = setPendingModelFallback(
|
||||||
hook,
|
hook,
|
||||||
@@ -393,12 +393,12 @@ describe("model fallback hook", () => {
|
|||||||
const sessionID = "ses_model_fallback_ghcp"
|
const sessionID = "ses_model_fallback_ghcp"
|
||||||
clearPendingModelFallback(modelFallback, sessionID)
|
clearPendingModelFallback(modelFallback, sessionID)
|
||||||
|
|
||||||
const hook = modelFallback as unknown as {
|
const hook = testCoerce<{
|
||||||
"chat.message"?: (
|
"chat.message"?: (
|
||||||
input: { sessionID: string },
|
input: { sessionID: string },
|
||||||
output: { message: Record<string, unknown>; parts: Array<{ type: string; text?: string }> },
|
output: { message: Record<string, unknown>; parts: Array<{ type: string; text?: string }> },
|
||||||
) => Promise<void>
|
) => Promise<void>
|
||||||
}
|
}>(modelFallback)
|
||||||
|
|
||||||
setSessionFallbackChain(modelFallback, sessionID, [
|
setSessionFallbackChain(modelFallback, sessionID, [
|
||||||
{ providers: ["github-copilot"], model: "claude-sonnet-4-6" },
|
{ providers: ["github-copilot"], model: "claude-sonnet-4-6" },
|
||||||
@@ -434,12 +434,12 @@ describe("model fallback hook", () => {
|
|||||||
const sessionID = "ses_model_fallback_google"
|
const sessionID = "ses_model_fallback_google"
|
||||||
clearPendingModelFallback(modelFallback, sessionID)
|
clearPendingModelFallback(modelFallback, sessionID)
|
||||||
|
|
||||||
const hook = modelFallback as unknown as {
|
const hook = testCoerce<{
|
||||||
"chat.message"?: (
|
"chat.message"?: (
|
||||||
input: { sessionID: string },
|
input: { sessionID: string },
|
||||||
output: { message: Record<string, unknown>; parts: Array<{ type: string; text?: string }> },
|
output: { message: Record<string, unknown>; parts: Array<{ type: string; text?: string }> },
|
||||||
) => Promise<void>
|
) => Promise<void>
|
||||||
}
|
}>(modelFallback)
|
||||||
|
|
||||||
setSessionFallbackChain(modelFallback, sessionID, [
|
setSessionFallbackChain(modelFallback, sessionID, [
|
||||||
{ providers: ["google"], model: "gemini-3.1-pro-preview" },
|
{ providers: ["google"], model: "gemini-3.1-pro-preview" },
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ describe("no-hephaestus-non-gpt hook", () => {
|
|||||||
test("shows toast on every chat.message when hephaestus uses non-gpt model", async () => {
|
test("shows toast on every chat.message when hephaestus uses non-gpt model", async () => {
|
||||||
// given - hephaestus with claude model
|
// given - hephaestus with claude model
|
||||||
const showToast = spyOn({ fn: async (_input: unknown) => ({}) }, "fn")
|
const showToast = spyOn({ fn: async (_input: unknown) => ({}) }, "fn")
|
||||||
const hook = createNoHephaestusNonGptHook({
|
const hook = createNoHephaestusNonGptHook(testCoerce({
|
||||||
client: { tui: { showToast } },
|
client: { tui: { showToast } },
|
||||||
} as any)
|
}))
|
||||||
|
|
||||||
const output1 = createOutput()
|
const output1 = createOutput()
|
||||||
const output2 = createOutput()
|
const output2 = createOutput()
|
||||||
@@ -54,9 +54,9 @@ describe("no-hephaestus-non-gpt hook", () => {
|
|||||||
test("shows warning and does not switch agent when allow_non_gpt_model is enabled", async () => {
|
test("shows warning and does not switch agent when allow_non_gpt_model is enabled", async () => {
|
||||||
// given - hephaestus with claude model and opt-out enabled
|
// given - hephaestus with claude model and opt-out enabled
|
||||||
const showToast = spyOn({ fn: async (_input: unknown) => ({}) }, "fn")
|
const showToast = spyOn({ fn: async (_input: unknown) => ({}) }, "fn")
|
||||||
const hook = createNoHephaestusNonGptHook({
|
const hook = createNoHephaestusNonGptHook(testCoerce({
|
||||||
client: { tui: { showToast } },
|
client: { tui: { showToast } },
|
||||||
} as any, {
|
}), {
|
||||||
allowNonGptModel: true,
|
allowNonGptModel: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -83,9 +83,9 @@ describe("no-hephaestus-non-gpt hook", () => {
|
|||||||
test("does not show toast when hephaestus uses gpt model", async () => {
|
test("does not show toast when hephaestus uses gpt model", async () => {
|
||||||
// given - hephaestus with gpt model
|
// given - hephaestus with gpt model
|
||||||
const showToast = spyOn({ fn: async (_input: unknown) => ({}) }, "fn")
|
const showToast = spyOn({ fn: async (_input: unknown) => ({}) }, "fn")
|
||||||
const hook = createNoHephaestusNonGptHook({
|
const hook = createNoHephaestusNonGptHook(testCoerce({
|
||||||
client: { tui: { showToast } },
|
client: { tui: { showToast } },
|
||||||
} as any)
|
}))
|
||||||
|
|
||||||
const output = createOutput()
|
const output = createOutput()
|
||||||
|
|
||||||
@@ -104,9 +104,9 @@ describe("no-hephaestus-non-gpt hook", () => {
|
|||||||
test("does not show toast for non-hephaestus agent", async () => {
|
test("does not show toast for non-hephaestus agent", async () => {
|
||||||
// given - sisyphus with claude model (non-gpt)
|
// given - sisyphus with claude model (non-gpt)
|
||||||
const showToast = spyOn({ fn: async (_input: unknown) => ({}) }, "fn")
|
const showToast = spyOn({ fn: async (_input: unknown) => ({}) }, "fn")
|
||||||
const hook = createNoHephaestusNonGptHook({
|
const hook = createNoHephaestusNonGptHook(testCoerce({
|
||||||
client: { tui: { showToast } },
|
client: { tui: { showToast } },
|
||||||
} as any)
|
}))
|
||||||
|
|
||||||
const output = createOutput()
|
const output = createOutput()
|
||||||
|
|
||||||
@@ -127,9 +127,9 @@ describe("no-hephaestus-non-gpt hook", () => {
|
|||||||
_resetForTesting()
|
_resetForTesting()
|
||||||
updateSessionAgent("ses_4", HEPHAESTUS_DISPLAY)
|
updateSessionAgent("ses_4", HEPHAESTUS_DISPLAY)
|
||||||
const showToast = spyOn({ fn: async (_input: unknown) => ({}) }, "fn")
|
const showToast = spyOn({ fn: async (_input: unknown) => ({}) }, "fn")
|
||||||
const hook = createNoHephaestusNonGptHook({
|
const hook = createNoHephaestusNonGptHook(testCoerce({
|
||||||
client: { tui: { showToast } },
|
client: { tui: { showToast } },
|
||||||
} as any)
|
}))
|
||||||
|
|
||||||
const output = createOutput()
|
const output = createOutput()
|
||||||
|
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ function createOutput(): HookOutput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createHookContext(showToast: (input: unknown) => Promise<unknown>): PluginInput {
|
function createHookContext(showToast: (input: unknown) => Promise<unknown>): PluginInput {
|
||||||
return {
|
return testCoerce<PluginInput>({
|
||||||
client: { tui: { showToast } },
|
client: { tui: { showToast } },
|
||||||
} as unknown as PluginInput
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("no-sisyphus-gpt hook", () => {
|
describe("no-sisyphus-gpt hook", () => {
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ function truncateQuestionLabels(args: AskUserQuestionArgs): AskUserQuestionArgs
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasQuestions(args: Record<string, unknown>): args is Record<string, unknown> & AskUserQuestionArgs {
|
||||||
|
return Array.isArray(args.questions);
|
||||||
|
}
|
||||||
|
|
||||||
export function createQuestionLabelTruncatorHook() {
|
export function createQuestionLabelTruncatorHook() {
|
||||||
return {
|
return {
|
||||||
"tool.execute.before": async (
|
"tool.execute.before": async (
|
||||||
@@ -50,10 +54,8 @@ export function createQuestionLabelTruncatorHook() {
|
|||||||
const toolName = input.tool?.toLowerCase();
|
const toolName = input.tool?.toLowerCase();
|
||||||
|
|
||||||
if (toolName === "askuserquestion" || toolName === "ask_user_question") {
|
if (toolName === "askuserquestion" || toolName === "ask_user_question") {
|
||||||
const args = output.args as unknown as AskUserQuestionArgs | undefined;
|
if (hasQuestions(output.args)) {
|
||||||
|
const truncatedArgs = truncateQuestionLabels(output.args);
|
||||||
if (args?.questions) {
|
|
||||||
const truncatedArgs = truncateQuestionLabels(args);
|
|
||||||
Object.assign(output.args, truncatedArgs);
|
Object.assign(output.args, truncatedArgs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ describe("createQuestionLabelTruncatorHook", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// when
|
// when
|
||||||
await hook["tool.execute.before"]?.(input as any, output as any);
|
await hook["tool.execute.before"]?.(testCoerce(input), testCoerce(output));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
const truncatedLabel = (output.args as any).questions[0].options[0].label;
|
const truncatedLabel = (testCoerce(output.args)).questions[0].options[0].label;
|
||||||
expect(truncatedLabel.length).toBeLessThanOrEqual(30);
|
expect(truncatedLabel.length).toBeLessThanOrEqual(30);
|
||||||
expect(truncatedLabel).toBe("This is a very long label t...");
|
expect(truncatedLabel).toBe("This is a very long label t...");
|
||||||
expect(truncatedLabel.endsWith("...")).toBe(true);
|
expect(truncatedLabel.endsWith("...")).toBe(true);
|
||||||
@@ -50,10 +50,10 @@ describe("createQuestionLabelTruncatorHook", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// when
|
// when
|
||||||
await hook["tool.execute.before"]?.(input as any, output as any);
|
await hook["tool.execute.before"]?.(testCoerce(input), testCoerce(output));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
const resultLabel = (output.args as any).questions[0].options[0].label;
|
const resultLabel = (testCoerce(output.args)).questions[0].options[0].label;
|
||||||
expect(resultLabel).toBe(shortLabel);
|
expect(resultLabel).toBe(shortLabel);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -74,10 +74,10 @@ describe("createQuestionLabelTruncatorHook", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// when
|
// when
|
||||||
await hook["tool.execute.before"]?.(input as any, output as any);
|
await hook["tool.execute.before"]?.(testCoerce(input), testCoerce(output));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
const resultLabel = (output.args as any).questions[0].options[0].label;
|
const resultLabel = (testCoerce(output.args)).questions[0].options[0].label;
|
||||||
expect(resultLabel).toBe(exactLabel);
|
expect(resultLabel).toBe(exactLabel);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ describe("createQuestionLabelTruncatorHook", () => {
|
|||||||
const originalArgs = { ...output.args };
|
const originalArgs = { ...output.args };
|
||||||
|
|
||||||
// when
|
// when
|
||||||
await hook["tool.execute.before"]?.(input as any, output as any);
|
await hook["tool.execute.before"]?.(testCoerce(input), testCoerce(output));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
expect(output.args).toEqual(originalArgs);
|
expect(output.args).toEqual(originalArgs);
|
||||||
@@ -120,11 +120,11 @@ describe("createQuestionLabelTruncatorHook", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// when
|
// when
|
||||||
await hook["tool.execute.before"]?.(input as any, output as any);
|
await hook["tool.execute.before"]?.(testCoerce(input), testCoerce(output));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
const q1opts = (output.args as any).questions[0].options;
|
const q1opts = (testCoerce(output.args)).questions[0].options;
|
||||||
const q2opts = (output.args as any).questions[1].options;
|
const q2opts = (testCoerce(output.args)).questions[1].options;
|
||||||
|
|
||||||
expect(q1opts[0].label).toBe("Very long label number one ...");
|
expect(q1opts[0].label).toBe("Very long label number one ...");
|
||||||
expect(q1opts[0].label.length).toBeLessThanOrEqual(30);
|
expect(q1opts[0].label.length).toBeLessThanOrEqual(30);
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ describe("runtime-fallback fallback-models", () => {
|
|||||||
//#given
|
//#given
|
||||||
const sessionID = "ses_runtime_fallback_category"
|
const sessionID = "ses_runtime_fallback_category"
|
||||||
SessionCategoryRegistry.register(sessionID, "quick")
|
SessionCategoryRegistry.register(sessionID, "quick")
|
||||||
const pluginConfig = {
|
const pluginConfig = testCoerce({
|
||||||
categories: {
|
categories: {
|
||||||
quick: {
|
quick: {
|
||||||
fallback_models: ["openai/gpt-5.2", "anthropic/claude-opus-4-7"],
|
fallback_models: ["openai/gpt-5.2", "anthropic/claude-opus-4-7"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as any
|
})
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
const result = getFallbackModelsForSession(sessionID, undefined, pluginConfig)
|
const result = getFallbackModelsForSession(sessionID, undefined, pluginConfig)
|
||||||
@@ -29,13 +29,13 @@ describe("runtime-fallback fallback-models", () => {
|
|||||||
|
|
||||||
test("uses agent-specific fallback_models when agent is resolved", () => {
|
test("uses agent-specific fallback_models when agent is resolved", () => {
|
||||||
//#given
|
//#given
|
||||||
const pluginConfig = {
|
const pluginConfig = testCoerce({
|
||||||
agents: {
|
agents: {
|
||||||
oracle: {
|
oracle: {
|
||||||
fallback_models: ["openai/gpt-5.2", "anthropic/claude-opus-4-7"],
|
fallback_models: ["openai/gpt-5.2", "anthropic/claude-opus-4-7"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as any
|
})
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
const result = getFallbackModelsForSession("ses_runtime_fallback_agent", "oracle", pluginConfig)
|
const result = getFallbackModelsForSession("ses_runtime_fallback_agent", "oracle", pluginConfig)
|
||||||
@@ -46,7 +46,7 @@ describe("runtime-fallback fallback-models", () => {
|
|||||||
|
|
||||||
test("does not fall back to another agent chain when agent cannot be resolved", () => {
|
test("does not fall back to another agent chain when agent cannot be resolved", () => {
|
||||||
//#given
|
//#given
|
||||||
const pluginConfig = {
|
const pluginConfig = testCoerce({
|
||||||
agents: {
|
agents: {
|
||||||
sisyphus: {
|
sisyphus: {
|
||||||
fallback_models: ["quotio/gpt-5.2", "quotio/glm-5", "quotio/kimi-k2.5"],
|
fallback_models: ["quotio/gpt-5.2", "quotio/glm-5", "quotio/kimi-k2.5"],
|
||||||
@@ -55,7 +55,7 @@ describe("runtime-fallback fallback-models", () => {
|
|||||||
fallback_models: ["openai/gpt-5.2", "anthropic/claude-opus-4-7"],
|
fallback_models: ["openai/gpt-5.2", "anthropic/claude-opus-4-7"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as any
|
})
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
const result = getFallbackModelsForSession("ses_runtime_fallback_unknown", undefined, pluginConfig)
|
const result = getFallbackModelsForSession("ses_runtime_fallback_unknown", undefined, pluginConfig)
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ describe("runtime-fallback", () => {
|
|||||||
abort?: (args: unknown) => Promise<unknown>
|
abort?: (args: unknown) => Promise<unknown>
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
return {
|
return testCoerce({
|
||||||
client: {
|
client: {
|
||||||
tui: {
|
tui: {
|
||||||
showToast: async (opts: { body: { title: string; message: string; variant: string; duration: number } }) => {
|
showToast: async (opts: { body: { title: string; message: string; variant: string; duration: number } }) => {
|
||||||
@@ -59,7 +59,7 @@ describe("runtime-fallback", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
directory: "/test/dir",
|
directory: "/test/dir",
|
||||||
} as any
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMockConfig(overrides?: Partial<RuntimeFallbackConfig>): RuntimeFallbackConfig {
|
function createMockConfig(overrides?: Partial<RuntimeFallbackConfig>): RuntimeFallbackConfig {
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ describe("session-notification-sender", () => {
|
|||||||
describe("#when calling ctx.$ for notifications", () => {
|
describe("#when calling ctx.$ for notifications", () => {
|
||||||
test("#then should call .quiet() on all shell commands to suppress stdout/stderr", async () => {
|
test("#then should call .quiet() on all shell commands to suppress stdout/stderr", async () => {
|
||||||
const quietCalls: string[] = []
|
const quietCalls: string[] = []
|
||||||
const mockCtx = {
|
const mockCtx = testCoerce<PluginInput>({
|
||||||
$: (cmd: TemplateStringsArray, ...values: unknown[]) => {
|
$: (cmd: TemplateStringsArray, ...values: unknown[]) => {
|
||||||
const cmdStr = cmd.reduce((acc, part, i) => acc + part + (values[i] ?? ""), "")
|
const cmdStr = cmd.reduce((acc, part, i) => acc + part + (values[i] ?? ""), "")
|
||||||
const result = { stdout: Buffer.from(""), stderr: Buffer.from(""), exitCode: 0 }
|
const result = { stdout: Buffer.from(""), stderr: Buffer.from(""), exitCode: 0 }
|
||||||
@@ -95,7 +95,7 @@ describe("session-notification-sender", () => {
|
|||||||
promise.nothrow = () => promise
|
promise.nothrow = () => promise
|
||||||
return promise
|
return promise
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput
|
})
|
||||||
|
|
||||||
await sender.sendSessionNotification(mockCtx, "darwin", "Test", "Message")
|
await sender.sendSessionNotification(mockCtx, "darwin", "Test", "Message")
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ describe("session-notification-sender", () => {
|
|||||||
spyOn(utils, "getTerminalNotifierPath").mockResolvedValue(null)
|
spyOn(utils, "getTerminalNotifierPath").mockResolvedValue(null)
|
||||||
|
|
||||||
const quietCalls: string[] = []
|
const quietCalls: string[] = []
|
||||||
const mockCtx = {
|
const mockCtx = testCoerce<PluginInput>({
|
||||||
$: (cmd: TemplateStringsArray, ...values: unknown[]) => {
|
$: (cmd: TemplateStringsArray, ...values: unknown[]) => {
|
||||||
const cmdStr = cmd.reduce((acc, part, i) => acc + part + (values[i] ?? ""), "")
|
const cmdStr = cmd.reduce((acc, part, i) => acc + part + (values[i] ?? ""), "")
|
||||||
const result = { stdout: Buffer.from(""), stderr: Buffer.from(""), exitCode: 0 }
|
const result = { stdout: Buffer.from(""), stderr: Buffer.from(""), exitCode: 0 }
|
||||||
@@ -130,7 +130,7 @@ describe("session-notification-sender", () => {
|
|||||||
}
|
}
|
||||||
return promise
|
return promise
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput
|
})
|
||||||
|
|
||||||
await sender.sendSessionNotification(mockCtx, "darwin", "Test", "Message")
|
await sender.sendSessionNotification(mockCtx, "darwin", "Test", "Message")
|
||||||
|
|
||||||
@@ -142,9 +142,9 @@ describe("session-notification-sender", () => {
|
|||||||
spyOn(utils, "getCmuxPath").mockResolvedValue("/usr/local/bin/cmux")
|
spyOn(utils, "getCmuxPath").mockResolvedValue("/usr/local/bin/cmux")
|
||||||
|
|
||||||
const calls: string[] = []
|
const calls: string[] = []
|
||||||
const mockCtx = {
|
const mockCtx = testCoerce<PluginInput>({
|
||||||
$: createShellPromise((cmdStr) => { calls.push(cmdStr) }),
|
$: createShellPromise((cmdStr) => { calls.push(cmdStr) }),
|
||||||
} as unknown as PluginInput
|
})
|
||||||
|
|
||||||
await sender.sendSessionNotification(mockCtx, "darwin", "Test", "Message")
|
await sender.sendSessionNotification(mockCtx, "darwin", "Test", "Message")
|
||||||
|
|
||||||
@@ -157,9 +157,9 @@ describe("session-notification-sender", () => {
|
|||||||
test("#then should fall back to terminal-notifier when cmux fails", async () => {
|
test("#then should fall back to terminal-notifier when cmux fails", async () => {
|
||||||
spyOn(utils, "getCmuxPath").mockResolvedValue("/usr/local/bin/cmux")
|
spyOn(utils, "getCmuxPath").mockResolvedValue("/usr/local/bin/cmux")
|
||||||
|
|
||||||
const mockCtx = {
|
const mockCtx = testCoerce<PluginInput>({
|
||||||
$: createThrowingShellPromise((cmdStr) => cmdStr.includes("cmux notify")),
|
$: createThrowingShellPromise((cmdStr) => cmdStr.includes("cmux notify")),
|
||||||
} as unknown as PluginInput
|
})
|
||||||
|
|
||||||
const originalFactory = mockCtx.$
|
const originalFactory = mockCtx.$
|
||||||
const trackingCalls: string[] = []
|
const trackingCalls: string[] = []
|
||||||
@@ -180,9 +180,9 @@ describe("session-notification-sender", () => {
|
|||||||
spyOn(utils, "getCmuxPath").mockResolvedValue("/usr/local/bin/cmux")
|
spyOn(utils, "getCmuxPath").mockResolvedValue("/usr/local/bin/cmux")
|
||||||
|
|
||||||
const trackingCalls: string[] = []
|
const trackingCalls: string[] = []
|
||||||
const mockCtx = {
|
const mockCtx = testCoerce<PluginInput>({
|
||||||
$: createThrowingShellPromise((cmdStr) => cmdStr.includes("cmux notify") || cmdStr.includes("terminal-notifier")),
|
$: createThrowingShellPromise((cmdStr) => cmdStr.includes("cmux notify") || cmdStr.includes("terminal-notifier")),
|
||||||
} as unknown as PluginInput
|
})
|
||||||
|
|
||||||
const originalFactory = mockCtx.$
|
const originalFactory = mockCtx.$
|
||||||
mockCtx.$ = ((cmd: TemplateStringsArray, ...values: unknown[]) => {
|
mockCtx.$ = ((cmd: TemplateStringsArray, ...values: unknown[]) => {
|
||||||
@@ -200,9 +200,9 @@ describe("session-notification-sender", () => {
|
|||||||
|
|
||||||
test("#then should skip cmux when not available and use terminal-notifier", async () => {
|
test("#then should skip cmux when not available and use terminal-notifier", async () => {
|
||||||
const calls: string[] = []
|
const calls: string[] = []
|
||||||
const mockCtx = {
|
const mockCtx = testCoerce<PluginInput>({
|
||||||
$: createShellPromise((cmdStr) => { calls.push(cmdStr) }),
|
$: createShellPromise((cmdStr) => { calls.push(cmdStr) }),
|
||||||
} as unknown as PluginInput
|
})
|
||||||
|
|
||||||
await sender.sendSessionNotification(mockCtx, "darwin", "Test", "Message")
|
await sender.sendSessionNotification(mockCtx, "darwin", "Test", "Message")
|
||||||
|
|
||||||
@@ -213,7 +213,7 @@ describe("session-notification-sender", () => {
|
|||||||
|
|
||||||
test("#then should call .quiet() on linux notify-send", async () => {
|
test("#then should call .quiet() on linux notify-send", async () => {
|
||||||
const quietCalls: string[] = []
|
const quietCalls: string[] = []
|
||||||
const mockCtx = {
|
const mockCtx = testCoerce<PluginInput>({
|
||||||
$: (cmd: TemplateStringsArray, ...values: unknown[]) => {
|
$: (cmd: TemplateStringsArray, ...values: unknown[]) => {
|
||||||
const cmdStr = cmd.reduce((acc, part, i) => acc + part + (values[i] ?? ""), "")
|
const cmdStr = cmd.reduce((acc, part, i) => acc + part + (values[i] ?? ""), "")
|
||||||
const result = { stdout: Buffer.from(""), stderr: Buffer.from(""), exitCode: 0 }
|
const result = { stdout: Buffer.from(""), stderr: Buffer.from(""), exitCode: 0 }
|
||||||
@@ -236,7 +236,7 @@ describe("session-notification-sender", () => {
|
|||||||
}
|
}
|
||||||
return promise
|
return promise
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput
|
})
|
||||||
|
|
||||||
await sender.sendSessionNotification(mockCtx, "linux", "Test", "Message")
|
await sender.sendSessionNotification(mockCtx, "linux", "Test", "Message")
|
||||||
|
|
||||||
@@ -246,7 +246,7 @@ describe("session-notification-sender", () => {
|
|||||||
|
|
||||||
test("#then should call .quiet() on win32 powershell", async () => {
|
test("#then should call .quiet() on win32 powershell", async () => {
|
||||||
const quietCalls: string[] = []
|
const quietCalls: string[] = []
|
||||||
const mockCtx = {
|
const mockCtx = testCoerce<PluginInput>({
|
||||||
$: (cmd: TemplateStringsArray, ...values: unknown[]) => {
|
$: (cmd: TemplateStringsArray, ...values: unknown[]) => {
|
||||||
const cmdStr = cmd.reduce((acc, part, i) => acc + part + (values[i] ?? ""), "")
|
const cmdStr = cmd.reduce((acc, part, i) => acc + part + (values[i] ?? ""), "")
|
||||||
const result = { stdout: Buffer.from(""), stderr: Buffer.from(""), exitCode: 0 }
|
const result = { stdout: Buffer.from(""), stderr: Buffer.from(""), exitCode: 0 }
|
||||||
@@ -269,7 +269,7 @@ describe("session-notification-sender", () => {
|
|||||||
}
|
}
|
||||||
return promise
|
return promise
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput
|
})
|
||||||
|
|
||||||
await sender.sendSessionNotification(mockCtx, "win32", "Test", "Message")
|
await sender.sendSessionNotification(mockCtx, "win32", "Test", "Message")
|
||||||
|
|
||||||
@@ -283,7 +283,7 @@ describe("session-notification-sender", () => {
|
|||||||
describe("#when calling ctx.$ for sound playback", () => {
|
describe("#when calling ctx.$ for sound playback", () => {
|
||||||
test("#then should call .quiet() on darwin afplay", async () => {
|
test("#then should call .quiet() on darwin afplay", async () => {
|
||||||
const quietCalls: string[] = []
|
const quietCalls: string[] = []
|
||||||
const mockCtx = {
|
const mockCtx = testCoerce<PluginInput>({
|
||||||
$: (cmd: TemplateStringsArray, ...values: unknown[]) => {
|
$: (cmd: TemplateStringsArray, ...values: unknown[]) => {
|
||||||
const cmdStr = cmd.reduce((acc, part, i) => acc + part + (values[i] ?? ""), "")
|
const cmdStr = cmd.reduce((acc, part, i) => acc + part + (values[i] ?? ""), "")
|
||||||
const result = { stdout: Buffer.from(""), stderr: Buffer.from(""), exitCode: 0 }
|
const result = { stdout: Buffer.from(""), stderr: Buffer.from(""), exitCode: 0 }
|
||||||
@@ -306,7 +306,7 @@ describe("session-notification-sender", () => {
|
|||||||
}
|
}
|
||||||
return promise
|
return promise
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput
|
})
|
||||||
|
|
||||||
await sender.playSessionNotificationSound(mockCtx, "darwin", "/sound.aiff")
|
await sender.playSessionNotificationSound(mockCtx, "darwin", "/sound.aiff")
|
||||||
|
|
||||||
@@ -316,7 +316,7 @@ describe("session-notification-sender", () => {
|
|||||||
|
|
||||||
test("#then should call .quiet() on linux paplay", async () => {
|
test("#then should call .quiet() on linux paplay", async () => {
|
||||||
const quietCalls: string[] = []
|
const quietCalls: string[] = []
|
||||||
const mockCtx = {
|
const mockCtx = testCoerce<PluginInput>({
|
||||||
$: (cmd: TemplateStringsArray, ...values: unknown[]) => {
|
$: (cmd: TemplateStringsArray, ...values: unknown[]) => {
|
||||||
const cmdStr = cmd.reduce((acc, part, i) => acc + part + (values[i] ?? ""), "")
|
const cmdStr = cmd.reduce((acc, part, i) => acc + part + (values[i] ?? ""), "")
|
||||||
const result = { stdout: Buffer.from(""), stderr: Buffer.from(""), exitCode: 0 }
|
const result = { stdout: Buffer.from(""), stderr: Buffer.from(""), exitCode: 0 }
|
||||||
@@ -339,7 +339,7 @@ describe("session-notification-sender", () => {
|
|||||||
}
|
}
|
||||||
return promise
|
return promise
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput
|
})
|
||||||
|
|
||||||
await sender.playSessionNotificationSound(mockCtx, "linux", "/sound.oga")
|
await sender.playSessionNotificationSound(mockCtx, "linux", "/sound.oga")
|
||||||
|
|
||||||
@@ -351,7 +351,7 @@ describe("session-notification-sender", () => {
|
|||||||
spyOn(utils, "getPaplayPath").mockResolvedValue(null)
|
spyOn(utils, "getPaplayPath").mockResolvedValue(null)
|
||||||
|
|
||||||
const quietCalls: string[] = []
|
const quietCalls: string[] = []
|
||||||
const mockCtx = {
|
const mockCtx = testCoerce<PluginInput>({
|
||||||
$: (cmd: TemplateStringsArray, ...values: unknown[]) => {
|
$: (cmd: TemplateStringsArray, ...values: unknown[]) => {
|
||||||
const cmdStr = cmd.reduce((acc, part, i) => acc + part + (values[i] ?? ""), "")
|
const cmdStr = cmd.reduce((acc, part, i) => acc + part + (values[i] ?? ""), "")
|
||||||
const result = { stdout: Buffer.from(""), stderr: Buffer.from(""), exitCode: 0 }
|
const result = { stdout: Buffer.from(""), stderr: Buffer.from(""), exitCode: 0 }
|
||||||
@@ -374,7 +374,7 @@ describe("session-notification-sender", () => {
|
|||||||
}
|
}
|
||||||
return promise
|
return promise
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput
|
})
|
||||||
|
|
||||||
await sender.playSessionNotificationSound(mockCtx, "linux", "/sound.oga")
|
await sender.playSessionNotificationSound(mockCtx, "linux", "/sound.oga")
|
||||||
|
|
||||||
@@ -384,7 +384,7 @@ describe("session-notification-sender", () => {
|
|||||||
|
|
||||||
test("#then should call .quiet() on win32 powershell sound", async () => {
|
test("#then should call .quiet() on win32 powershell sound", async () => {
|
||||||
const quietCalls: string[] = []
|
const quietCalls: string[] = []
|
||||||
const mockCtx = {
|
const mockCtx = testCoerce<PluginInput>({
|
||||||
$: (cmd: TemplateStringsArray, ...values: unknown[]) => {
|
$: (cmd: TemplateStringsArray, ...values: unknown[]) => {
|
||||||
const cmdStr = cmd.reduce((acc, part, i) => acc + part + (values[i] ?? ""), "")
|
const cmdStr = cmd.reduce((acc, part, i) => acc + part + (values[i] ?? ""), "")
|
||||||
const result = { stdout: Buffer.from(""), stderr: Buffer.from(""), exitCode: 0 }
|
const result = { stdout: Buffer.from(""), stderr: Buffer.from(""), exitCode: 0 }
|
||||||
@@ -407,7 +407,7 @@ describe("session-notification-sender", () => {
|
|||||||
}
|
}
|
||||||
return promise
|
return promise
|
||||||
},
|
},
|
||||||
} as unknown as PluginInput
|
})
|
||||||
|
|
||||||
await sender.playSessionNotificationSound(mockCtx, "win32", "C:\\sound.wav")
|
await sender.playSessionNotificationSound(mockCtx, "win32", "C:\\sound.wav")
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ type ClientWithPromptAsync = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasPromptAsync(client: Client): client is Client & ClientWithPromptAsync {
|
||||||
|
return "promptAsync" in client.session && typeof client.session.promptAsync === "function"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
interface ToolUsePart {
|
interface ToolUsePart {
|
||||||
type: "tool_use"
|
type: "tool_use"
|
||||||
@@ -111,7 +115,11 @@ export async function recoverToolResultMissing(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await (client as unknown as ClientWithPromptAsync).session.promptAsync(promptInput)
|
if (!hasPromptAsync(client)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.session.promptAsync(promptInput)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ function createMockClient(handlers: {
|
|||||||
messages?: (sessionID: string) => unknown[]
|
messages?: (sessionID: string) => unknown[]
|
||||||
message?: (sessionID: string, messageID: string) => unknown
|
message?: (sessionID: string, messageID: string) => unknown
|
||||||
}) {
|
}) {
|
||||||
return {
|
return testCoerce({
|
||||||
session: {
|
session: {
|
||||||
messages: async (opts: { path: { id: string } }) => {
|
messages: async (opts: { path: { id: string } }) => {
|
||||||
if (handlers.messages) {
|
if (handlers.messages) {
|
||||||
@@ -28,7 +28,7 @@ function createMockClient(handlers: {
|
|||||||
throw new Error("not implemented")
|
throw new Error("not implemented")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as unknown
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("session-recovery storage SDK readers", () => {
|
describe("session-recovery storage SDK readers", () => {
|
||||||
|
|||||||
@@ -738,7 +738,7 @@ You are starting a Sisyphus work session.
|
|||||||
const promptAsyncMock = spyOn({
|
const promptAsyncMock = spyOn({
|
||||||
promptAsync: async (_request: unknown) => undefined,
|
promptAsync: async (_request: unknown) => undefined,
|
||||||
}, "promptAsync")
|
}, "promptAsync")
|
||||||
const ctx = {
|
const ctx = testCoerce<Parameters<typeof createAtlasHook>[0]>({
|
||||||
directory: testDir,
|
directory: testDir,
|
||||||
client: {
|
client: {
|
||||||
session: {
|
session: {
|
||||||
@@ -747,7 +747,7 @@ You are starting a Sisyphus work session.
|
|||||||
messages: async () => ({ data: [] }),
|
messages: async () => ({ data: [] }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as unknown as Parameters<typeof createAtlasHook>[0]
|
})
|
||||||
const startWorkHook = createStartWorkHook(ctx)
|
const startWorkHook = createStartWorkHook(ctx)
|
||||||
const atlasHook = createAtlasHook(ctx)
|
const atlasHook = createAtlasHook(ctx)
|
||||||
const output = {
|
const output = {
|
||||||
@@ -784,18 +784,18 @@ You are starting a Sisyphus work session.
|
|||||||
promptAsync: async (_request: unknown) => undefined,
|
promptAsync: async (_request: unknown) => undefined,
|
||||||
}, "promptAsync")
|
}, "promptAsync")
|
||||||
|
|
||||||
globalThis.setTimeout = ((callback: Function, delay?: number, ...args: unknown[]) => {
|
globalThis.setTimeout = testCoerce<typeof setTimeout>(((callback: Function, delay?: number, ...args: unknown[]) => {
|
||||||
const normalized = typeof delay === "number" ? delay : 0
|
const normalized = typeof delay === "number" ? delay : 0
|
||||||
if (normalized >= 5000) {
|
if (normalized >= 5000) {
|
||||||
const id = nextTimerId++
|
const id = nextTimerId++
|
||||||
capturedTimers.set(id, { callback: () => callback(...args), cleared: false })
|
capturedTimers.set(id, { callback: () => callback(...args), cleared: false })
|
||||||
return id as unknown as ReturnType<typeof setTimeout>
|
return testCoerce<ReturnType<typeof setTimeout>>(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
return originalSetTimeout(callback as Parameters<typeof originalSetTimeout>[0], delay)
|
return originalSetTimeout(callback as Parameters<typeof originalSetTimeout>[0], delay)
|
||||||
}) as unknown as typeof setTimeout
|
}))
|
||||||
|
|
||||||
globalThis.clearTimeout = ((id?: number | ReturnType<typeof setTimeout>) => {
|
globalThis.clearTimeout = testCoerce<typeof clearTimeout>(((id?: number | ReturnType<typeof setTimeout>) => {
|
||||||
if (typeof id === "number" && capturedTimers.has(id)) {
|
if (typeof id === "number" && capturedTimers.has(id)) {
|
||||||
capturedTimers.get(id)!.cleared = true
|
capturedTimers.get(id)!.cleared = true
|
||||||
capturedTimers.delete(id)
|
capturedTimers.delete(id)
|
||||||
@@ -803,11 +803,11 @@ You are starting a Sisyphus work session.
|
|||||||
}
|
}
|
||||||
|
|
||||||
originalClearTimeout(id as Parameters<typeof originalClearTimeout>[0])
|
originalClearTimeout(id as Parameters<typeof originalClearTimeout>[0])
|
||||||
}) as unknown as typeof clearTimeout
|
}))
|
||||||
|
|
||||||
Date.now = () => fakeNow
|
Date.now = () => fakeNow
|
||||||
|
|
||||||
const ctx = {
|
const ctx = testCoerce<Parameters<typeof createAtlasHook>[0]>({
|
||||||
directory: testDir,
|
directory: testDir,
|
||||||
client: {
|
client: {
|
||||||
session: {
|
session: {
|
||||||
@@ -816,13 +816,13 @@ You are starting a Sisyphus work session.
|
|||||||
messages: async () => ({ data: [] }),
|
messages: async () => ({ data: [] }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as unknown as Parameters<typeof createAtlasHook>[0]
|
})
|
||||||
const startWorkHook = createStartWorkHook(ctx)
|
const startWorkHook = createStartWorkHook(ctx)
|
||||||
const atlasHook = createAtlasHook(ctx, {
|
const atlasHook = createAtlasHook(ctx, {
|
||||||
directory: testDir,
|
directory: testDir,
|
||||||
backgroundManager: {
|
backgroundManager: testCoerce<NonNullable<Parameters<typeof createAtlasHook>[1]>["backgroundManager"]>({
|
||||||
getTasksByParentSession: () => backgroundRunning ? [{ status: "running" }] : [],
|
getTasksByParentSession: () => backgroundRunning ? [{ status: "running" }] : [],
|
||||||
} as unknown as NonNullable<Parameters<typeof createAtlasHook>[1]>["backgroundManager"],
|
}),
|
||||||
})
|
})
|
||||||
const output = {
|
const output = {
|
||||||
message: {} as Record<string, unknown>,
|
message: {} as Record<string, unknown>,
|
||||||
|
|||||||
@@ -31,14 +31,14 @@ describe("stop-continuation-guard", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
function createMockPluginInput() {
|
function createMockPluginInput() {
|
||||||
return {
|
return testCoerce<PluginInput>({
|
||||||
client: {
|
client: {
|
||||||
tui: {
|
tui: {
|
||||||
showToast: async () => ({}),
|
showToast: async () => ({}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
directory: createTempDir(),
|
directory: createTempDir(),
|
||||||
} as unknown as PluginInput
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function createBackgroundTask(status: BackgroundTask["status"], id: string): BackgroundTask {
|
function createBackgroundTask(status: BackgroundTask["status"], id: string): BackgroundTask {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ describe("createTaskResumeInfoHook", () => {
|
|||||||
const input = createInput("task")
|
const input = createInput("task")
|
||||||
const output = {
|
const output = {
|
||||||
title: "delegate_task",
|
title: "delegate_task",
|
||||||
output: undefined as unknown as string,
|
output: testCoerce<string>(undefined),
|
||||||
metadata: {},
|
metadata: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user