fix(hooks): respect previous message's agent mode in message sending hooks
Message hooks like todo-continuation-enforcer and background-notification now preserve the agent mode from the previous message when sending follow-up prompts. This ensures that continuation messages and task completion notifications use the same agent that was active in the conversation. - Export findNearestMessageWithFields and MESSAGE_STORAGE from hook-message-injector - Add getMessageDir helper to locate session message directories - Pass agent field to session.prompt in todo-continuation-enforcer - Pass agent field to session.prompt in BackgroundManager.notifyParentSession Closes #59 🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
@@ -1,9 +1,15 @@
|
|||||||
|
import { existsSync, readdirSync } from "node:fs"
|
||||||
|
import { join } from "node:path"
|
||||||
import type { PluginInput } from "@opencode-ai/plugin"
|
import type { PluginInput } from "@opencode-ai/plugin"
|
||||||
import type {
|
import type {
|
||||||
BackgroundTask,
|
BackgroundTask,
|
||||||
LaunchInput,
|
LaunchInput,
|
||||||
} from "./types"
|
} from "./types"
|
||||||
import { log } from "../../shared/logger"
|
import { log } from "../../shared/logger"
|
||||||
|
import {
|
||||||
|
findNearestMessageWithFields,
|
||||||
|
MESSAGE_STORAGE,
|
||||||
|
} from "../hook-message-injector"
|
||||||
|
|
||||||
type OpencodeClient = PluginInput["client"]
|
type OpencodeClient = PluginInput["client"]
|
||||||
|
|
||||||
@@ -24,6 +30,20 @@ interface Event {
|
|||||||
properties?: EventProperties
|
properties?: EventProperties
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getMessageDir(sessionID: string): string | null {
|
||||||
|
if (!existsSync(MESSAGE_STORAGE)) return null
|
||||||
|
|
||||||
|
const directPath = join(MESSAGE_STORAGE, sessionID)
|
||||||
|
if (existsSync(directPath)) return directPath
|
||||||
|
|
||||||
|
for (const dir of readdirSync(MESSAGE_STORAGE)) {
|
||||||
|
const sessionPath = join(MESSAGE_STORAGE, dir, sessionID)
|
||||||
|
if (existsSync(sessionPath)) return sessionPath
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
export class BackgroundManager {
|
export class BackgroundManager {
|
||||||
private tasks: Map<string, BackgroundTask>
|
private tasks: Map<string, BackgroundTask>
|
||||||
private notifications: Map<string, BackgroundTask[]>
|
private notifications: Map<string, BackgroundTask[]>
|
||||||
@@ -253,9 +273,13 @@ export class BackgroundManager {
|
|||||||
|
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
try {
|
try {
|
||||||
|
const messageDir = getMessageDir(task.parentSessionID)
|
||||||
|
const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null
|
||||||
|
|
||||||
await this.client.session.prompt({
|
await this.client.session.prompt({
|
||||||
path: { id: task.parentSessionID },
|
path: { id: task.parentSessionID },
|
||||||
body: {
|
body: {
|
||||||
|
agent: prevMessage?.agent,
|
||||||
parts: [{ type: "text", text: message }],
|
parts: [{ type: "text", text: message }],
|
||||||
},
|
},
|
||||||
query: { directory: this.directory },
|
query: { directory: this.directory },
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
export { injectHookMessage } from "./injector"
|
export { injectHookMessage, findNearestMessageWithFields } from "./injector"
|
||||||
|
export type { StoredMessage } from "./injector"
|
||||||
export type { MessageMeta, OriginalMessageContext, TextPart } from "./types"
|
export type { MessageMeta, OriginalMessageContext, TextPart } from "./types"
|
||||||
|
export { MESSAGE_STORAGE } from "./constants"
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import { join } from "node:path"
|
|||||||
import { MESSAGE_STORAGE, PART_STORAGE } from "./constants"
|
import { MESSAGE_STORAGE, PART_STORAGE } from "./constants"
|
||||||
import type { MessageMeta, OriginalMessageContext, TextPart } from "./types"
|
import type { MessageMeta, OriginalMessageContext, TextPart } from "./types"
|
||||||
|
|
||||||
interface StoredMessage {
|
export interface StoredMessage {
|
||||||
agent?: string
|
agent?: string
|
||||||
model?: { providerID?: string; modelID?: string }
|
model?: { providerID?: string; modelID?: string }
|
||||||
tools?: Record<string, boolean>
|
tools?: Record<string, boolean>
|
||||||
}
|
}
|
||||||
|
|
||||||
function findNearestMessageWithFields(messageDir: string): StoredMessage | null {
|
export function findNearestMessageWithFields(messageDir: string): StoredMessage | null {
|
||||||
try {
|
try {
|
||||||
const files = readdirSync(messageDir)
|
const files = readdirSync(messageDir)
|
||||||
.filter((f) => f.endsWith(".json"))
|
.filter((f) => f.endsWith(".json"))
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
|
import { existsSync, readdirSync } from "node:fs"
|
||||||
|
import { join } from "node:path"
|
||||||
import type { PluginInput } from "@opencode-ai/plugin"
|
import type { PluginInput } from "@opencode-ai/plugin"
|
||||||
|
import {
|
||||||
|
findNearestMessageWithFields,
|
||||||
|
MESSAGE_STORAGE,
|
||||||
|
} from "../features/hook-message-injector"
|
||||||
|
|
||||||
export interface TodoContinuationEnforcer {
|
export interface TodoContinuationEnforcer {
|
||||||
handler: (input: { event: { type: string; properties?: unknown } }) => Promise<void>
|
handler: (input: { event: { type: string; properties?: unknown } }) => Promise<void>
|
||||||
@@ -21,6 +27,20 @@ Incomplete tasks remain in your todo list. Continue working on the next pending
|
|||||||
- Mark each task complete when finished
|
- Mark each task complete when finished
|
||||||
- Do not stop until all tasks are done`
|
- Do not stop until all tasks are done`
|
||||||
|
|
||||||
|
function getMessageDir(sessionID: string): string | null {
|
||||||
|
if (!existsSync(MESSAGE_STORAGE)) return null
|
||||||
|
|
||||||
|
const directPath = join(MESSAGE_STORAGE, sessionID)
|
||||||
|
if (existsSync(directPath)) return directPath
|
||||||
|
|
||||||
|
for (const dir of readdirSync(MESSAGE_STORAGE)) {
|
||||||
|
const sessionPath = join(MESSAGE_STORAGE, dir, sessionID)
|
||||||
|
if (existsSync(sessionPath)) return sessionPath
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
function detectInterrupt(error: unknown): boolean {
|
function detectInterrupt(error: unknown): boolean {
|
||||||
if (!error) return false
|
if (!error) return false
|
||||||
if (typeof error === "object") {
|
if (typeof error === "object") {
|
||||||
@@ -137,9 +157,14 @@ export function createTodoContinuationEnforcer(ctx: PluginInput): TodoContinuati
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Get previous message's agent info to respect agent mode
|
||||||
|
const messageDir = getMessageDir(sessionID)
|
||||||
|
const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null
|
||||||
|
|
||||||
await ctx.client.session.prompt({
|
await ctx.client.session.prompt({
|
||||||
path: { id: sessionID },
|
path: { id: sessionID },
|
||||||
body: {
|
body: {
|
||||||
|
agent: prevMessage?.agent,
|
||||||
parts: [
|
parts: [
|
||||||
{
|
{
|
||||||
type: "text",
|
type: "text",
|
||||||
|
|||||||
Reference in New Issue
Block a user