diff --git a/src/hooks/anthropic-context-window-limit-recovery/storage-paths.ts b/src/hooks/anthropic-context-window-limit-recovery/storage-paths.ts index 95825a0a..249603fa 100644 --- a/src/hooks/anthropic-context-window-limit-recovery/storage-paths.ts +++ b/src/hooks/anthropic-context-window-limit-recovery/storage-paths.ts @@ -1,10 +1,6 @@ -import { join } from "node:path" -import { getOpenCodeStorageDir } from "../../shared/data-path" +import { MESSAGE_STORAGE, PART_STORAGE } from "../../shared" -const OPENCODE_STORAGE_DIR = getOpenCodeStorageDir() - -export const MESSAGE_STORAGE_DIR = join(OPENCODE_STORAGE_DIR, "message") -export const PART_STORAGE_DIR = join(OPENCODE_STORAGE_DIR, "part") +export { MESSAGE_STORAGE as MESSAGE_STORAGE_DIR, PART_STORAGE as PART_STORAGE_DIR } export const TRUNCATION_MESSAGE = "[TOOL RESULT TRUNCATED - Context limit exceeded. Original output was too large and has been truncated to recover the session. Please re-run this tool if you need the full output.]" diff --git a/src/hooks/atlas/atlas-hook.ts b/src/hooks/atlas/atlas-hook.ts index 5d8c47f4..94a6470e 100644 --- a/src/hooks/atlas/atlas-hook.ts +++ b/src/hooks/atlas/atlas-hook.ts @@ -19,7 +19,7 @@ export function createAtlasHook(ctx: PluginInput, options?: AtlasHookOptions) { return { handler: createAtlasEventHandler({ ctx, options, sessions, getState }), - "tool.execute.before": createToolExecuteBeforeHandler({ pendingFilePaths }), + "tool.execute.before": createToolExecuteBeforeHandler({ ctx, pendingFilePaths }), "tool.execute.after": createToolExecuteAfterHandler({ ctx, pendingFilePaths }), } } diff --git a/src/hooks/atlas/tool-execute-after.ts b/src/hooks/atlas/tool-execute-after.ts index f82f3e49..8a7240c4 100644 --- a/src/hooks/atlas/tool-execute-after.ts +++ b/src/hooks/atlas/tool-execute-after.ts @@ -23,7 +23,7 @@ export function createToolExecuteAfterHandler(input: { return } - if (!isCallerOrchestrator(toolInput.sessionID)) { + if (!(await isCallerOrchestrator(toolInput.sessionID, ctx.client))) { return } diff --git a/src/hooks/atlas/tool-execute-before.ts b/src/hooks/atlas/tool-execute-before.ts index 6fb6ba9d..51f67000 100644 --- a/src/hooks/atlas/tool-execute-before.ts +++ b/src/hooks/atlas/tool-execute-before.ts @@ -1,21 +1,23 @@ import { log } from "../../shared/logger" import { SYSTEM_DIRECTIVE_PREFIX } from "../../shared/system-directive" import { isCallerOrchestrator } from "../../shared/session-utils" +import type { PluginInput } from "@opencode-ai/plugin" import { HOOK_NAME } from "./hook-name" import { ORCHESTRATOR_DELEGATION_REQUIRED, SINGLE_TASK_DIRECTIVE } from "./system-reminder-templates" import { isSisyphusPath } from "./sisyphus-path" import { isWriteOrEditToolName } from "./write-edit-tool-policy" export function createToolExecuteBeforeHandler(input: { + ctx: PluginInput pendingFilePaths: Map }): ( toolInput: { tool: string; sessionID?: string; callID?: string }, toolOutput: { args: Record; message?: string } ) => Promise { - const { pendingFilePaths } = input + const { ctx, pendingFilePaths } = input return async (toolInput, toolOutput): Promise => { - if (!isCallerOrchestrator(toolInput.sessionID)) { + if (!(await isCallerOrchestrator(toolInput.sessionID, ctx.client))) { return } diff --git a/src/hooks/session-recovery/constants.ts b/src/hooks/session-recovery/constants.ts index a45b8026..8d5ea5e4 100644 --- a/src/hooks/session-recovery/constants.ts +++ b/src/hooks/session-recovery/constants.ts @@ -1,9 +1,4 @@ -import { join } from "node:path" -import { getOpenCodeStorageDir } from "../../shared/data-path" - -export const OPENCODE_STORAGE = getOpenCodeStorageDir() -export const MESSAGE_STORAGE = join(OPENCODE_STORAGE, "message") -export const PART_STORAGE = join(OPENCODE_STORAGE, "part") +export { OPENCODE_STORAGE, MESSAGE_STORAGE, PART_STORAGE } from "../../shared" export const THINKING_TYPES = new Set(["thinking", "redacted_thinking", "reasoning"]) export const META_TYPES = new Set(["step-start", "step-finish"]) diff --git a/src/hooks/sisyphus-junior-notepad/hook.ts b/src/hooks/sisyphus-junior-notepad/hook.ts index f80c0df0..28a284e6 100644 --- a/src/hooks/sisyphus-junior-notepad/hook.ts +++ b/src/hooks/sisyphus-junior-notepad/hook.ts @@ -5,7 +5,7 @@ import { SYSTEM_DIRECTIVE_PREFIX } from "../../shared/system-directive" import { log } from "../../shared/logger" import { HOOK_NAME, NOTEPAD_DIRECTIVE } from "./constants" -export function createSisyphusJuniorNotepadHook(_ctx: PluginInput) { +export function createSisyphusJuniorNotepadHook(ctx: PluginInput) { return { "tool.execute.before": async ( input: { tool: string; sessionID: string; callID: string }, @@ -17,7 +17,7 @@ export function createSisyphusJuniorNotepadHook(_ctx: PluginInput) { } // 2. Check if caller is Atlas (orchestrator) - if (!isCallerOrchestrator(input.sessionID)) { + if (!(await isCallerOrchestrator(input.sessionID, ctx.client))) { return } diff --git a/src/shared/opencode-storage-detection.ts b/src/shared/opencode-storage-detection.ts index 7fdb5a5c..3e0aa474 100644 --- a/src/shared/opencode-storage-detection.ts +++ b/src/shared/opencode-storage-detection.ts @@ -3,10 +3,11 @@ import { join } from "node:path" import { getDataDir } from "./data-path" import { isOpenCodeVersionAtLeast, OPENCODE_SQLITE_VERSION } from "./opencode-version" -let cachedResult: boolean | null = null +const NOT_CACHED = Symbol("NOT_CACHED") +let cachedResult: boolean | typeof NOT_CACHED = NOT_CACHED export function isSqliteBackend(): boolean { - if (cachedResult !== null) { + if (cachedResult !== NOT_CACHED) { return cachedResult } @@ -19,5 +20,5 @@ export function isSqliteBackend(): boolean { } export function resetSqliteBackendCache(): void { - cachedResult = null + cachedResult = NOT_CACHED } \ No newline at end of file diff --git a/src/shared/session-utils.ts b/src/shared/session-utils.ts index 40e73bb2..ce228361 100644 --- a/src/shared/session-utils.ts +++ b/src/shared/session-utils.ts @@ -1,12 +1,22 @@ -import * as path from "node:path" -import * as os from "node:os" -import { existsSync, readdirSync } from "node:fs" -import { join } from "node:path" -import { findNearestMessageWithFields, MESSAGE_STORAGE } from "../features/hook-message-injector" +import { findNearestMessageWithFields, findNearestMessageWithFieldsFromSDK } from "../features/hook-message-injector" import { getMessageDir } from "./opencode-message-dir" +import { isSqliteBackend } from "./opencode-storage-detection" +import type { PluginInput } from "@opencode-ai/plugin" -export function isCallerOrchestrator(sessionID?: string): boolean { +export async function isCallerOrchestrator(sessionID?: string, client?: PluginInput["client"]): Promise { if (!sessionID) return false + + // Beta mode: use SDK if client provided + if (isSqliteBackend() && client) { + try { + const nearest = await findNearestMessageWithFieldsFromSDK(client, sessionID) + return nearest?.agent?.toLowerCase() === "atlas" + } catch { + return false + } + } + + // Stable mode: use JSON files const messageDir = getMessageDir(sessionID) if (!messageDir) return false const nearest = findNearestMessageWithFields(messageDir)