echoes-vault-opencode 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/index.ts +12 -11
  2. package/package.json +1 -1
  3. package/runtime.ts +21 -2
  4. package/tui.tsx +1 -1
package/index.ts CHANGED
@@ -3,7 +3,7 @@ import { tool } from "@opencode-ai/plugin"
3
3
  import * as fs from "node:fs/promises"
4
4
  import * as path from "node:path"
5
5
  import { fileURLToPath } from "node:url"
6
- import { runEchoes } from "./runtime.ts"
6
+ import { resolveEchoesWorkspace, runEchoes } from "./runtime.ts"
7
7
 
8
8
  const PLUGIN_ROOT = path.dirname(fileURLToPath(import.meta.url))
9
9
 
@@ -48,7 +48,8 @@ const OpenCodeEchoes: Plugin = async ({ directory, worktree }) => {
48
48
 
49
49
  // Lifecycle tools are one-shot capabilities granted only by the matching explicit slash command.
50
50
  const authorizedStatusActions = new Map<string, StatusAction>()
51
- const fallbackWorkspace = worktree || directory
51
+ const workspaceFor = (contextDirectory?: string, contextWorktree?: string): string =>
52
+ resolveEchoesWorkspace(contextDirectory, contextWorktree, directory, worktree)
52
53
 
53
54
  const requireAuthorization = (sessionID: string, expected: StatusAction): void => {
54
55
  if (authorizedStatusActions.get(sessionID) !== expected) {
@@ -81,7 +82,7 @@ const OpenCodeEchoes: Plugin = async ({ directory, worktree }) => {
81
82
  async execute(_args, ctx) {
82
83
  requireAuthorization(ctx.sessionID, "init")
83
84
  const output = displayOutput(
84
- await runEchoes(ctx.worktree || ctx.directory || fallbackWorkspace, "init", {
85
+ await runEchoes(workspaceFor(ctx.directory, ctx.worktree), "init", {
85
86
  signal: ctx.abort,
86
87
  }),
87
88
  )
@@ -97,7 +98,7 @@ const OpenCodeEchoes: Plugin = async ({ directory, worktree }) => {
97
98
  async execute(_args, ctx) {
98
99
  requireAuthorization(ctx.sessionID, "start")
99
100
  const output = displayOutput(
100
- await runEchoes(ctx.worktree || ctx.directory || fallbackWorkspace, "start", {
101
+ await runEchoes(workspaceFor(ctx.directory, ctx.worktree), "start", {
101
102
  args: ["--recent", "3"],
102
103
  signal: ctx.abort,
103
104
  }),
@@ -113,7 +114,7 @@ const OpenCodeEchoes: Plugin = async ({ directory, worktree }) => {
113
114
  args: {},
114
115
  async execute(_args, ctx) {
115
116
  return displayOutput(
116
- await runEchoes(ctx.worktree || ctx.directory || fallbackWorkspace, "status", {
117
+ await runEchoes(workspaceFor(ctx.directory, ctx.worktree), "status", {
117
118
  args: ["--format", "card"],
118
119
  signal: ctx.abort,
119
120
  }),
@@ -148,7 +149,7 @@ const OpenCodeEchoes: Plugin = async ({ directory, worktree }) => {
148
149
  async execute(args, ctx) {
149
150
  requireAuthorization(ctx.sessionID, "end")
150
151
  const output = displayOutput(
151
- await runEchoes(ctx.worktree || ctx.directory || fallbackWorkspace, "end", {
152
+ await runEchoes(workspaceFor(ctx.directory, ctx.worktree), "end", {
152
153
  args: ["--confirm-explicit-user-end", "--payload", "-"],
153
154
  payload: {
154
155
  dailySummary: args.dailySummary,
@@ -173,7 +174,7 @@ const OpenCodeEchoes: Plugin = async ({ directory, worktree }) => {
173
174
  },
174
175
  async execute(args, ctx) {
175
176
  return displayOutput(
176
- await runEchoes(ctx.worktree || ctx.directory || fallbackWorkspace, "append", {
177
+ await runEchoes(workspaceFor(ctx.directory, ctx.worktree), "append", {
177
178
  args: ["--payload", "-"],
178
179
  payload: { entry: args.logEntry },
179
180
  signal: ctx.abort,
@@ -193,7 +194,7 @@ const OpenCodeEchoes: Plugin = async ({ directory, worktree }) => {
193
194
  const runtimeArgs = [args.query]
194
195
  if (args.limit !== undefined) runtimeArgs.push("--limit", String(args.limit))
195
196
  return displayOutput(
196
- await runEchoes(ctx.worktree || ctx.directory || fallbackWorkspace, "search", {
197
+ await runEchoes(workspaceFor(ctx.directory, ctx.worktree), "search", {
197
198
  args: runtimeArgs,
198
199
  signal: ctx.abort,
199
200
  }),
@@ -209,7 +210,7 @@ const OpenCodeEchoes: Plugin = async ({ directory, worktree }) => {
209
210
  },
210
211
  async execute(args, ctx) {
211
212
  return displayOutput(
212
- await runEchoes(ctx.worktree || ctx.directory || fallbackWorkspace, "hash", {
213
+ await runEchoes(workspaceFor(ctx.directory, ctx.worktree), "hash", {
213
214
  args: [args.filename],
214
215
  signal: ctx.abort,
215
216
  }),
@@ -233,7 +234,7 @@ const OpenCodeEchoes: Plugin = async ({ directory, worktree }) => {
233
234
  },
234
235
  async execute(args, ctx) {
235
236
  return displayOutput(
236
- await runEchoes(ctx.worktree || ctx.directory || fallbackWorkspace, "upsert", {
237
+ await runEchoes(workspaceFor(ctx.directory, ctx.worktree), "upsert", {
237
238
  args: ["--payload", "-"],
238
239
  payload: {
239
240
  filename: args.filename,
@@ -252,7 +253,7 @@ const OpenCodeEchoes: Plugin = async ({ directory, worktree }) => {
252
253
  args: {},
253
254
  async execute(_args, ctx) {
254
255
  return displayOutput(
255
- await runEchoes(ctx.worktree || ctx.directory || fallbackWorkspace, "hydrate", {
256
+ await runEchoes(workspaceFor(ctx.directory, ctx.worktree), "hydrate", {
256
257
  signal: ctx.abort,
257
258
  }),
258
259
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "echoes-vault-opencode",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "OpenCode adapter for the agent-neutral EchoesVault Protocol and repository-local Markdown memory runtime.",
5
5
  "type": "module",
6
6
  "main": "index.ts",
package/runtime.ts CHANGED
@@ -45,6 +45,24 @@ export class EchoesRuntimeError extends Error {
45
45
  }
46
46
  }
47
47
 
48
+ /**
49
+ * Prefer OpenCode's current session directory over its worktree hint. Some global-plugin launches
50
+ * report `/` as the worktree even when the session directory points at the real project.
51
+ */
52
+ export const resolveEchoesWorkspace = (...candidates: Array<string | undefined>): string => {
53
+ for (const candidate of candidates) {
54
+ if (!candidate?.trim()) continue
55
+ const resolved = path.resolve(candidate)
56
+ if (resolved !== path.parse(resolved).root) return resolved
57
+ }
58
+ throw new EchoesRuntimeError(
59
+ "OpenCode did not provide a safe project directory. Refusing to use the filesystem root as the EchoesVault workspace.",
60
+ null,
61
+ "",
62
+ "",
63
+ )
64
+ }
65
+
48
66
  let adapterVersionPromise: Promise<string> | undefined
49
67
 
50
68
  export const getAdapterVersion = async (): Promise<string> => {
@@ -81,6 +99,7 @@ export const runEchoes = async (
81
99
  command: EchoesCommand,
82
100
  options: RunEchoesOptions = {},
83
101
  ): Promise<string> => {
102
+ const resolvedWorkspace = resolveEchoesWorkspace(workspace)
84
103
  const adapterVersion = options.adapterVersion ?? (await getAdapterVersion())
85
104
  const launcher = options.launcherPath ?? BUNDLED_RUNTIME
86
105
  const commandArgs = options.args ?? []
@@ -90,7 +109,7 @@ export const runEchoes = async (
90
109
  const argv = [
91
110
  launcher,
92
111
  "--workspace",
93
- workspace,
112
+ resolvedWorkspace,
94
113
  "--agent",
95
114
  "opencode",
96
115
  "--adapter-version",
@@ -101,7 +120,7 @@ export const runEchoes = async (
101
120
 
102
121
  return await new Promise<string>((resolve, reject) => {
103
122
  const child = spawn(python, argv, {
104
- cwd: workspace,
123
+ cwd: resolvedWorkspace,
105
124
  stdio: [payload === undefined ? "ignore" : "pipe", "pipe", "pipe"],
106
125
  })
107
126
  const stdout: Buffer[] = []
package/tui.tsx CHANGED
@@ -50,7 +50,7 @@ const readSnapshot = (workspace: string): VaultSnapshot => ({
50
50
 
51
51
  const tui: TuiPlugin = async (api, _options, meta) => {
52
52
  const theme = api.theme.current
53
- const workspace = api.state.path.worktree || api.state.path.directory || process.cwd()
53
+ const workspace = api.state.path.directory || api.state.path.worktree || process.cwd()
54
54
 
55
55
  api.slots.register({
56
56
  slots: {