2026-02-08 13:57:26 +09:00
|
|
|
import type { DelegateTaskArgs, ToolContextWithMetadata } from "./types"
|
2026-04-17 14:42:38 +09:00
|
|
|
import type { ExecutorContext, ParentContext, SessionMessage } from "./executor-types"
|
2026-04-10 15:53:14 +09:00
|
|
|
import { isPlanFamily } from "./constants"
|
2026-04-16 13:52:20 +09:00
|
|
|
import { publishToolMetadata } from "../../features/tool-metadata-store"
|
2026-02-08 13:57:26 +09:00
|
|
|
import { getTaskToastManager } from "../../features/task-toast-manager"
|
2026-02-08 18:03:15 +09:00
|
|
|
import { getAgentToolRestrictions } from "../../shared/agent-tool-restrictions"
|
2026-04-20 15:16:48 +09:00
|
|
|
import { getMessageDir, normalizeSDKResponse } from "../../shared"
|
2026-02-09 15:37:19 +09:00
|
|
|
import { promptWithModelSuggestionRetry } from "../../shared/model-suggestion-retry"
|
2026-02-08 13:57:26 +09:00
|
|
|
import { findNearestMessageWithFields } from "../../features/hook-message-injector"
|
|
|
|
|
import { formatDuration } from "./time-formatter"
|
2026-02-10 22:54:30 +09:00
|
|
|
import { syncContinuationDeps, type SyncContinuationDeps } from "./sync-continuation-deps"
|
2026-02-14 14:30:22 +09:00
|
|
|
import { setSessionTools } from "../../shared/session-tools-store"
|
2026-03-06 22:56:51 +09:00
|
|
|
import { buildTaskPrompt } from "./prompt-builder"
|
2026-04-16 23:13:44 +09:00
|
|
|
import { buildTaskMetadataBlock } from "../../features/tool-metadata-store/task-metadata-contract"
|
|
|
|
|
import { getTaskID } from "./task-id"
|
2026-04-17 14:42:38 +09:00
|
|
|
import { resolveMetadataModel } from "./resolve-metadata-model"
|
2026-02-08 13:57:26 +09:00
|
|
|
|
2026-04-20 15:48:58 +09:00
|
|
|
type ResumeModel = { providerID: string; modelID: string }
|
|
|
|
|
|
|
|
|
|
type ResumeContext = {
|
|
|
|
|
resumeAgent?: string
|
|
|
|
|
resumeModel?: ResumeModel
|
|
|
|
|
resumeVariant?: string
|
|
|
|
|
anchorMessageCount?: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function resolveResumeContext(
|
|
|
|
|
client: ExecutorContext["client"],
|
|
|
|
|
continuationID: string
|
|
|
|
|
): Promise<ResumeContext> {
|
|
|
|
|
try {
|
|
|
|
|
const messagesResp = await client.session.messages({ path: { id: continuationID } })
|
|
|
|
|
const messages = normalizeSDKResponse(messagesResp, [] as SessionMessage[])
|
|
|
|
|
|
|
|
|
|
for (let index = messages.length - 1; index >= 0; index--) {
|
|
|
|
|
const info = messages[index].info
|
|
|
|
|
if (info?.agent || info?.model || (info?.modelID && info?.providerID)) {
|
|
|
|
|
return {
|
|
|
|
|
resumeAgent: info.agent,
|
|
|
|
|
resumeModel: info.model ?? (info.providerID && info.modelID
|
|
|
|
|
? { providerID: info.providerID, modelID: info.modelID }
|
|
|
|
|
: undefined),
|
|
|
|
|
resumeVariant: info.variant,
|
|
|
|
|
anchorMessageCount: messages.length,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { anchorMessageCount: messages.length }
|
|
|
|
|
} catch {
|
|
|
|
|
const resumeMessageDir = getMessageDir(continuationID)
|
|
|
|
|
const resumeMessage = resumeMessageDir ? findNearestMessageWithFields(resumeMessageDir) : null
|
|
|
|
|
const resumeMessageModel = resumeMessage?.model
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
resumeAgent: resumeMessage?.agent,
|
|
|
|
|
resumeModel: resumeMessageModel?.providerID && resumeMessageModel.modelID
|
|
|
|
|
? { providerID: resumeMessageModel.providerID, modelID: resumeMessageModel.modelID }
|
|
|
|
|
: undefined,
|
|
|
|
|
resumeVariant: resumeMessageModel?.variant,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-08 13:57:26 +09:00
|
|
|
export async function executeSyncContinuation(
|
|
|
|
|
args: DelegateTaskArgs,
|
|
|
|
|
ctx: ToolContextWithMetadata,
|
2026-02-10 22:54:30 +09:00
|
|
|
executorCtx: ExecutorContext,
|
2026-04-17 14:42:38 +09:00
|
|
|
parentContext: ParentContext,
|
2026-04-20 15:35:45 +09:00
|
|
|
deps: SyncContinuationDeps = syncContinuationDeps,
|
|
|
|
|
systemContent?: string
|
2026-02-08 13:57:26 +09:00
|
|
|
): Promise<string> {
|
2026-03-28 09:34:58 -07:00
|
|
|
const { client, syncPollTimeoutMs, sisyphusAgentConfig } = executorCtx
|
2026-02-08 13:57:26 +09:00
|
|
|
const toastManager = getTaskToastManager()
|
2026-04-16 23:13:44 +09:00
|
|
|
const continuationID = getTaskID(args)
|
|
|
|
|
if (!continuationID) {
|
|
|
|
|
throw new Error("task_id is required to continue a sync task")
|
|
|
|
|
}
|
|
|
|
|
const taskId = `resume_sync_${continuationID.slice(0, 8)}`
|
2026-02-08 13:57:26 +09:00
|
|
|
const startTime = new Date()
|
|
|
|
|
|
|
|
|
|
if (toastManager) {
|
|
|
|
|
toastManager.addTask({
|
|
|
|
|
id: taskId,
|
|
|
|
|
description: args.description,
|
|
|
|
|
agent: "continue",
|
|
|
|
|
isBackground: false,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-09 15:37:19 +09:00
|
|
|
let resumeAgent: string | undefined
|
2026-04-20 15:48:58 +09:00
|
|
|
let resumeModel: ResumeModel | undefined
|
2026-02-09 15:37:19 +09:00
|
|
|
let resumeVariant: string | undefined
|
2026-02-10 16:17:24 +09:00
|
|
|
let anchorMessageCount: number | undefined
|
2026-02-08 13:57:26 +09:00
|
|
|
|
2026-02-09 15:37:19 +09:00
|
|
|
try {
|
2026-04-20 15:48:58 +09:00
|
|
|
const resumeContext = await resolveResumeContext(client, continuationID)
|
|
|
|
|
resumeAgent = resumeContext.resumeAgent
|
|
|
|
|
resumeModel = resumeContext.resumeModel
|
|
|
|
|
resumeVariant = resumeContext.resumeVariant
|
|
|
|
|
anchorMessageCount = resumeContext.anchorMessageCount
|
2026-02-08 13:57:26 +09:00
|
|
|
|
2026-04-20 15:32:16 +09:00
|
|
|
const resumeModelForMetadata = resumeModel && resumeVariant !== undefined
|
|
|
|
|
? { ...resumeModel, variant: resumeVariant }
|
|
|
|
|
: resumeModel
|
|
|
|
|
|
2026-04-20 15:35:45 +09:00
|
|
|
const syncContMeta = {
|
2026-03-04 16:35:06 +09:00
|
|
|
title: `Continue: ${args.description}`,
|
|
|
|
|
metadata: {
|
|
|
|
|
prompt: args.prompt,
|
2026-04-20 14:50:11 +09:00
|
|
|
...(resumeAgent !== undefined ? { agent: resumeAgent } : {}),
|
|
|
|
|
...(args.category !== undefined ? { category: args.category } : {}),
|
2026-04-20 14:50:40 +09:00
|
|
|
...(args.requested_subagent_type !== undefined ? { requested_subagent_type: args.requested_subagent_type } : {}),
|
2026-03-04 16:35:06 +09:00
|
|
|
load_skills: args.load_skills,
|
|
|
|
|
description: args.description,
|
|
|
|
|
run_in_background: args.run_in_background,
|
2026-04-16 23:13:44 +09:00
|
|
|
taskId: continuationID,
|
|
|
|
|
sessionId: continuationID,
|
2026-03-04 16:35:06 +09:00
|
|
|
sync: true,
|
|
|
|
|
command: args.command,
|
2026-04-20 15:32:16 +09:00
|
|
|
model: resolveMetadataModel(resumeModelForMetadata, parentContext.model),
|
2026-03-04 16:35:06 +09:00
|
|
|
},
|
|
|
|
|
}
|
2026-04-16 13:52:20 +09:00
|
|
|
await publishToolMetadata(ctx, syncContMeta)
|
2026-03-04 16:35:06 +09:00
|
|
|
|
2026-04-10 15:53:14 +09:00
|
|
|
const allowTask = isPlanFamily(resumeAgent)
|
2026-03-28 09:34:58 -07:00
|
|
|
const tddEnabled = sisyphusAgentConfig?.tdd
|
|
|
|
|
const effectivePrompt = buildTaskPrompt(args.prompt, resumeAgent, tddEnabled)
|
2026-02-14 14:30:22 +09:00
|
|
|
const tools = {
|
|
|
|
|
task: allowTask,
|
|
|
|
|
call_omo_agent: true,
|
|
|
|
|
question: false,
|
2026-03-24 23:54:29 +09:00
|
|
|
...(resumeAgent ? getAgentToolRestrictions(resumeAgent) : {}),
|
2026-02-14 14:30:22 +09:00
|
|
|
}
|
2026-04-16 23:13:44 +09:00
|
|
|
setSessionTools(continuationID, tools)
|
2026-02-08 17:34:47 +09:00
|
|
|
|
2026-02-09 15:37:19 +09:00
|
|
|
await promptWithModelSuggestionRetry(client, {
|
2026-04-16 23:13:44 +09:00
|
|
|
path: { id: continuationID },
|
2026-02-08 17:34:47 +09:00
|
|
|
body: {
|
|
|
|
|
...(resumeAgent !== undefined ? { agent: resumeAgent } : {}),
|
|
|
|
|
...(resumeModel !== undefined ? { model: resumeModel } : {}),
|
|
|
|
|
...(resumeVariant !== undefined ? { variant: resumeVariant } : {}),
|
2026-04-20 15:35:45 +09:00
|
|
|
system: systemContent,
|
2026-02-14 14:30:22 +09:00
|
|
|
tools,
|
2026-03-06 22:56:51 +09:00
|
|
|
parts: [{ type: "text", text: effectivePrompt }],
|
2026-02-08 17:34:47 +09:00
|
|
|
},
|
|
|
|
|
})
|
2026-02-10 16:17:24 +09:00
|
|
|
} catch (promptError) {
|
|
|
|
|
if (toastManager) {
|
|
|
|
|
toastManager.removeTask(taskId)
|
|
|
|
|
}
|
|
|
|
|
const errorMessage = promptError instanceof Error ? promptError.message : String(promptError)
|
2026-04-16 23:13:44 +09:00
|
|
|
return `Failed to send continuation prompt: ${errorMessage}\n\nTask ID: ${continuationID}`
|
2026-02-10 16:17:24 +09:00
|
|
|
}
|
2026-02-08 13:57:26 +09:00
|
|
|
|
2026-02-10 16:17:24 +09:00
|
|
|
try {
|
2026-02-10 22:54:30 +09:00
|
|
|
const pollError = await deps.pollSyncSession(ctx, client, {
|
2026-04-16 23:13:44 +09:00
|
|
|
sessionID: continuationID,
|
2026-02-10 16:17:24 +09:00
|
|
|
agentToUse: resumeAgent ?? "continue",
|
|
|
|
|
toastManager,
|
|
|
|
|
taskId,
|
|
|
|
|
anchorMessageCount,
|
2026-02-27 13:54:50 +08:00
|
|
|
}, syncPollTimeoutMs)
|
2026-02-10 16:17:24 +09:00
|
|
|
if (pollError) {
|
|
|
|
|
return pollError
|
|
|
|
|
}
|
2026-02-08 13:57:26 +09:00
|
|
|
|
2026-04-16 23:13:44 +09:00
|
|
|
const result = await deps.fetchSyncResult(client, continuationID, anchorMessageCount)
|
2026-02-10 22:54:30 +09:00
|
|
|
if (!result.ok) {
|
|
|
|
|
return result.error
|
|
|
|
|
}
|
2026-02-08 13:57:26 +09:00
|
|
|
|
2026-02-10 16:17:24 +09:00
|
|
|
const duration = formatDuration(startTime)
|
2026-02-08 13:57:26 +09:00
|
|
|
|
2026-02-10 16:17:24 +09:00
|
|
|
return `Task continued and completed in ${duration}.
|
2026-02-08 13:57:26 +09:00
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
2026-02-09 15:37:19 +09:00
|
|
|
${result.textContent || "(No text output)"}
|
2026-02-08 13:57:26 +09:00
|
|
|
|
2026-04-16 23:13:44 +09:00
|
|
|
${buildTaskMetadataBlock({
|
|
|
|
|
sessionId: continuationID,
|
|
|
|
|
taskId: continuationID,
|
|
|
|
|
agent: resumeAgent,
|
2026-04-20 15:35:45 +09:00
|
|
|
category: args.category,
|
2026-04-16 23:13:44 +09:00
|
|
|
})}`
|
2026-02-10 16:17:24 +09:00
|
|
|
} finally {
|
|
|
|
|
if (toastManager) {
|
|
|
|
|
toastManager.removeTask(taskId)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-08 13:57:26 +09:00
|
|
|
}
|