@sjawhar/pi-legion-envoy 0.19.0 → 0.20.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.
package/README.md CHANGED
@@ -118,6 +118,10 @@ The shared contract supplies the model-facing schemas and descriptions. The
118
118
  `dispatch` skill describes when to use each operation for issues, asks, review
119
119
  feedback, documents, artifacts, and status reads.
120
120
 
121
+ `dispatch_artifact` accepts exactly one upload source: a local `path`, or inline `content`.
122
+ For example, an architect can post its primary specification with
123
+ `{ issue, name: "spec.md", content: "# Design", primary: true }`.
124
+
121
125
  Lifecycle and scope decisions between Legion roles go through `envoy_publish` to the owning
122
126
  architect's role topic; Dispatch is for durable questions to the human and the shared
123
127
  document, not for coordination between roles.
package/dist/envoy.js CHANGED
@@ -29687,6 +29687,10 @@ var ChildStatusEventPayloadSchema = object({
29687
29687
  to: string2().optional()
29688
29688
  });
29689
29689
  // ../contracts/src/dispatch-tools.ts
29690
+ function dispatchToolSchema(spec, z, opts) {
29691
+ const shape = spec.arguments(z);
29692
+ return spec.validation === undefined ? z.object(shape, opts) : z.refineObject(shape, spec.validation.check, spec.validation.message, opts);
29693
+ }
29690
29694
  var ISSUE_REFERENCE = "An issue is a native KEY or external owner/repo#n reference; an external reference creates its native issue on first use in the repository's mapped project (DISPATCH_REPO_PROJECTS) or, failing that, the default project (DISPATCH_DEFAULT_PROJECT).";
29691
29695
  var ASK_URGENCIES = ["low", "med", "high", "blocking"];
29692
29696
  var DOC_EDIT_OPS = ["replace", "delete", "insert"];
@@ -29783,14 +29787,22 @@ var dispatchToolSpecs = [
29783
29787
  },
29784
29788
  {
29785
29789
  name: "dispatch_artifact",
29786
- description: "Upload a local file as an issue artifact. Do not use it to edit a live document; use dispatch_doc_edit " + `instead. Files are limited to 25 MiB. ${ISSUE_REFERENCE}`,
29790
+ description: "Attach a local file or inline text as an issue artifact. Do not use it to edit a live document; use " + `dispatch_doc_edit instead. Exactly one of path or content is required; artifacts are limited to 25 MiB. ${ISSUE_REFERENCE}`,
29787
29791
  arguments: (z) => ({
29788
29792
  issue: z.string().describe(ISSUE_REFERENCE),
29789
29793
  name: z.string().describe("Artifact filename shown in Dispatch."),
29790
- path: z.string().describe("Local path to the file to upload."),
29794
+ path: z.string().describe("Local path to the file to upload.").optional(),
29795
+ content: z.string().describe("Inline text to store as a Markdown document.").optional(),
29791
29796
  primary: z.boolean().describe("Make this document the issue primary artifact.").optional(),
29792
29797
  summary: z.string().describe("Optional version summary.").optional()
29793
- })
29798
+ }),
29799
+ validation: {
29800
+ check: (value) => {
29801
+ const input = value;
29802
+ return typeof input.path === "string" !== (typeof input.content === "string");
29803
+ },
29804
+ message: "Exactly one of path or content is required."
29805
+ }
29794
29806
  },
29795
29807
  {
29796
29808
  name: "dispatch_read",
@@ -30198,7 +30210,18 @@ function zodSchemaApi(zod) {
30198
30210
  schema = schema.max(opts.max);
30199
30211
  return schema;
30200
30212
  },
30201
- object: (shape) => api.object(shape)
30213
+ object: (shape, opts = {}) => {
30214
+ let schema = api.object(shape);
30215
+ if (opts.strict)
30216
+ schema = schema.strict();
30217
+ return schema;
30218
+ },
30219
+ refineObject: (shape, check, message, opts = {}) => {
30220
+ let schema = api.object(shape);
30221
+ if (opts.strict)
30222
+ schema = schema.strict();
30223
+ return schema.refine(check, message);
30224
+ }
30202
30225
  };
30203
30226
  }
30204
30227
  // ../envoy-client/src/defaults.ts
