@sjawhar/opencode-legion-envoy 5.2.1 → 5.3.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.
@@ -13998,7 +13998,7 @@ var dispatchToolSpecs = [
13998
13998
  spec: z2.string().describe(`Optional initial primary-document markdown. ${SPEC_WRITING_POINTER}`).optional(),
13999
13999
  labels: z2.array(z2.string({ min: 1, max: 40 }), { max: 20 }).describe("Optional initial labels, at most 20 labels of up to 40 characters.").optional(),
14000
14000
  priority: z2.number({ int: true, min: 0, max: 3 }).describe("Optional coarse priority: P0 is highest and P3 is lowest.").optional(),
14001
- assignee: z2.string().describe("GitHub login of the human who answers this issue's asks; defaults to your owner when you act for a person, else the parent's assignee, else unassigned.").optional(),
14001
+ assignee: z2.string().describe("Email of the person who answers this issue's asks (someone who has signed in to Dispatch); defaults to your owner when you act for a person, else the parent's assignee, else unassigned.").optional(),
14002
14002
  components: componentsArgument(z2).optional()
14003
14003
  })
14004
14004
  },
@@ -14238,7 +14238,7 @@ var dispatchToolSpecs = [
14238
14238
  {
14239
14239
  name: "dispatch_doc_read",
14240
14240
  example: { issue: "DSP-1" },
14241
- description: "Read a live document or a named document version. Do not use it for issue status, asks, or events; " + "use dispatch_read instead. Supply ref, issue, or project plus artifact; issue plus an omitted artifact reads the primary document. " + "A live read returns its document token for an optional dispatch_doc_edit precondition; use /blocks for per-block tokens. " + OWNER_REFERENCE,
14241
+ description: "Read a live document or a named document version, or the text of an uploaded file at its latest or named version. " + "Do not use it for issue status, asks, or events; " + "use dispatch_read instead. Supply ref, issue, or project plus artifact; issue plus an omitted artifact reads the primary document. " + "A live read returns its document token for an optional dispatch_doc_edit precondition; use /blocks for per-block tokens. " + "A file that is not UTF-8 text is described, with the route that serves its bytes. " + OWNER_REFERENCE,
14242
14242
  arguments: (z2) => ({
14243
14243
  issue: z2.string().describe(ISSUE_REFERENCE).optional(),
14244
14244
  project: z2.string().describe("Project key owning the document.").optional(),
@@ -14352,7 +14352,7 @@ var dispatchToolSpecs = [
14352
14352
  {
14353
14353
  name: "dispatch_whoami",
14354
14354
  example: {},
14355
- description: "Who Dispatch takes this session for: {session, owner}. owner is the lowercase GitHub login of the human whose personal token you run under (the default assignee of issues you create), or null under the shared token.",
14355
+ description: "Who Dispatch takes this session for: {session, owner}. owner is the lowercase email of the person whose personal token you run under (the default assignee of issues you create), or null under the shared token.",
14356
14356
  arguments: () => ({}),
14357
14357
  strict: true
14358
14358
  }
@@ -15026,6 +15026,20 @@ class DispatchClient {
15026
15026
  async docRead(id, version2) {
15027
15027
  return version2 === undefined ? this.#json("GET", ["api", "v1", "artifacts", id, "text"]) : this.#json("GET", ["api", "v1", "artifacts", id, "versions", String(version2)]);
15028
15028
  }
15029
+ async fileVersion(id, version2) {
15030
+ const url2 = this.#url(["api", "v1", "artifacts", id, "versions", String(version2)]);
15031
+ const response = await this.fetchImpl(url2, {
15032
+ method: "GET",
15033
+ headers: { Accept: "*/*", Authorization: `Bearer ${this.token}` },
15034
+ signal: this.#signal
15035
+ });
15036
+ if (!response.ok)
15037
+ return this.#response("GET", url2, response);
15038
+ return {
15039
+ mime: response.headers.get("Content-Type") ?? "application/octet-stream",
15040
+ bytes: new Uint8Array(await response.arrayBuffer())
15041
+ };
15042
+ }
15029
15043
  async artifactBlocks(id) {
15030
15044
  return this.#json("GET", ["api", "v1", "artifacts", id, "blocks"]);
15031
15045
  }
@@ -16308,6 +16322,30 @@ async function blockAsks(client, resolved, state) {
16308
16322
  const asks = await (resolved.issue === undefined ? client.getArtifactAsks(resolved.artifact.id, state) : client.listIssueAsks(resolved.issue.key, state));
16309
16323
  return asks.filter((ask) => typeof ask.block_id === "string" && ask.block_artifact?.id === resolved.artifact.id);
16310
16324
  }
16325
+ async function readUploadedFile(client, resolved, requested) {
16326
+ const { artifact } = resolved;
16327
+ const latest = Math.max(0, ...artifact.versions.map((version2) => version2.number));
16328
+ const number4 = requested ?? latest;
16329
+ const file2 = await client.fileVersion(artifact.id, number4);
16330
+ const of = number4 === latest ? "" : ` of ${latest}`;
16331
+ const size = `${file2.bytes.length.toLocaleString("en-US")} bytes`;
16332
+ const details = resolved.owner.kind === "project" ? { project: artifact.project, document: documentLabel(artifact.project, artifact.slug) } : { issue: resolved.issue?.key };
16333
+ let text;
16334
+ try {
16335
+ text = new TextDecoder("utf-8", { fatal: true }).decode(file2.bytes);
16336
+ } catch {
16337
+ return {
16338
+ text: `${artifact.name} is an uploaded ${file2.mime} file (version ${number4}${of}, ${size}) that is not ` + `UTF-8 text, so dispatch_doc_read cannot show it. GET /api/v1/artifacts/${artifact.id}/versions/${number4} serves its bytes.`,
16339
+ details
16340
+ };
16341
+ }
16342
+ return {
16343
+ text: `File ${artifact.name}: ${file2.mime}, version ${number4}${of}, ${size}.
16344
+
16345
+ ${text}`,
16346
+ details
16347
+ };
16348
+ }
16311
16349
  async function refuseOpenDecisionBlocks(client, tool, resolved) {
16312
16350
  const artifact = resolved.artifact;
16313
16351
  const latest = artifact.approval?.latest_version;
@@ -17005,6 +17043,9 @@ ${followsAsk(askOwner)}`,
17005
17043
  const artifactReference = optionalString(args, "artifact") ?? (ownerArguments.ref?.kind === "spec" || ownerArguments.ref?.kind === "artifact" ? ownerArguments.ref.id : undefined);
17006
17044
  const resolved = await resolveDocument(documentOwner(), artifactReference);
17007
17045
  const version2 = optionalNumber(args, "version") ?? ownerArguments.ref?.version;
17046
+ if (resolved.artifact.kind === "file" || resolved.artifact.kind === "image") {
17047
+ return readUploadedFile(client, resolved, version2);
17048
+ }
17008
17049
  const documentPromise = client.docRead(resolved.artifact.id, version2);
17009
17050
  const marksPromise = openArtifactMarks(client, resolved);
17010
17051
  const marksResultPromise = marksPromise.then((value) => ({ status: "fulfilled", value }), (reason) => ({ status: "rejected", reason }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/opencode-legion-envoy",
3
- "version": "5.2.1",
3
+ "version": "5.3.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "main": "dist/src/server.js",
@@ -156,7 +156,7 @@ Create an issue for newly tracked work with:
156
156
  ```ts
157
157
  dispatch_issue({ project, title, parent?, external?, spec?, force?, labels?: string[], priority?: 0 | 1 | 2 | 3, assignee?: string })
158
158
  ```
159
- `labels` are optional initial labels: Dispatch trims them, preserves their case, and removes case-insensitive duplicates. `priority` is yours on creation too — see [Priority is yours to set](skill://dispatch/references/issues.md). Set `assignee` (a GitHub login on the sign-in allowlist) only when the human said who owns the work; otherwise the default above applies, so a child inherits its parent's assignee. It returns
159
+ `labels` are optional initial labels: Dispatch trims them, preserves their case, and removes case-insensitive duplicates. `priority` is yours on creation too — see [Priority is yours to set](skill://dispatch/references/issues.md). Set `assignee` (the email of a person who has signed in to Dispatch) only when the human said who owns the work; otherwise the default above applies, so a child inherits its parent's assignee. It returns
160
160
  `details` `{ issue }`; creating an issue does not subscribe you to it (see [Following](#following)). Use `dispatch_issue` only to create an issue; never use it to park a question. When `spec` is supplied,
161
161
  follow [Writing a spec](#writing-a-spec).
162
162
 
@@ -418,10 +418,10 @@ Read the current document before changing it:
418
418
  ```ts
419
419
  dispatch_doc_read({ issue?, project?, artifact?, version?, ref? })
420
420
  ```
421
- It returns live or versioned markdown with open marks. A live read ends with a document token; `issue` with an
422
- omitted `artifact` reads the issue specification; a project needs `artifact`; and a
423
- `dispatch://PROJECT/artifact/<document-ref>` ref supplies both, where `document-ref` is the slug (an id or a
424
- filename resolves when no document has that slug).
421
+ It returns live or versioned markdown with open marks. A live read ends with a document token; `issue` with an omitted
422
+ `artifact` reads the issue specification; a project needs `artifact`; and a `dispatch://PROJECT/artifact/<document-ref>`
423
+ ref supplies both, where `document-ref` is the slug (an id or a filename resolves when no document has that slug). A file
424
+ `dispatch_artifact` uploaded reads its text at the latest or named version, or a description when it is not UTF-8 text.
425
425
 
426
426
  Editing one is [Editing a document](skill://dispatch/references/document-edits.md): the shape of `dispatch_doc_edit`,
427
427
  how to quote the text you mean, one `replace` per paragraph, preconditions against a stale edit, and
@@ -12,9 +12,10 @@ is the shared devbox fallback; do not configure it for an individual agent.
12
12
 
13
13
  The deployed Dispatch server's browser origin is configured separately with
14
14
  `DISPATCH_SERVER_URL` in the deployment `compose/.env`. Do not change an
15
- agent's `envoy.json` to set the GitHub OAuth callback origin: the value must
16
- be the exact URL humans type in their browser, and the GitHub App callback is
17
- `<DISPATCH_SERVER_URL>/auth/callback`.
15
+ agent's `envoy.json` to set the sign-in callback origin: the value must be the
16
+ exact URL humans type in their browser, and `<DISPATCH_SERVER_URL>/auth/callback`
17
+ is the callback registered on the sign-in pool's app client
18
+ (`DISPATCH_SIGNIN_CLIENT_ID`).
18
19
 
19
20
  ### Finding a route
20
21
 
@@ -11,7 +11,7 @@ An ask goes to the issue's assignee: their Inbox opens on **Mine**, which lists
11
11
  ```ts
12
12
  dispatch_whoami({})
13
13
  ```
14
- It returns `details` `{ session, owner, service }`: `owner` is the lowercase login of the human whose personal token you run under, or `null` under the shared token or a verified service token, in which case `service` is that token's subject (`system:serviceaccount:<namespace>:<name>`, `null` otherwise) and every write you make is rendered `(as <namespace>/<name>)` — the namespace is kept because every namespace has a `default` service account, and a subject that is not a Kubernetes one is shown whole. An issue you create without `assignee` goes to your owner; with no owner it inherits its parent's assignee, or stays unassigned without a parent. If an issue you are asking on is unassigned and the answer matters, assign it to your owner (`PATCH /api/v1/issues/{key}` with `{"assignee": "<login>"}`; any authenticated caller may reassign, and an unlisted login is refused with `ASSIGNEE_NOT_ALLOWED`) or name in the question who should answer it. Never reassign an issue a human holds to get an answer faster: that is the human's call.
14
+ It returns `details` `{ session, owner, service }`: `owner` is the lowercase email of the person whose personal token you run under, or `null` under the shared token or a verified service token, in which case `service` is that token's subject (`system:serviceaccount:<namespace>:<name>`, `null` otherwise) and every write you make is rendered `(as <namespace>/<name>)` — the namespace is kept because every namespace has a `default` service account, and a subject that is not a Kubernetes one is shown whole. An issue you create without `assignee` goes to your owner; with no owner it inherits its parent's assignee, or stays unassigned without a parent. If an issue you are asking on is unassigned and the answer matters, assign it to your owner (`PATCH /api/v1/issues/{key}` with `{"assignee": "<email>"}`; any authenticated caller may reassign, and an email nobody has signed in with is refused with `ASSIGNEE_NOT_ALLOWED`) or name in the question who should answer it. Never reassign an issue a human holds to get an answer faster: that is the human's call.
15
15
 
16
16
  ## Handing a human a to-do, editing, resolving, and replying
17
17
 
@@ -126,7 +126,7 @@ reorder it the way [Issue reads](#board-rank-priority-and-assignee-on-a-read) de
126
126
 
127
127
  ## Board rank, priority, and assignee on a read
128
128
 
129
- Issue reads include `rank`, the server-owned ordering key used by project boards; reorder through `PATCH /api/v1/issues/{key}` with `{"rank": {"before": "<key>", "after": "<key>"}}`, either neighbor optional and both in the issue's project. A bearer caller also names its own session in that body, `"actor": {"kind": "session", "id": "<your session id>"}`, or the server refuses with `ACTOR_KIND`. They also include nullable coarse priority (`P0` highest through `P3` lowest) and `assignee`: the lowercase GitHub login of the human who answers the issue's asks, or `null` when nobody holds it. `dispatch_read` of an issue prints it as `Assignee: <login>` or `Assignee: unassigned`.
129
+ Issue reads include `rank`, the server-owned ordering key used by project boards; reorder through `PATCH /api/v1/issues/{key}` with `{"rank": {"before": "<key>", "after": "<key>"}}`, either neighbor optional and both in the issue's project. A bearer caller also names its own session in that body, `"actor": {"kind": "session", "id": "<your session id>"}`, or the server refuses with `ACTOR_KIND`. They also include nullable coarse priority (`P0` highest through `P3` lowest) and `assignee`: the lowercase email of the person who answers the issue's asks, or `null` when nobody holds it. `dispatch_read` of an issue prints it as `Assignee: <email>` or `Assignee: unassigned`.
130
130
 
131
131
  ## Reading a project's backlog
132
132