@sjawhar/claude-envoy 1.16.0 → 1.18.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,5 +2,5 @@
2
2
  "name": "claude-envoy",
3
3
  "description": "Deliver Envoy events as Claude Code channel notifications and expose Envoy MCP tools.",
4
4
  "mcpServers": "./.mcp.json",
5
- "version": "1.16.0"
5
+ "version": "1.18.0"
6
6
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-envoy",
3
- "version": "1.16.0",
3
+ "version": "1.18.0",
4
4
  "description": "Deliver Envoy events as Claude Code channel notifications and expose Envoy MCP tools.",
5
5
  "mcpServers": {}
6
6
  }
package/dist/dispatch.js CHANGED
@@ -15111,7 +15111,7 @@ var commentValidation = {
15111
15111
  refusal: (value) => commentProblem(value) ?? `${commentOwner.message} ${COMMENT_TURN_RULE}`
15112
15112
  };
15113
15113
  var ISSUE_UPDATE_FIELDS_RULE = "Issue update requires at least one field besides issue: status, title, labels, priority, external_links, route, parent, blocked_by, components, repo, before, or after.";
15114
- var ISSUE_UPDATE_DONE_RULE = "status done requires reason, a non-empty note saying why the issue is closing, posted on the issue before it closes because a closed issue refuses messages, comments, and artifacts";
15114
+ var ISSUE_UPDATE_DONE_RULE = "status done requires reason, a non-empty note saying why the issue is closing, posted on the issue before it closes so the close carries its note";
15115
15115
  var ISSUE_UPDATE_REASON_RULE = "reason goes only with status done";
15116
15116
  var ISSUE_UPDATE_RULE = `${ISSUE_UPDATE_FIELDS_RULE} ${ISSUE_UPDATE_DONE_RULE}; ${ISSUE_UPDATE_REASON_RULE}.`;
