@sjawhar/pi-legion-envoy 7.5.0 → 7.5.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/dist/legion.js CHANGED
@@ -17475,7 +17475,7 @@ function endInjectedUserTurns(sessionID) {
17475
17475
  // package.json
17476
17476
  var package_default = {
17477
17477
  name: "@sjawhar/pi-legion-envoy",
17478
- version: "7.5.0",
17478
+ version: "7.5.1",
17479
17479
  license: "Apache-2.0",
17480
17480
  type: "module",
17481
17481
  omp: {
@@ -17843,6 +17843,7 @@ var LegionThreadOutcome = exports_external.union([
17843
17843
  ]);
17844
17844
  var LegionThreadsResolveResponse = exports_external.strictObject({
17845
17845
  threads: exports_external.array(LegionThreadOutcome),
17846
+ withheld: exports_external.number().int().nonnegative(),
17846
17847
  refused: exports_external.strictObject({ url: nonEmptyString, error: nonEmptyString }).optional()
17847
17848
  });
17848
17849
  var LegionEmptyResponse = exports_external.strictObject({});
@@ -18867,9 +18868,10 @@ var LEGION_HANDOFF_COMPLETE = {
18867
18868
  },
18868
18869
  refusal: (attempt) => `refused \`${attempt}\`: a phase is completed with the \`legion\` tool's ` + "`handoff_complete`, never a shell command: the tool call is what records the phase " + "complete. A root architect or a `task` subagent has no handoff actions: it leaves the " + "completion to the worker. Text that only names the command (a commit message, a PR body) " + "reads as the command: pass it in a file."
18869
18870
  };
18871
+ var TREE_PANE_RULES = [JJ_LOG_REWRITE, LEGION_HANDOFF_COMPLETE];
18870
18872
  var PANE_RULES = {
18871
- "phase-worker": [JJ_LOG_REWRITE, LEGION_HANDOFF_COMPLETE],
18872
- "root-architect": [LEGION_HANDOFF_COMPLETE]
18873
+ "phase-worker": TREE_PANE_RULES,
18874
+ "root-architect": TREE_PANE_RULES
18873
18875
  };
18874
18876
  function refusedCommand(command, commands, rule) {
18875
18877
  if (commands === undefined) {
@@ -18916,7 +18918,11 @@ function paneRuleRefusal(toolCall, rules) {
18916
18918
  }
18917
18919
  var LEGION_LOADED_MARKER = Symbol.for("legion.pi-envoy.legion-loaded");
18918
18920
  var CODE_MUTATION_TOOLS = ["edit", "write", "apply_patch"];
18919
- var MERGER_BLOCKED_TOOLS = [...CODE_MUTATION_TOOLS, "task"];
18921
+ var CODE_TOOL_REFUSAL = {
18922
+ architect: "the architect delegates all code work to phase workers",
18923
+ reviewer: "the reviewer edits nothing except the final .legion/ cleanup commit via bash",
18924
+ merger: "the merger only verifies and reports"
18925
+ };
18920
18926
  var TOOL_DEVICE_URL = /^xd:\/\//iu;
18921
18927
  function isToolDeviceInvocation(toolCall) {
18922
18928
  return toolCall.toolName === "write" && typeof toolCall.input.path === "string" && TOOL_DEVICE_URL.test(toolCall.input.path);
@@ -18999,24 +19005,10 @@ function legionExtension(pi) {
18999
19005
  return;
19000
19006
  const sessionID = context.sessionManager.getSessionId();
19001
19007
  const active = claimSession.capability(sessionID);
19002
- const isToolDevice = isToolDeviceInvocation(toolCall);
19003
- if (active?.role === "architect" && !isToolDevice && (CODE_MUTATION_TOOLS.includes(toolCall.toolName) || toolCall.toolName === "bash" && !isSingleLegionCommand(toolCall.input.command))) {
19004
- return { block: true, reason: "the architect delegates all code work to phase workers" };
19005
- }
19006
- if (active?.role === "architect" && toolCall.toolName === "task") {
19007
- return {
19008
- block: true,
19009
- reason: "the architect delegates only through child issues and the daemon's phase workers; Legion runs one agent per process"
19010
- };
19011
- }
19012
- if (active?.role === "reviewer" && !isToolDevice && CODE_MUTATION_TOOLS.includes(toolCall.toolName)) {
19013
- return {
19014
- block: true,
19015
- reason: "the reviewer edits nothing except the final .legion/ cleanup commit via bash"
19016
- };
19017
- }
19018
- if (active?.role === "merger" && !isToolDevice && MERGER_BLOCKED_TOOLS.includes(toolCall.toolName)) {
19019
- return { block: true, reason: "the merger only verifies and reports" };
19008
+ const codeToolRefusal = active === undefined ? undefined : CODE_TOOL_REFUSAL[active.role];
19009
+ const mutatesCode = CODE_MUTATION_TOOLS.includes(toolCall.toolName) || active?.role === "architect" && toolCall.toolName === "bash" && !isSingleLegionCommand(toolCall.input.command);
19010
+ if (codeToolRefusal !== undefined && mutatesCode && !isToolDeviceInvocation(toolCall)) {
19011
+ return { block: true, reason: codeToolRefusal };
19020
19012
  }
19021
19013
  if (!needsGrant(toolCall))
19022
19014
  return;
@@ -19108,5 +19100,6 @@ function legionExtension(pi) {
19108
19100
  }
19109
19101
  export {
19110
19102
  setLegionBootstrapExitForTests,
19111
- legionExtension as default
19103
+ legionExtension as default,
19104
+ CODE_TOOL_REFUSAL
19112
19105
  };
@@ -285,6 +285,13 @@ is open. That is the one review-thread step a human takes: the review App cannot
285
285
  on a pull request the implementer opened, and the implementer's and merger's runs of the command
286
286
  close every accepted one.
287
287
 
288
+ If a reviewer reports that `legion threads resolve` exited 1 counting threads that hold the
289
+ implement App's pending draft (the daemon counts them and never names them), tell the worker
290
+ holding the issue's phase (`envoy_publish` to its role topic) to move the issue back to
291
+ `implementing` with `request_backward_move`, naming the count, so the implementer submits or
292
+ discards its pending review; its own run of the command names those threads `left open … an
293
+ unsubmitted draft in a pending review`. Open no ask for it: the implementer clears it.
294
+
288
295
  ## 7. Close
289
296
 
290
297
  After the merge result and the implementer's production report are recorded, post the sign-off and
@@ -20,7 +20,16 @@ Every path it cites is in sjawhar/legion.
20
20
  the daemon instead (`POST /legion/v1/threads/resolve`), which resolves as the implementer's App
21
21
  only the threads a bot outside Legion's role Apps opened whose newest submitted comment is the
22
22
  reviewer's `Accepted:`, on the pull request of the reviewer's own issue, and prints the same
23
- lines; the reviewer never holds the implementer's token.
23
+ lines; the reviewer never holds the implementer's token. A thread whose newest comment is a
24
+ draft in the implement App's pending review (the implementer's or the merger's: GitHub shows it
25
+ to that App alone, as which the daemon reads) is left open and never named: the command prints
26
+ only how many there are (`<n> unresolved threads hold the implement App's pending draft and
27
+ were left open`) and exits 1, as it does on a refusal. Report that count to the architect before
28
+ you spend your one re-run of the failed workflow; the architect sends the issue back so the
29
+ implementer submits or discards its pending review. Once the review is submitted, the
30
+ implementer's reply is the thread's newest comment, which leaves it open: answer the thread
31
+ again, then run the command again. Once the review is discarded, your `Accepted:` is the newest
32
+ comment again, and running the command again closes the thread.
24
33
  When `LEGION_GRANT_FILE` or `LEGION_GRANT` is set, use `legion threads resolve --pr <number> --repo <owner>/<repo>`.
25
34
  When neither is set, add `--gh` to that command, which applies the fallback's rule below through
26
35
  your own `gh`; where no `legion` command is installed, use `gh api graphql` with the session's
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/pi-legion-envoy",
3
- "version": "7.5.0",
3
+ "version": "7.5.1",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "omp": {