fix(stop-continuation): scope start-work clearing to fallback template
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -17,6 +17,18 @@ import { clearSessionModel, getSessionModel, setSessionModel } from "../shared/s
|
|||||||
type ChatMessagePart = { type: string; text?: string; [key: string]: unknown }
|
type ChatMessagePart = { type: string; text?: string; [key: string]: unknown }
|
||||||
type ChatMessageHandlerOutput = { message: Record<string, unknown>; parts: ChatMessagePart[] }
|
type ChatMessageHandlerOutput = { message: Record<string, unknown>; parts: ChatMessagePart[] }
|
||||||
|
|
||||||
|
function createStartWorkTemplateOutput(): ChatMessageHandlerOutput {
|
||||||
|
return {
|
||||||
|
message: {},
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `<session-context>context</session-context>\nYou are starting a Sisyphus work session.`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function createStopContinuationGuardMock(isStopped: boolean) {
|
function createStopContinuationGuardMock(isStopped: boolean) {
|
||||||
const clearCalls: string[] = []
|
const clearCalls: string[] = []
|
||||||
const isStoppedCalls: string[] = []
|
const isStoppedCalls: string[] = []
|
||||||
@@ -248,10 +260,7 @@ describe("createChatMessageHandler - stop continuation clearing for raw slash fa
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
const handler = createChatMessageHandler(args)
|
const handler = createChatMessageHandler(args)
|
||||||
const output: ChatMessageHandlerOutput = {
|
const output = createStartWorkTemplateOutput()
|
||||||
message: {},
|
|
||||||
parts: [{ type: "text", text: "/start-work" }],
|
|
||||||
}
|
|
||||||
|
|
||||||
// when
|
// when
|
||||||
await handler(createMockInput("sisyphus"), output)
|
await handler(createMockInput("sisyphus"), output)
|
||||||
@@ -322,6 +331,31 @@ describe("createChatMessageHandler - stop continuation clearing for raw slash fa
|
|||||||
expect(stopContinuationGuard.clearCalls).toEqual(["test-session"])
|
expect(stopContinuationGuard.clearCalls).toEqual(["test-session"])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("does not clear stop state for ordinary stopped chat messages", async () => {
|
||||||
|
// given
|
||||||
|
const stopContinuationGuard = createStopContinuationGuardMock(true)
|
||||||
|
const startWorkCalls: string[] = []
|
||||||
|
const args = createMockHandlerArgs()
|
||||||
|
args.hooks.stopContinuationGuard = stopContinuationGuard.guard
|
||||||
|
args.hooks.startWork = {
|
||||||
|
"chat.message": async (input: { sessionID: string }) => {
|
||||||
|
startWorkCalls.push(input.sessionID)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const handler = createChatMessageHandler(args)
|
||||||
|
|
||||||
|
// when
|
||||||
|
await handler(createMockInput("sisyphus"), {
|
||||||
|
message: {},
|
||||||
|
parts: [{ type: "text", text: "continue helping with this bug" }],
|
||||||
|
})
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(startWorkCalls).toEqual(["test-session"])
|
||||||
|
expect(stopContinuationGuard.isStoppedCalls).toHaveLength(0)
|
||||||
|
expect(stopContinuationGuard.clearCalls).toHaveLength(0)
|
||||||
|
})
|
||||||
|
|
||||||
test("does not clear stop state when the session was not stopped", async () => {
|
test("does not clear stop state when the session was not stopped", async () => {
|
||||||
// given
|
// given
|
||||||
const stopContinuationGuard = createStopContinuationGuardMock(false)
|
const stopContinuationGuard = createStopContinuationGuardMock(false)
|
||||||
@@ -346,7 +380,7 @@ describe("createChatMessageHandler - stop continuation clearing for raw slash fa
|
|||||||
// when
|
// when
|
||||||
await handler(createMockInput("sisyphus"), {
|
await handler(createMockInput("sisyphus"), {
|
||||||
message: {},
|
message: {},
|
||||||
parts: [{ type: "text", text: "/start-work" }],
|
parts: createStartWorkTemplateOutput().parts,
|
||||||
})
|
})
|
||||||
await handler(createMockInput("sisyphus"), {
|
await handler(createMockInput("sisyphus"), {
|
||||||
message: {},
|
message: {},
|
||||||
@@ -371,8 +405,6 @@ describe("createChatMessageHandler - stop continuation clearing for raw slash fa
|
|||||||
"test-session",
|
"test-session",
|
||||||
"test-session",
|
"test-session",
|
||||||
"test-session",
|
"test-session",
|
||||||
"test-session",
|
|
||||||
"test-session",
|
|
||||||
])
|
])
|
||||||
expect(stopContinuationGuard.clearCalls).toHaveLength(0)
|
expect(stopContinuationGuard.clearCalls).toHaveLength(0)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export type ChatMessageInput = {
|
|||||||
type StartWorkHookOutput = { parts: Array<{ type: string; text?: string }> }
|
type StartWorkHookOutput = { parts: Array<{ type: string; text?: string }> }
|
||||||
|
|
||||||
type SessionModelOverride = { providerID: string; modelID: string }
|
type SessionModelOverride = { providerID: string; modelID: string }
|
||||||
|
const START_WORK_TEMPLATE_MARKER = "You are starting a Sisyphus work session."
|
||||||
|
|
||||||
type RawLoopCommand =
|
type RawLoopCommand =
|
||||||
| { command: "ralph-loop" | "ulw-loop"; args: string }
|
| { command: "ralph-loop" | "ulw-loop"; args: string }
|
||||||
@@ -125,6 +126,23 @@ function parseRawLoopSlashCommand(promptText: string): RawLoopCommand | null {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractPromptText(parts: ChatMessagePart[]): string {
|
||||||
|
return (
|
||||||
|
parts
|
||||||
|
?.filter((part) => part.type === "text" && part.text)
|
||||||
|
.map((part) => part.text)
|
||||||
|
.join("\n")
|
||||||
|
.trim() || ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStartWorkFallbackTemplate(promptText: string): boolean {
|
||||||
|
return (
|
||||||
|
promptText.includes("<session-context>") &&
|
||||||
|
promptText.includes(START_WORK_TEMPLATE_MARKER)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function clearStoppedContinuationBeforeWorkStart(
|
function clearStoppedContinuationBeforeWorkStart(
|
||||||
hooks: CreatedHooks,
|
hooks: CreatedHooks,
|
||||||
sessionID: string,
|
sessionID: string,
|
||||||
@@ -221,7 +239,10 @@ export function createChatMessageHandler(args: {
|
|||||||
await hooks.noSisyphusGpt?.["chat.message"]?.(input, output)
|
await hooks.noSisyphusGpt?.["chat.message"]?.(input, output)
|
||||||
await hooks.noHephaestusNonGpt?.["chat.message"]?.(input, output)
|
await hooks.noHephaestusNonGpt?.["chat.message"]?.(input, output)
|
||||||
if (hooks.startWork && isStartWorkHookOutput(output)) {
|
if (hooks.startWork && isStartWorkHookOutput(output)) {
|
||||||
clearStoppedContinuationBeforeWorkStart(hooks, input.sessionID, "start-work")
|
const promptText = extractPromptText(output.parts)
|
||||||
|
if (isStartWorkFallbackTemplate(promptText)) {
|
||||||
|
clearStoppedContinuationBeforeWorkStart(hooks, input.sessionID, "start-work")
|
||||||
|
}
|
||||||
await hooks.startWork["chat.message"]?.(input, output)
|
await hooks.startWork["chat.message"]?.(input, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,12 +262,7 @@ export function createChatMessageHandler(args: {
|
|||||||
|
|
||||||
if (hooks.ralphLoop && output.message[NATIVE_LOOP_TRIGGERED_FLAG] !== true) {
|
if (hooks.ralphLoop && output.message[NATIVE_LOOP_TRIGGERED_FLAG] !== true) {
|
||||||
const parts = output.parts
|
const parts = output.parts
|
||||||
const promptText =
|
const promptText = extractPromptText(parts)
|
||||||
parts
|
|
||||||
?.filter((p) => p.type === "text" && p.text)
|
|
||||||
.map((p) => p.text)
|
|
||||||
.join("\n")
|
|
||||||
.trim() || ""
|
|
||||||
|
|
||||||
const isRalphLoopTemplate =
|
const isRalphLoopTemplate =
|
||||||
promptText.includes("You are starting a Ralph Loop") &&
|
promptText.includes("You are starting a Ralph Loop") &&
|
||||||
|
|||||||
Reference in New Issue
Block a user