Skip to content
This repository was archived by the owner on Apr 1, 2026. It is now read-only.

Commit 041353f

Browse files
authored
make /init a default slash command on server side (anomalyco#3677)
1 parent c72f8b1 commit 041353f

6 files changed

Lines changed: 92 additions & 49 deletions

File tree

packages/opencode/src/acp/agent.ts

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ import { Storage } from "@/storage/storage"
2828
import { Command } from "@/command"
2929
import { Agent as Agents } from "@/agent/agent"
3030
import { Permission } from "@/permission"
31-
import { Session } from "@/session"
32-
import { Identifier } from "@/id/id"
3331
import { SessionCompaction } from "@/session/compaction"
3432
import type { Config } from "@/config/config"
3533
import { MCP } from "@/mcp"
@@ -89,7 +87,11 @@ export namespace ACP {
8987
})
9088
if (!res) return
9189
if (res.outcome.outcome !== "selected") {
92-
Permission.respond({ sessionID: permission.sessionID, permissionID: permission.id, response: "reject" })
90+
Permission.respond({
91+
sessionID: permission.sessionID,
92+
permissionID: permission.id,
93+
response: "reject",
94+
})
9395
return
9496
}
9597
Permission.respond({
@@ -111,9 +113,11 @@ export namespace ACP {
111113
const acpSession = this.sessionManager.get(part.sessionID)
112114
if (!acpSession) return
113115

114-
const message = await Storage.read<MessageV2.Info>(["message", part.sessionID, part.messageID]).catch(
115-
() => undefined,
116-
)
116+
const message = await Storage.read<MessageV2.Info>([
117+
"message",
118+
part.sessionID,
119+
part.messageID,
120+
]).catch(() => undefined)
117121
if (!message || message.role !== "assistant") return
118122

119123
if (part.type === "tool") {
@@ -192,7 +196,9 @@ export namespace ACP {
192196
sessionUpdate: "plan",
193197
entries: parsedTodos.data.map((todo) => {
194198
const status: PlanEntry["status"] =
195-
todo.status === "cancelled" ? "completed" : (todo.status as PlanEntry["status"])
199+
todo.status === "cancelled"
200+
? "completed"
201+
: (todo.status as PlanEntry["status"])
196202
return {
197203
priority: "medium",
198204
status,
@@ -375,11 +381,6 @@ export namespace ACP {
375381
description: command.description ?? "",
376382
}))
377383
const names = new Set(availableCommands.map((c) => c.name))
378-
if (!names.has("init"))
379-
availableCommands.push({
380-
name: "init",
381-
description: "create/update a AGENTS.md",
382-
})
383384
if (!names.has("compact"))
384385
availableCommands.push({
385386
name: "compact",
@@ -404,7 +405,8 @@ export namespace ACP {
404405
description: agent.description,
405406
}))
406407

407-
const currentModeId = availableModes.find((m) => m.name === "build")?.id ?? availableModes[0].id
408+
const currentModeId =
409+
availableModes.find((m) => m.name === "build")?.id ?? availableModes[0].id
408410

409411
const mcpServers: Record<string, Config.Mcp> = {}
410412
for (const server of params.mcpServers) {
@@ -585,14 +587,6 @@ export namespace ACP {
585587
}
586588

587589
switch (cmd.name) {
588-
case "init":
589-
await Session.initialize({
590-
sessionID,
591-
messageID: Identifier.ascending("message"),
592-
providerID: model.providerID,
593-
modelID: model.modelID,
594-
})
595-
break
596590
case "compact":
597591
await SessionCompaction.run({
598592
sessionID,
@@ -665,7 +659,9 @@ export namespace ACP {
665659

666660
function parseUri(
667661
uri: string,
668-
): { type: "file"; url: string; filename: string; mime: string } | { type: "text"; text: string } {
662+
):
663+
| { type: "file"; url: string; filename: string; mime: string }
664+
| { type: "text"; text: string } {
669665
try {
670666
if (uri.startsWith("file://")) {
671667
const path = uri.slice(7)

packages/opencode/src/command/index.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,27 @@
11
import z from "zod"
22
import { Config } from "../config/config"
33
import { Instance } from "../project/instance"
4+
import PROMPT_INITIALIZE from "./template/initialize.txt"
5+
import { Bus } from "../bus"
6+
import { Identifier } from "../id/id"
47

58
export namespace Command {
9+
export const Default = {
10+
INIT: "init",
11+
} as const
12+
13+
export const Event = {
14+
Executed: Bus.event(
15+
"command.executed",
16+
z.object({
17+
name: z.string(),
18+
sessionID: Identifier.schema("session"),
19+
arguments: z.string(),
20+
messageID: Identifier.schema("message"),
21+
}),
22+
),
23+
}
24+
625
export const Info = z
726
.object({
827
name: z.string(),
@@ -33,6 +52,14 @@ export namespace Command {
3352
}
3453
}
3554

55+
if (result[Default.INIT] === undefined) {
56+
result[Default.INIT] = {
57+
name: Default.INIT,
58+
description: "create/update AGENTS.md",
59+
template: PROMPT_INITIALIZE.replace("${path}", Instance.worktree),
60+
}
61+
}
62+
3663
return result
3764
})
3865

packages/opencode/src/session/prompt/initialize.txt renamed to packages/opencode/src/command/template/initialize.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ The file you create will be given to agentic coding agents (such as yourself) th
66
If there are Cursor rules (in .cursor/rules/ or .cursorrules) or Copilot rules (in .github/copilot-instructions.md), make sure to include them.
77

88
If there's already an AGENTS.md, improve it if it's located in ${path}
9+
10+
$ARGUMENTS

packages/opencode/src/project/bootstrap.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import { LSP } from "../lsp"
55
import { FileWatcher } from "../file/watcher"
66
import { File } from "../file"
77
import { Flag } from "../flag/flag"
8+
import { Project } from "./project"
9+
import { Bus } from "../bus"
10+
import { Command } from "../command"
11+
import { Instance } from "./instance"
812

913
export async function InstanceBootstrap() {
1014
if (Flag.OPENCODE_EXPERIMENTAL_NO_BOOTSTRAP) return
@@ -14,4 +18,10 @@ export async function InstanceBootstrap() {
1418
await LSP.init()
1519
FileWatcher.init()
1620
File.init()
21+
22+
Bus.subscribe(Command.Event.Executed, async (payload) => {
23+
if (payload.properties.name === Command.Default.INIT) {
24+
await Project.setInitialized(Instance.project.id)
25+
}
26+
})
1727
}

packages/opencode/src/session/index.ts

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ import { Decimal } from "decimal.js"
22
import z from "zod"
33
import { type LanguageModelUsage, type ProviderMetadata } from "ai"
44

5-
import PROMPT_INITIALIZE from "../session/prompt/initialize.txt"
6-
75
import { Bus } from "../bus"
86
import { Config } from "../config/config"
97
import { Flag } from "../flag/flag"
@@ -14,11 +12,11 @@ import { Share } from "../share/share"
1412
import { Storage } from "../storage/storage"
1513
import { Log } from "../util/log"
1614
import { MessageV2 } from "./message-v2"
17-
import { Project } from "../project/project"
1815
import { Instance } from "../project/instance"
1916
import { SessionPrompt } from "./prompt"
2017
import { fn } from "@/util/fn"
2118
import { Snapshot } from "@/snapshot"
19+
import { Command } from "../command"
2220

2321
export namespace Session {
2422
const log = Log.create({ service: "session" })
@@ -164,7 +162,12 @@ export namespace Session {
164162
})
165163
})
166164

167-
export async function createNext(input: { id?: string; title?: string; parentID?: string; directory: string }) {
165+
export async function createNext(input: {
166+
id?: string
167+
title?: string
168+
parentID?: string
169+
directory: string
170+
}) {
168171
const result: Info = {
169172
id: Identifier.descending("session", input.id),
170173
version: Installation.VERSION,
@@ -402,7 +405,9 @@ export namespace Session {
402405
.add(new Decimal(tokens.input).mul(input.model.cost?.input ?? 0).div(1_000_000))
403406
.add(new Decimal(tokens.output).mul(input.model.cost?.output ?? 0).div(1_000_000))
404407
.add(new Decimal(tokens.cache.read).mul(input.model.cost?.cache_read ?? 0).div(1_000_000))
405-
.add(new Decimal(tokens.cache.write).mul(input.model.cost?.cache_write ?? 0).div(1_000_000))
408+
.add(
409+
new Decimal(tokens.cache.write).mul(input.model.cost?.cache_write ?? 0).div(1_000_000),
410+
)
406411
.toNumber(),
407412
tokens,
408413
}
@@ -423,22 +428,13 @@ export namespace Session {
423428
messageID: Identifier.schema("message"),
424429
}),
425430
async (input) => {
426-
await SessionPrompt.prompt({
431+
await SessionPrompt.command({
427432
sessionID: input.sessionID,
428433
messageID: input.messageID,
429-
model: {
430-
providerID: input.providerID,
431-
modelID: input.modelID,
432-
},
433-
parts: [
434-
{
435-
id: Identifier.ascending("part"),
436-
type: "text",
437-
text: PROMPT_INITIALIZE.replace("${path}", Instance.worktree),
438-
},
439-
],
434+
model: input.providerID + "/" + input.modelID,
435+
command: Command.Default.INIT,
436+
arguments: "",
440437
})
441-
await Project.setInitialized(Instance.project.id)
442438
},
443439
)
444440
}

packages/opencode/src/session/prompt.ts

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1593,6 +1593,7 @@ export namespace SessionPrompt {
15931593
let index = 0
15941594
template = template.replace(bashRegex, () => results[index++])
15951595
}
1596+
template = template.trim()
15961597

15971598
const parts = [
15981599
{
@@ -1657,6 +1658,8 @@ export namespace SessionPrompt {
16571658
})()
16581659

16591660
const agent = await Agent.get(agentName)
1661+
let result: MessageV2.WithParts
1662+
16601663
if ((agent.mode === "subagent" && command.subtask !== false) || command.subtask === true) {
16611664
using abort = lock(input.sessionID)
16621665

@@ -1732,7 +1735,7 @@ export namespace SessionPrompt {
17321735
}
17331736
await Session.updatePart(toolPart)
17341737

1735-
const result = await TaskTool.init().then((t) =>
1738+
const taskResult = await TaskTool.init().then((t) =>
17361739
t.execute(args, {
17371740
sessionID: input.sessionID,
17381741
abort: abort.signal,
@@ -1760,22 +1763,31 @@ export namespace SessionPrompt {
17601763
},
17611764
input: toolPart.state.input,
17621765
title: "",
1763-
metadata: result.metadata,
1764-
output: result.output,
1766+
metadata: taskResult.metadata,
1767+
output: taskResult.output,
17651768
}
17661769
await Session.updatePart(toolPart)
17671770
}
17681771

1769-
return { info: assistantMsg, parts: [toolPart] }
1772+
result = { info: assistantMsg, parts: [toolPart] }
1773+
} else {
1774+
result = await prompt({
1775+
sessionID: input.sessionID,
1776+
messageID: input.messageID,
1777+
model,
1778+
agent: agentName,
1779+
parts,
1780+
})
17701781
}
17711782

1772-
return prompt({
1783+
Bus.publish(Command.Event.Executed, {
1784+
name: input.command,
17731785
sessionID: input.sessionID,
1774-
messageID: input.messageID,
1775-
model,
1776-
agent: agentName,
1777-
parts,
1786+
arguments: input.arguments,
1787+
messageID: result.info.id,
17781788
})
1789+
1790+
return result
17791791
}
17801792

17811793
async function ensureTitle(input: {

0 commit comments

Comments
 (0)