@@ -30943,9 +30966,6 @@ var DEFAULT_SERVER_URL = "http://localhost:8766";
30943
30966
  function normalizeDispatchUrl(url2) {
30944
30967
  return url2.replace(/\/+$/, "");
30945
30968
  }
30946
- function deprecatedMcpUrl(url2) {
30947
- return normalizeDispatchUrl(url2).replace(/\/mcp$/, "");
30948
- }
30949
30969
  function parsedDispatchUrl(value, source) {
30950
30970
  const url2 = normalizeDispatchUrl(value);
30951
30971
  try {
@@ -30995,7 +31015,6 @@ function readEnvoyFile(filePath) {
30995
31015
  }
30996
31016
  function resolveDispatchConfig(env, options = {}) {
30997
31017
  const explicitUrl = env.DISPATCH_URL;
30998
- const deprecatedUrl = explicitUrl ? undefined : env.DISPATCH_MCP_URL;
30999
31018
  const home = options.home ?? env.HOME ?? homedir();
31000
31019
  const cwd = options.cwd ?? process.cwd();
31001
31020
  const userFile = readEnvoyFile(path.join(home, ".config", "opencode", "envoy.json"));
@@ -31009,7 +31028,7 @@ function resolveDispatchConfig(env, options = {}) {
31009
31028
  ...userFile.kind === "valid" ? userFile.settings : null,
31010
31029
  ...repoFile.kind === "valid" ? repoFile.settings : null
31011
31030
  };
31012
- const rawUrl = explicitUrl !== undefined ? { value: explicitUrl, source: "DISPATCH_URL" } : deprecatedUrl !== undefined ? { value: deprecatedMcpUrl(deprecatedUrl), source: "DISPATCH_MCP_URL" } : merged.enabled === true ? { value: merged.serverUrl ?? DEFAULT_SERVER_URL, source: "dispatch.serverUrl" } : null;
31031
+ const rawUrl = explicitUrl !== undefined ? { value: explicitUrl, source: "DISPATCH_URL" } : merged.enabled === true ? { value: merged.serverUrl ?? DEFAULT_SERVER_URL, source: "dispatch.serverUrl" } : null;
31013
31032
  const url2 = rawUrl ? parsedDispatchUrl(rawUrl.value, rawUrl.source) : { url: null, error: null };
31014
31033
  const token = env.DISPATCH_TOKEN ?? merged.token ?? null;
31015
31034
  const tokenSource = env.DISPATCH_TOKEN === undefined ? "dispatch.token" : "DISPATCH_TOKEN";
@@ -31176,6 +31195,9 @@ class DispatchClient {
31176
31195
  return this.#json("POST", ["api", "v1", "issues", await this.#resolveIssue(issue), "messages"], input);
31177
31196
  }
31178
31197
  async artifact(issue, input) {
31198
+ const artifactPath = ["api", "v1", "issues", await this.#resolveIssue(issue), "artifacts"];
31199
+ if ("content" in input)
31200
+ return this.#json("POST", artifactPath, input);
31179
31201
  const form = new FormData;
31180
31202
  form.set("name", input.name);
31181
31203
  if (input.primary !== undefined)
@@ -31185,7 +31207,7 @@ class DispatchClient {
31185
31207
  if (input.actor !== undefined)
31186
31208
  form.set("actor", JSON.stringify(input.actor));
31187
31209
  form.set("file", input.file, input.name);
31188
- return this.#form("POST", ["api", "v1", "issues", await this.#resolveIssue(issue), "artifacts"], form);
31210
+ return this.#form("POST", artifactPath, form);
31189
31211
  }
31190
31212
  async getArtifact(id) {
31191
31213
  return this.#json("GET", ["api", "v1", "artifacts", id]);
@@ -31370,7 +31392,7 @@ function toolSchema(tool) {
31370
31392
  const spec = dispatchToolSpecs.find((candidate) => candidate.name === tool);
31371
31393
  if (!spec)
31372
31394
  throw new Error(`Unknown Dispatch tool: ${tool}`);
31373
- return object(spec.arguments(zodSchemaApi(exports_external))).strict();
31395
+ return dispatchToolSchema(spec, zodSchemaApi(exports_external), { strict: true });
31374
31396
  }
31375
31397
  async function resolveIssueArguments(tool, args, cwd, env, exec) {
31376
31398
  if (tool === "dispatch_issue")
@@ -31652,12 +31674,20 @@ Open anchored asks/comments: ${marks.join(", ")}`,
31652
31674
  case "dispatch_artifact": {
31653
31675
  const primary = optionalBoolean(args, "primary");
31654
31676
  const summary = optionalString(args, "summary");
31655
- const result = await client.artifact(issue(), {
31656
- name: stringArg(args, "name"),
31677
+ const name = stringArg(args, "name");
31678
+ const content = optionalString(args, "content");
31679
+ const result = await client.artifact(issue(), content === undefined ? {
31680
+ name,
31657
31681
  file: Bun.file(resolvePath(input.cwd, stringArg(args, "path"))),
31658
31682
  ...primary === undefined ? {} : { primary },
31659
31683
  ...summary === undefined ? {} : { summary },
31660
31684
  actor
31685
+ } : {
31686
+ name,
31687
+ content,
31688
+ ...primary === undefined ? {} : { primary },
31689
+ ...summary === undefined ? {} : { summary },
31690
+ actor
31661
31691
  });
31662
31692
  return {
31663
31693
  text: `Uploaded ${result.artifact.name} as version ${result.version.number}`,
@@ -32570,7 +32600,7 @@ function envoyExtension(pi) {
32570
32600
  name: spec.name,
32571
32601
  label: spec.name,
32572
32602
  description: spec.description,
32573
- parameters: pi.zod.object(spec.arguments(zodSchemaApi(pi.zod))),
32603
+ parameters: dispatchToolSchema(spec, zodSchemaApi(pi.zod)),
32574
32604
  execute: async (_id, params, _signal, _onUpdate, context) => {
32575
32605
  try {
32576
32606
  const result = await executeDispatchTool({
package/dist/legion.js CHANGED
@@ -29685,6 +29685,10 @@ var ChildStatusEventPayloadSchema = object({
29685
29685
  to: string2().optional()
29686
29686
  });
29687
29687
  // ../contracts/src/dispatch-tools.ts
29688
+ function dispatchToolSchema(spec, z, opts) {
29689
+ const shape = spec.arguments(z);
29690
+ return spec.validation === undefined ? z.object(shape, opts) : z.refineObject(shape, spec.validation.check, spec.validation.message, opts);
29691
+ }
29688
29692
  var ISSUE_REFERENCE = "An issue is a native KEY or external owner/repo#n reference; an external reference creates its native issue on first use in the repository's mapped project (DISPATCH_REPO_PROJECTS) or, failing that, the default project (DISPATCH_DEFAULT_PROJECT).";
29689
29693
  var ASK_URGENCIES = ["low", "med", "high", "blocking"];
29690
29694
  var DOC_EDIT_OPS = ["replace", "delete", "insert"];
@@ -29781,14 +29785,22 @@ var dispatchToolSpecs = [
29781
29785
  },
29782
29786
  {
29783
29787
  name: "dispatch_artifact",
29784
- description: "Upload a local file as an issue artifact. Do not use it to edit a live document; use dispatch_doc_edit " + `instead. Files are limited to 25 MiB. ${ISSUE_REFERENCE}`,
29788
+ description: "Attach a local file or inline text as an issue artifact. Do not use it to edit a live document; use " + `dispatch_doc_edit instead. Exactly one of path or content is required; artifacts are limited to 25 MiB. ${ISSUE_REFERENCE}`,
29785
29789
  arguments: (z) => ({
29786
29790
  issue: z.string().describe(ISSUE_REFERENCE),
29787
29791
  name: z.string().describe("Artifact filename shown in Dispatch."),
29788
- path: z.string().describe("Local path to the file to upload."),
29792
+ path: z.string().describe("Local path to the file to upload.").optional(),
29793
+ content: z.string().describe("Inline text to store as a Markdown document.").optional(),
29789
29794
  primary: z.boolean().describe("Make this document the issue primary artifact.").optional(),
29790
29795
  summary: z.string().describe("Optional version summary.").optional()
29791
- })
29796
+ }),
29797
+ validation: {
29798
+ check: (value) => {
29799
+ const input = value;
29800
+ return typeof input.path === "string" !== (typeof input.content === "string");
29801
+ },
29802
+ message: "Exactly one of path or content is required."
29803
+ }
29792
29804
  },
29793
29805
  {
29794
29806
  name: "dispatch_read",
@@ -30196,7 +30208,18 @@ function zodSchemaApi(zod) {
30196
30208
  schema = schema.max(opts.max);
30197
30209
  return schema;
30198
30210
  },
30199
- object: (shape) => api.object(shape)
30211
+ object: (shape, opts = {}) => {
30212
+ let schema = api.object(shape);
30213
+ if (opts.strict)
30214
+ schema = schema.strict();
30215
+ return schema;
30216
+ },
30217
+ refineObject: (shape, check, message, opts = {}) => {
30218
+ let schema = api.object(shape);
30219
+ if (opts.strict)
30220
+ schema = schema.strict();
30221
+ return schema.refine(check, message);
30222
+ }
30200
30223
  };
30201
30224
  }
30202
30225
  // ../envoy-client/src/defaults.ts
@@ -31428,9 +31451,6 @@ var DEFAULT_SERVER_URL = "http://localhost:8766";
31428
31451
  function normalizeDispatchUrl(url2) {
31429
31452
  return url2.replace(/\/+$/, "");
31430
31453
  }
31431
- function deprecatedMcpUrl(url2) {
31432
- return normalizeDispatchUrl(url2).replace(/\/mcp$/, "");
31433
- }
31434
31454
  function parsedDispatchUrl(value, source) {
31435
31455
  const url2 = normalizeDispatchUrl(value);
31436
31456
  try {
@@ -31480,7 +31500,6 @@ function readEnvoyFile(filePath) {
31480
31500
  }
31481
31501
  function resolveDispatchConfig(env, options = {}) {
31482
31502
  const explicitUrl = env.DISPATCH_URL;
31483
- const deprecatedUrl = explicitUrl ? undefined : env.DISPATCH_MCP_URL;
31484
31503
  const home = options.home ?? env.HOME ?? homedir();
31485
31504
  const cwd = options.cwd ?? process.cwd();
31486
31505
  const userFile = readEnvoyFile(path3.join(home, ".config", "opencode", "envoy.json"));
@@ -31494,7 +31513,7 @@ function resolveDispatchConfig(env, options = {}) {
31494
31513
  ...userFile.kind === "valid" ? userFile.settings : null,
31495
31514
  ...repoFile.kind === "valid" ? repoFile.settings : null
31496
31515
  };
31497
- const rawUrl = explicitUrl !== undefined ? { value: explicitUrl, source: "DISPATCH_URL" } : deprecatedUrl !== undefined ? { value: deprecatedMcpUrl(deprecatedUrl), source: "DISPATCH_MCP_URL" } : merged.enabled === true ? { value: merged.serverUrl ?? DEFAULT_SERVER_URL, source: "dispatch.serverUrl" } : null;
31516
+ const rawUrl = explicitUrl !== undefined ? { value: explicitUrl, source: "DISPATCH_URL" } : merged.enabled === true ? { value: merged.serverUrl ?? DEFAULT_SERVER_URL, source: "dispatch.serverUrl" } : null;
31498
31517
  const url2 = rawUrl ? parsedDispatchUrl(rawUrl.value, rawUrl.source) : { url: null, error: null };
31499
31518
  const token = env.DISPATCH_TOKEN ?? merged.token ?? null;
31500
31519
  const tokenSource = env.DISPATCH_TOKEN === undefined ? "dispatch.token" : "DISPATCH_TOKEN";
@@ -31661,6 +31680,9 @@ class DispatchClient {
31661
31680
  return this.#json("POST", ["api", "v1", "issues", await this.#resolveIssue(issue), "messages"], input);
31662
31681
  }
31663
31682
  async artifact(issue, input) {
31683
+ const artifactPath = ["api", "v1", "issues", await this.#resolveIssue(issue), "artifacts"];
31684
+ if ("content" in input)
31685
+ return this.#json("POST", artifactPath, input);
31664
31686
  const form = new FormData;
31665
31687
  form.set("name", input.name);
31666
31688
  if (input.primary !== undefined)
@@ -31670,7 +31692,7 @@ class DispatchClient {
31670
31692
  if (input.actor !== undefined)
31671
31693
  form.set("actor", JSON.stringify(input.actor));
31672
31694
  form.set("file", input.file, input.name);
31673
- return this.#form("POST", ["api", "v1", "issues", await this.#resolveIssue(issue), "artifacts"], form);
31695
+ return this.#form("POST", artifactPath, form);
31674
31696
  }
31675
31697
  async getArtifact(id) {
31676
31698
  return this.#json("GET", ["api", "v1", "artifacts", id]);
@@ -31855,7 +31877,7 @@ function toolSchema(tool) {
31855
31877
  const spec = dispatchToolSpecs.find((candidate) => candidate.name === tool);
31856
31878
  if (!spec)
31857
31879
  throw new Error(`Unknown Dispatch tool: ${tool}`);
31858
- return object(spec.arguments(zodSchemaApi(exports_external))).strict();
31880
+ return dispatchToolSchema(spec, zodSchemaApi(exports_external), { strict: true });
31859
31881
  }
31860
31882
  async function resolveIssueArguments(tool, args, cwd, env, exec) {
31861
31883
  if (tool === "dispatch_issue")
@@ -32137,12 +32159,20 @@ Open anchored asks/comments: ${marks.join(", ")}`,
32137
32159
  case "dispatch_artifact": {
32138
32160
  const primary = optionalBoolean(args, "primary");
32139
32161
  const summary = optionalString(args, "summary");
32140
- const result = await client.artifact(issue(), {
32141
- name: stringArg(args, "name"),
32162
+ const name = stringArg(args, "name");
32163
+ const content = optionalString(args, "content");
32164
+ const result = await client.artifact(issue(), content === undefined ? {
32165
+ name,
32142
32166
  file: Bun.file(resolvePath(input.cwd, stringArg(args, "path"))),
32143
32167
  ...primary === undefined ? {} : { primary },
32144
32168
  ...summary === undefined ? {} : { summary },
32145
32169
  actor
32170
+ } : {
32171
+ name,
32172
+ content,
32173
+ ...primary === undefined ? {} : { primary },
32174
+ ...summary === undefined ? {} : { summary },
32175
+ actor
32146
32176
  });
32147
32177
  return {
32148
32178
  text: `Uploaded ${result.artifact.name} as version ${result.version.number}`,
@@ -33047,7 +33077,7 @@ function envoyExtension(pi) {
33047
33077
  name: spec.name,
33048
33078
  label: spec.name,
33049
33079
  description: spec.description,
33050
- parameters: pi.zod.object(spec.arguments(zodSchemaApi(pi.zod))),
33080
+ parameters: dispatchToolSchema(spec, zodSchemaApi(pi.zod)),
33051
33081
  execute: async (_id, params, _signal, _onUpdate, context) => {
33052
33082
  try {
33053
33083
  const result = await executeDispatchTool({
@@ -105,15 +105,22 @@ invalid actor, route, or primary artifact.
105
105
 
106
106
  ## Artifacts
107
107
 
108
- Attach an image, diagram, or file with:
108
+ Attach an image, diagram, or local file with:
109
109
 
110
110
  ```ts
111
111
  dispatch_artifact({ issue, name, path, primary?, summary? })
112
112
  ```
113
113
 
114
- It returns `details` `{ issue, topic, artifact, version }`. `path` is the local file to upload.
115
- Uploading the same `name` creates its next version. Set `primary: true` only for a markdown file
116
- to make it the issue's spec.
114
+ Or, when the text is already in the call, post a Markdown document directly:
115
+
116
+ ```ts
117
+ dispatch_artifact({ issue, name: "spec.md", content: "# Design\n...", primary: true })
118
+ ```
119
+
120
+ Exactly one of `path` and `content` is required. The inline form sends JSON with
121
+ `Content-Type: application/json`. It returns `details` `{ issue, topic, artifact, version }`.
122
+ Uploading the same `name` creates its next version. Use `content` for an architect's primary spec;
123
+ set `primary: true` only for Markdown documents.
117
124
 
118
125
  ## Messages
119
126
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/pi-legion-envoy",
3
- "version": "0.19.0",
3
+ "version": "0.20.1",
4
4
  "type": "module",
5
5
  "omp": {
6
6
  "extensions": [