15117
15117
  function issueUpdateProblem(value) {
@@ -15256,7 +15256,7 @@ var dispatchToolSpecs = [
15256
15256
  priority: 1,
15257
15257
  before: "DSP-2"
15258
15258
  },
15259
- description: "Update an existing issue: move its lifecycle status, retitle it, replace its labels, set " + "its priority, link a URL (the pull request that delivers it, a run, a document), set its " + "route, set or clear its parent, replace the issues it is blocked by, attach it to " + "architecture components, set the repository it targets, or move it on its project's " + "board. Status is one of " + `${ISSUE_STATUSES.join(", ")}; outside Legion, move it yourself as the work advances; inside ` + "Legion the daemon moves it. A non-empty external_links is " + "merged into the issue's existing links by URL, so linking the pull request you just opened " + "keeps every earlier link; an empty external_links removes every link instead. components " + "replaces the issue's own attachment. Closing an issue " + "(status done) requires reason, the note that says why: it is posted on the issue as a " + "message, then the issue closes, because a closed issue refuses messages, comments, and " + "artifacts; reason goes only with status done. Closing an issue withdraws every open ask " + "it holds (resolution kind withdrawn, naming the closer), and reopening it brings exactly " + "those asks back. A closed issue takes only a board move " + "(before, after), components, " + "a reopening status (any status but done), and an external_links write that adds no new " + "link (so a link can move to the issue that replaced it); everything else, priority " + "included, waits for the reopen. " + "priority is yours to set and a human overrides it. before and after move the issue on " + "its project's board: before puts it above that issue, after below, and both together " + "land it between them. A project's board order is one agent's to keep; in a Legion " + "deployment that is the controller. Never reassign an issue a person holds to get a " + "faster answer: who holds it is their call. At least one " + `field besides issue is required. ${ISSUE_REFERENCE}`,
15259
+ description: "Update an existing issue: move its lifecycle status, retitle it, replace its labels, set " + "its priority, link a URL (the pull request that delivers it, a run, a document), set its " + "route, set or clear its parent, replace the issues it is blocked by, attach it to " + "architecture components, set the repository it targets, or move it on its project's " + "board. Status is one of " + `${ISSUE_STATUSES.join(", ")}; outside Legion, move it yourself as the work advances; inside ` + "Legion the daemon moves it. A non-empty external_links is " + "merged into the issue's existing links by URL, so linking the pull request you just opened " + "keeps every earlier link; an empty external_links removes every link instead. components " + "replaces the issue's own attachment. Closing an issue " + "(status done) requires reason, the note that says why: it is posted on the issue as a " + "message, then the issue closes, so the close carries its note; closing an issue that is " + "already closed is refused (ISSUE_CLOSED) before anything is posted. Reason goes only " + "with status done. Closing an issue withdraws every open ask " + "it holds (resolution kind withdrawn, naming the closer), and reopening it brings exactly " + "those asks back. " + "A closed issue still takes messages and comments, though not asks or artifacts; of this " + "tool's fields it takes only a board move " + "(before, after), components, " + "a reopening status (any status but done), and an external_links write that adds no new " + "link (so a link can move to the issue that replaced it); everything else, priority " + "included, waits for the reopen. " + "priority is yours to set and a human overrides it. before and after move the issue on " + "its project's board: before puts it above that issue, after below, and both together " + "land it between them. A project's board order is one agent's to keep; in a Legion " + "deployment that is the controller. Never reassign an issue a person holds to get a " + "faster answer: who holds it is their call. At least one " + `field besides issue is required. ${ISSUE_REFERENCE}`,
15260
15260
  arguments: (z) => ({
15261
15261
  issue: z.string().describe(ISSUE_REFERENCE),
15262
15262
  status: z.enum(ISSUE_STATUSES).describe("New lifecycle status.").optional(),
@@ -23331,6 +23331,9 @@ async function executeDispatchTool(input) {
23331
23331
  } catch (error) {
23332
23332
  throw refusalWithCode(error);
23333
23333
  }
23334
+ if (status === "done" && typeof before.closed_at === "string") {
23335
+ throw refusalWithCode(new DispatchServiceError("ISSUE_CLOSED", 409, "issue is closed"), `${issueKey} closed at ${before.closed_at}, so the reason was not posted and the close was not sent`);
23336
+ }
23334
23337
  let closingNote;
23335
23338
  if (reason !== undefined) {
23336
23339
  try {
@@ -36760,7 +36760,7 @@ var commentValidation = {
36760
36760
  refusal: (value) => commentProblem(value) ?? `${commentOwner.message} ${COMMENT_TURN_RULE}`
36761
36761
  };
36762
36762
  var ISSUE_UPDATE_FIELDS_RULE = "Issue update requires at least one field besides issue: status, title, labels, priority, external_links, route, parent, blocked_by, components, repo, before, or after.";
36763
- var ISSUE_UPDATE_DONE_RULE = "status done requires reason, a non-empty note saying why the issue is closing, posted on the issue before it closes because a closed issue refuses messages, comments, and artifacts";
36763
+ var ISSUE_UPDATE_DONE_RULE = "status done requires reason, a non-empty note saying why the issue is closing, posted on the issue before it closes so the close carries its note";
36764
36764
  var ISSUE_UPDATE_REASON_RULE = "reason goes only with status done";
36765
36765
  var ISSUE_UPDATE_RULE = `${ISSUE_UPDATE_FIELDS_RULE} ${ISSUE_UPDATE_DONE_RULE}; ${ISSUE_UPDATE_REASON_RULE}.`;
36766
36766
  function issueUpdateProblem(value) {
@@ -36905,7 +36905,7 @@ var dispatchToolSpecs = [
36905
36905
  priority: 1,
36906
36906
  before: "DSP-2"
36907
36907
  },
36908
- description: "Update an existing issue: move its lifecycle status, retitle it, replace its labels, set " + "its priority, link a URL (the pull request that delivers it, a run, a document), set its " + "route, set or clear its parent, replace the issues it is blocked by, attach it to " + "architecture components, set the repository it targets, or move it on its project's " + "board. Status is one of " + `${ISSUE_STATUSES.join(", ")}; outside Legion, move it yourself as the work advances; inside ` + "Legion the daemon moves it. A non-empty external_links is " + "merged into the issue's existing links by URL, so linking the pull request you just opened " + "keeps every earlier link; an empty external_links removes every link instead. components " + "replaces the issue's own attachment. Closing an issue " + "(status done) requires reason, the note that says why: it is posted on the issue as a " + "message, then the issue closes, because a closed issue refuses messages, comments, and " + "artifacts; reason goes only with status done. Closing an issue withdraws every open ask " + "it holds (resolution kind withdrawn, naming the closer), and reopening it brings exactly " + "those asks back. A closed issue takes only a board move " + "(before, after), components, " + "a reopening status (any status but done), and an external_links write that adds no new " + "link (so a link can move to the issue that replaced it); everything else, priority " + "included, waits for the reopen. " + "priority is yours to set and a human overrides it. before and after move the issue on " + "its project's board: before puts it above that issue, after below, and both together " + "land it between them. A project's board order is one agent's to keep; in a Legion " + "deployment that is the controller. Never reassign an issue a person holds to get a " + "faster answer: who holds it is their call. At least one " + `field besides issue is required. ${ISSUE_REFERENCE}`,
36908
+ description: "Update an existing issue: move its lifecycle status, retitle it, replace its labels, set " + "its priority, link a URL (the pull request that delivers it, a run, a document), set its " + "route, set or clear its parent, replace the issues it is blocked by, attach it to " + "architecture components, set the repository it targets, or move it on its project's " + "board. Status is one of " + `${ISSUE_STATUSES.join(", ")}; outside Legion, move it yourself as the work advances; inside ` + "Legion the daemon moves it. A non-empty external_links is " + "merged into the issue's existing links by URL, so linking the pull request you just opened " + "keeps every earlier link; an empty external_links removes every link instead. components " + "replaces the issue's own attachment. Closing an issue " + "(status done) requires reason, the note that says why: it is posted on the issue as a " + "message, then the issue closes, so the close carries its note; closing an issue that is " + "already closed is refused (ISSUE_CLOSED) before anything is posted. Reason goes only " + "with status done. Closing an issue withdraws every open ask " + "it holds (resolution kind withdrawn, naming the closer), and reopening it brings exactly " + "those asks back. " + "A closed issue still takes messages and comments, though not asks or artifacts; of this " + "tool's fields it takes only a board move " + "(before, after), components, " + "a reopening status (any status but done), and an external_links write that adds no new " + "link (so a link can move to the issue that replaced it); everything else, priority " + "included, waits for the reopen. " + "priority is yours to set and a human overrides it. before and after move the issue on " + "its project's board: before puts it above that issue, after below, and both together " + "land it between them. A project's board order is one agent's to keep; in a Legion " + "deployment that is the controller. Never reassign an issue a person holds to get a " + "faster answer: who holds it is their call. At least one " + `field besides issue is required. ${ISSUE_REFERENCE}`,
36909
36909
  arguments: (z) => ({
36910
36910
  issue: z.string().describe(ISSUE_REFERENCE),
36911
36911
  status: z.enum(ISSUE_STATUSES).describe("New lifecycle status.").optional(),
@@ -50103,6 +50103,9 @@ async function executeDispatchTool(input) {
50103
50103
  } catch (error) {
50104
50104
  throw refusalWithCode(error);
50105
50105
  }
50106
+ if (status === "done" && typeof before.closed_at === "string") {
50107
+ throw refusalWithCode(new DispatchServiceError("ISSUE_CLOSED", 409, "issue is closed"), `${issueKey} closed at ${before.closed_at}, so the reason was not posted and the close was not sent`);
50108
+ }
50106
50109
  let closingNote;
50107
50110
  if (reason !== undefined) {
50108
50111
  try {
@@ -55211,7 +55214,7 @@ var import_nats2 = __toESM(require_mod4(), 1);
55211
55214
 
55212
55215
  // package.json
55213
55216
  var name = "@sjawhar/claude-envoy";
55214
- var version2 = "1.16.0";
55217
+ var version2 = "1.18.0";
55215
55218
  var legion = {
55216
55219
  daemonApiVersion: 56
55217
55220
  };
@@ -14110,7 +14110,7 @@ var commentValidation = {
14110
14110
  refusal: (value) => commentProblem(value) ?? `${commentOwner.message} ${COMMENT_TURN_RULE}`
14111
14111
  };
14112
14112
  var ISSUE_UPDATE_FIELDS_RULE = "Issue update requires at least one field besides issue: status, title, labels, priority, external_links, route, parent, blocked_by, components, repo, before, or after.";
14113
- var ISSUE_UPDATE_DONE_RULE = "status done requires reason, a non-empty note saying why the issue is closing, posted on the issue before it closes because a closed issue refuses messages, comments, and artifacts";
14113
+ var ISSUE_UPDATE_DONE_RULE = "status done requires reason, a non-empty note saying why the issue is closing, posted on the issue before it closes so the close carries its note";
14114
14114
  var ISSUE_UPDATE_REASON_RULE = "reason goes only with status done";
14115
14115
  var ISSUE_UPDATE_RULE = `${ISSUE_UPDATE_FIELDS_RULE} ${ISSUE_UPDATE_DONE_RULE}; ${ISSUE_UPDATE_REASON_RULE}.`;
14116
14116
  function issueUpdateProblem(value) {
@@ -14255,7 +14255,7 @@ var dispatchToolSpecs = [
14255
14255
  priority: 1,
14256
14256
  before: "DSP-2"
14257
14257
  },
14258
- description: "Update an existing issue: move its lifecycle status, retitle it, replace its labels, set " + "its priority, link a URL (the pull request that delivers it, a run, a document), set its " + "route, set or clear its parent, replace the issues it is blocked by, attach it to " + "architecture components, set the repository it targets, or move it on its project's " + "board. Status is one of " + `${ISSUE_STATUSES.join(", ")}; outside Legion, move it yourself as the work advances; inside ` + "Legion the daemon moves it. A non-empty external_links is " + "merged into the issue's existing links by URL, so linking the pull request you just opened " + "keeps every earlier link; an empty external_links removes every link instead. components " + "replaces the issue's own attachment. Closing an issue " + "(status done) requires reason, the note that says why: it is posted on the issue as a " + "message, then the issue closes, because a closed issue refuses messages, comments, and " + "artifacts; reason goes only with status done. Closing an issue withdraws every open ask " + "it holds (resolution kind withdrawn, naming the closer), and reopening it brings exactly " + "those asks back. A closed issue takes only a board move " + "(before, after), components, " + "a reopening status (any status but done), and an external_links write that adds no new " + "link (so a link can move to the issue that replaced it); everything else, priority " + "included, waits for the reopen. " + "priority is yours to set and a human overrides it. before and after move the issue on " + "its project's board: before puts it above that issue, after below, and both together " + "land it between them. A project's board order is one agent's to keep; in a Legion " + "deployment that is the controller. Never reassign an issue a person holds to get a " + "faster answer: who holds it is their call. At least one " + `field besides issue is required. ${ISSUE_REFERENCE}`,
14258
+ description: "Update an existing issue: move its lifecycle status, retitle it, replace its labels, set " + "its priority, link a URL (the pull request that delivers it, a run, a document), set its " + "route, set or clear its parent, replace the issues it is blocked by, attach it to " + "architecture components, set the repository it targets, or move it on its project's " + "board. Status is one of " + `${ISSUE_STATUSES.join(", ")}; outside Legion, move it yourself as the work advances; inside ` + "Legion the daemon moves it. A non-empty external_links is " + "merged into the issue's existing links by URL, so linking the pull request you just opened " + "keeps every earlier link; an empty external_links removes every link instead. components " + "replaces the issue's own attachment. Closing an issue " + "(status done) requires reason, the note that says why: it is posted on the issue as a " + "message, then the issue closes, so the close carries its note; closing an issue that is " + "already closed is refused (ISSUE_CLOSED) before anything is posted. Reason goes only " + "with status done. Closing an issue withdraws every open ask " + "it holds (resolution kind withdrawn, naming the closer), and reopening it brings exactly " + "those asks back. " + "A closed issue still takes messages and comments, though not asks or artifacts; of this " + "tool's fields it takes only a board move " + "(before, after), components, " + "a reopening status (any status but done), and an external_links write that adds no new " + "link (so a link can move to the issue that replaced it); everything else, priority " + "included, waits for the reopen. " + "priority is yours to set and a human overrides it. before and after move the issue on " + "its project's board: before puts it above that issue, after below, and both together " + "land it between them. A project's board order is one agent's to keep; in a Legion " + "deployment that is the controller. Never reassign an issue a person holds to get a " + "faster answer: who holds it is their call. At least one " + `field besides issue is required. ${ISSUE_REFERENCE}`,
14259
14259
  arguments: (z) => ({
14260
14260
  issue: z.string().describe(ISSUE_REFERENCE),
14261
14261
  status: z.enum(ISSUE_STATUSES).describe("New lifecycle status.").optional(),
@@ -22335,6 +22335,9 @@ async function executeDispatchTool(input) {
22335
22335
  } catch (error) {
22336
22336
  throw refusalWithCode(error);
22337
22337
  }
22338
+ if (status === "done" && typeof before.closed_at === "string") {
22339
+ throw refusalWithCode(new DispatchServiceError("ISSUE_CLOSED", 409, "issue is closed"), `${issueKey} closed at ${before.closed_at}, so the reason was not posted and the close was not sent`);
22340
+ }
22338
22341
  let closingNote;
22339
22342
  if (reason !== undefined) {
22340
22343
  try {
@@ -187,13 +187,13 @@ The merge closes the issue: when GitHub reports the pull request merged while th
187
187
  `linger_hours` with every member suspended. The linger's end, the tree's last step, waits until
188
188
  every member's open production check has closed.
189
189
 
190
- Nothing of the production check is yours. Its record lives on the pull request alone, since
191
- Dispatch refuses a write on a closed issue. A defect it finds is never an issue the checking role
192
- files: a clear defect goes to the controller, and any other is a `dispatch ask`. `read_record`
193
- shows a closed issue's open check (`check`: the pull request, the merge commit, the role the
194
- check records as the one that works it, the looks, what woke the last one, and `nextWakeAt`, when
195
- the next look is due — the time the checking agent named for itself on a `pending`, the retry of
196
- a look nobody reported, or the check's own bound where neither stands).
190
+ Nothing of the production check is yours. Its record is the pull request and the result of the
191
+ issue's `after_merge` check, whether or not the issue is closed. A defect it finds is never an
192
+ issue the checking role files: a clear defect goes to the controller, and any other is a
193
+ `dispatch ask`. `read_record` shows a closed issue's open check (`check`: the pull request, the
194
+ merge commit, the role the check records as the one that works it, the looks, what woke the last
195
+ one, and `nextWakeAt`, when the next look is due — the time the checking agent named for itself on
196
+ a `pending`, the retry of a look nobody reported, or the check's own bound where neither stands).
197
197
 
198
198
  ## Hand-over at a root's linger
199
199
 
@@ -31,7 +31,7 @@ unassigned.
31
31
  Each time you read it:
32
32
 
33
33
  - `Status: done` means someone closed it: the close withdrew its open asks (each recorded
34
- `withdrawn`; a reopen brings them back), and Dispatch refuses an ask or a message there
34
+ `withdrawn`; a reopen brings them back), and Dispatch refuses an ask there
35
35
  (`409 ISSUE_CLOSED`). Reopen it with `set_status` to `icebox` and say so in the day's report.
36
36
  - `Assignee: unassigned` with a person known: assign it, naming your session (`dispatch whoami`):
37
37
  `curl -sS -X PATCH -H "Authorization: Bearer $(cat "$DISPATCH_TOKEN_FILE")" -H 'Content-Type: application/json' -d '{"assignee": "<email>", "actor": {"kind": "session", "id": "<session id>"}}' "$DISPATCH_URL/api/v1/issues/<REPORT KEY>"`.
@@ -319,7 +319,8 @@ reviewing until you finish.
319
319
  recorded the result: `dispatch result` against the issue's `after_merge` check, at the merge
320
320
  commit, its `observed` ending `woken by <event>`, with the printed line in the PR body's
321
321
  `Checks` block and one pull-request comment carrying the Legion footer. A closed issue takes a
322
- result for an `after_merge` or `default_branch` check and refuses every other write. A staging
322
+ result for an `after_merge` or `default_branch` check, a comment and a message, and refuses
323
+ every other write. A staging
323
324
  pass is not this, since a
324
325
  staging gate does not run every resource production does.
325
326
  - `pending`, when production does not carry the merge yet (a shared profile still holding the
@@ -371,9 +372,9 @@ reviewing until you finish.
371
372
  check as not run, and before reporting it you write on the pull request, in the check's line and
372
373
  a comment, that the check did not run because production did not carry the merge within the
373
374
  bound.
374
- - **The record lives on the pull request and in the result.** Dispatch refuses a message, a
375
- comment or an ask on a closed issue (`ISSUE_CLOSED`) and takes only a result for an
376
- `after_merge` or `default_branch` check, so the deployment instructions' "on the pull request
375
+ - **The record lives on the pull request and in the result.** Dispatch takes a comment, a message
376
+ and a result for an `after_merge` or `default_branch` check on a closed issue, and refuses an
377
+ ask there (`ISSUE_CLOSED`), so the deployment instructions' "on the pull request
377
378
  and the issue" is the pull request and that result. A defect reaches Dispatch by the two paths above:
378
379
  a clear defect as the message to the controller, which gives it to the tree that owns that code,
379
380
  and any other as the ask on the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/claude-envoy",
3
- "version": "1.16.0",
3
+ "version": "1.18.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Claude Code channel plugin for Legion's Envoy bus",
6
6
  "type": "module",