@sjawhar/opencode-legion-envoy 0.7.0 → 0.9.0

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.
@@ -2,32 +2,38 @@ import { existsSync, readFileSync } from "node:fs";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { messageFor } from "@legion/envoy-client/errors";
5
- import { logger } from "../log";
6
5
  import { type EnvoyConfig, EnvoyConfigSchema } from "./schema";
7
6
 
8
7
  export interface LoadEnvoyConfigOptions {
9
8
  homeDir?: string;
10
9
  }
11
10
 
11
+ /** A present but unusable envoy.json. The plugin refuses to load rather than run with dispatch silently off. */
12
+ export class EnvoyConfigError extends Error {
13
+ readonly filePath: string;
14
+ constructor(filePath: string, detail: string) {
15
+ super(`[envoy-plugin] invalid config at ${filePath}: ${detail}`);
16
+ this.name = "EnvoyConfigError";
17
+ this.filePath = filePath;
18
+ }
19
+ }
20
+
12
21
  function readConfigFile(filePath: string): EnvoyConfig | null {
13
22
  if (!existsSync(filePath)) return null;
23
+ let raw: unknown;
14
24
  try {
15
- const content = readFileSync(filePath, "utf-8");
16
- const raw = JSON.parse(content) as unknown;
17
- const parsed = EnvoyConfigSchema.safeParse(raw);
18
- if (!parsed.success) {
19
- const issues = parsed.error.issues
20
- .map((issue) => `${issue.path.join(".")}: ${issue.message}`)
21
- .join(", ");
22
- logger.warn(`[envoy-plugin] Invalid config at ${filePath}: ${issues}`);
23
- return null;
24
- }
25
- return parsed.data as EnvoyConfig;
25
+ raw = JSON.parse(readFileSync(filePath, "utf-8")) as unknown;
26
26
  } catch (error) {
27
- const message = messageFor(error);
28
- logger.warn(`[envoy-plugin] Failed to load config at ${filePath}: ${message}`);
29
- return null;
27
+ throw new EnvoyConfigError(filePath, messageFor(error));
28
+ }
29
+ const parsed = EnvoyConfigSchema.safeParse(raw);
30
+ if (!parsed.success) {
31
+ const issues = parsed.error.issues
32
+ .map((issue) => `${issue.path.join(".")}: ${issue.message}`)
33
+ .join(", ");
34
+ throw new EnvoyConfigError(filePath, issues);
30
35
  }
36
+ return parsed.data;
31
37
  }
32
38
 
33
39
  function mergeConfig(base: EnvoyConfig, override: EnvoyConfig): EnvoyConfig {
@@ -6,11 +6,6 @@ export const DispatchConfigSchema = z
6
6
  .object({
7
7
  enabled: z.boolean().optional(),
8
8
  serverUrl: z.string().url().optional(),
9
- defaultRepo: z
10
- .string()
11
- .regex(/^[^/]+\/[^/]+$/)
12
- .optional(),
13
- appClientId: z.string().optional(),
14
9
  })
15
10
  .strict();
16
11
 
package/src/server.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import { existsSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
1
4
  import { agentSubject } from "@legion/contracts";
2
5
  import { envoyDefaultsFromEnvironment } from "@legion/envoy-client/defaults";
3
6
  import { dispatchSubscriptionTopic } from "@legion/envoy-client/dispatch-subscribe";
@@ -10,6 +13,18 @@ import { buildDispatchMcpEntry, injectEnvoyMcp } from "./dispatch-mcp";
10
13
  import { logger } from "./log";
11
14
  import { resolvePort } from "./port";
12
15
 
16
+ const moduleDirectory = path.dirname(fileURLToPath(import.meta.url));
17
+ // Legion skills ship inside this package so OpenCode carries them without a
18
+ // vendor checkout. OpenCode discovers skills only from fixed directories and
19
+ // `config.skills.paths` — never from plugin package dirs — so the config hook
20
+ // below must inject this path itself.
21
+ // Published tarball: dist/src/server.js → <pkg>/skills (staged at prepack).
22
+ // Repo checkout: src/server.ts → <repo>/skills.
23
+ const skillsDirectory = [
24
+ path.resolve(moduleDirectory, "../../skills"),
25
+ path.resolve(moduleDirectory, "../../../skills"),
26
+ ].find((dir) => existsSync(dir));
27
+
13
28
  const [
14
29
  subscribeSpec,
15
30
  unsubscribeSpec,
@@ -139,7 +154,20 @@ export default async (input: { serverUrl: URL }) => {
139
154
  });
140
155
 
141
156
  return {
142
- config: (cfg: { mcp?: Record<string, unknown> } & Record<string, unknown>) => {
157
+ config: (
158
+ cfg: { mcp?: Record<string, unknown>; skills?: { paths?: string[] } } & Record<
159
+ string,
160
+ unknown
161
+ >
162
+ ) => {
163
+ // Serve the bundled legion skills to every session on this serve.
164
+ if (skillsDirectory) {
165
+ cfg.skills ??= {};
166
+ cfg.skills.paths ??= [];
167
+ if (!cfg.skills.paths.includes(skillsDirectory)) {
168
+ cfg.skills.paths.push(skillsDirectory);
169
+ }
170
+ }
143
171
  // Inject the envoy MCP entry into the OpenCode config when
144
172
  // dispatch is enabled. Centralizing this in the plugin (instead of
145
173
  // each user's opencode.json) means: