Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions packages/opencode/src/session/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1497,6 +1497,73 @@ export namespace SessionPrompt {
})
throw error
}

// Block session-only commands when session doesn't exist
// See fork-features.json: "Plugin command execution and sessionOnly guard"
if (command.sessionOnly) {
try {
await Session.get(input.sessionID)
} catch (error) {
const message = `/${command.name} requires an existing session`
log.warn("session-only command blocked", {
command: command.name,
sessionID: input.sessionID,
error,
})
Bus.publish(Session.Event.Error, {
sessionID: input.sessionID,
error: new NamedError.Unknown({
message,
}).toObject(),
})
throw new Error(message)
}
}

// Plugin commands execute directly via hook
// See fork-features.json: "Plugin command execution and sessionOnly guard"
if (command.type === "plugin") {
const plugins = await Plugin.list()
for (const plugin of plugins) {
const pluginCommands = plugin["plugin.command"]
const pluginCommand = pluginCommands?.[command.name]
if (!pluginCommand) continue

try {
const client = await Plugin.client()
await pluginCommand.execute({
sessionID: input.sessionID,
arguments: input.arguments,
client,
})
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
log.error("plugin command failed", { command: command.name, error: message })
Bus.publish(Session.Event.Error, {
sessionID: input.sessionID,
error: new NamedError.Unknown({
message: `/${command.name} failed: ${message}`,
}).toObject(),
})
throw error
}

// Emit event if plugin created a message
const last = await Session.messages({ sessionID: input.sessionID, limit: 1 })
if (last.length > 0) {
Bus.publish(Command.Event.Executed, {
name: command.name,
sessionID: input.sessionID,
arguments: input.arguments,
messageID: last[0].info.id,
})
return last[0]
}
return
}
return
}

const agentName = command.agent ?? input.agent ?? (await Agent.defaultAgent())

const raw = input.arguments.match(argsRegex) ?? []
Expand Down
25 changes: 23 additions & 2 deletions script/sync/fork-features.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"description": "Fork-specific features from upstream PRs that must be preserved during merges",
"lastUpdated": "2026-01-07",
"lastChange": "Cherry-picked PR #5432: Stream ripgrep output in grep tool to prevent memory exhaustion. Also added spinner support for all running tool calls.",
"note": "v1.1.6 sync. Fork includes streaming grep optimization, spinner for all tool calls, and critical PWA/SDK fixes.",
"lastChange": "Restored plugin command execution and sessionOnly guard that was lost during v1.1.6 merge.",
"note": "v1.1.6 sync. Fork includes streaming grep optimization, spinner for all tool calls, plugin command execution, and critical PWA/SDK fixes.",
"forkDependencies": {
"description": "NPM dependencies added by fork features that MUST be preserved during package.json merges. These are frequently lost when accepting upstream version bumps.",
"packages/opencode/package.json": [
Expand Down Expand Up @@ -1801,6 +1801,27 @@
"markers": ["MATCH_LIMIT", "reader.read()", "proc.kill()", "truncated"]
}
]
},
{
"pr": 0,
"title": "Plugin command execution and sessionOnly guard",
"author": "fork",
"status": "fork-only",
"description": "Execute plugin commands directly via plugin hooks instead of treating them as templates. Includes sessionOnly guard to block commands that require an existing session. Lost during v1.1.6 merge and restored.",
"files": ["packages/opencode/src/session/prompt.ts"],
"criticalCode": [
{
"file": "packages/opencode/src/session/prompt.ts",
"description": "SessionOnly guard checks if session exists before executing command",
"markers": ["command.sessionOnly", "Session.get(input.sessionID)", "requires an existing session"]
},
{
"file": "packages/opencode/src/session/prompt.ts",
"description": "Plugin command execution via plugin hooks with error handling",
"markers": ["command.type === \"plugin\"", "Plugin.list()", "pluginCommand.execute", "Plugin.client()"]
}
],
"note": "This code was originally added in commit 57ac84410 but lost during v1.1.6 upstream merge. Tests in test/command/plugin-commands.test.ts verify this behavior."
}
]
}