@sjawhar/pi-legion 10.7.2 → 10.8.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
@@ -16171,7 +16171,7 @@ function ompCapabilityHost() {
16171
16171
  // package.json
16172
16172
  var package_default = {
16173
16173
  name: "@sjawhar/pi-legion",
16174
- version: "10.7.2",
16174
+ version: "10.8.1",
16175
16175
  license: "Apache-2.0",
16176
16176
  type: "module",
16177
16177
  omp: {
@@ -16183,7 +16183,7 @@ var package_default = {
16183
16183
  ]
16184
16184
  },
16185
16185
  legion: {
16186
- daemonApiVersion: 29
16186
+ daemonApiVersion: 30
16187
16187
  },
16188
16188
  repository: {
16189
16189
  type: "git",
@@ -16461,8 +16461,11 @@ var LegionControllerSecretResponse = exports_external.strictObject({
16461
16461
  designGate: exports_external.enum(["root-issues", "off"])
16462
16462
  });
16463
16463
  var LegionControllerCredentialResponse = exports_external.strictObject({
16464
- hosts: nonEmptyString,
16465
- config: nonEmptyString,
16464
+ owners: exports_external.record(nonEmptyString, exports_external.strictObject({
16465
+ hosts: nonEmptyString,
16466
+ config: nonEmptyString
16467
+ })),
16468
+ default: nonEmptyString,
16466
16469
  app: exports_external.enum(["implement", "review"]),
16467
16470
  expiresAt: timestamp
16468
16471
  });
@@ -17125,16 +17128,18 @@ function stepPhaseStall(state, input) {
17125
17128
  case "handoff-complete":
17126
17129
  return { state: "closed" };
17127
17130
  case "inbound-event":
17128
- return { state: state === "quiet" ? "open" : state };
17129
- case "settle":
17130
- if (state !== "open")
17131
+ return { state: state === "quiet" || state === "text-call" ? "open" : state };
17132
+ case "settle": {
17133
+ if (state !== "open" && state !== "text-call")
17131
17134
  return { state };
17132
17135
  if (WAITING_REPLY.test(input.lastAssistantText))
17133
17136
  return { state: "quiet" };
17134
- return {
17135
- state: "quiet",
17136
- followUp: TOOL_CALL_SHAPED.test(input.lastAssistantText) ? TEXT_TOOL_CALL + FOLLOW_UP : FOLLOW_UP
17137
- };
17137
+ const textCall = TOOL_CALL_SHAPED.test(input.lastAssistantText);
17138
+ if (state === "text-call") {
17139
+ return textCall ? { state: "quiet", followUp: TEXT_TOOL_CALL + FOLLOW_UP } : { state: "quiet" };
17140
+ }
17141
+ return textCall ? { state: "text-call", followUp: TEXT_TOOL_CALL + FOLLOW_UP } : { state: "quiet", followUp: FOLLOW_UP };
17142
+ }
17138
17143
  }
17139
17144
  }
17140
17145
  function inboundKind(message) {
@@ -17157,7 +17162,7 @@ function assistantText(message) {
17157
17162
  `);
17158
17163
  }
17159
17164
  function isPhaseStall(value) {
17160
- return value === "closed" || value === "open" || value === "quiet";
17165
+ return value === "closed" || value === "open" || value === "text-call" || value === "quiet";
17161
17166
  }
17162
17167
  function restorePhaseStall(branch) {
17163
17168
  for (let index = branch.length - 1;index >= 0; index -= 1) {
@@ -17295,7 +17300,7 @@ function createLegionTool(deps) {
17295
17300
  return {
17296
17301
  name: "legion",
17297
17302
  label: "legion",
17298
- description: "Perform the workflow operation the Legion daemon assigned this role. The daemon advances phases; this tool cannot spawn workers. " + "handoff_complete (phase workers and sub-architects; the daemon accepts a completion only from the role working the issue's current phase) " + "reports this phase complete: `summary` (two sentences for the architect, or the merger's READY packet), `verdict` pass|fail when your role's " + "instructions require one, `ready: true` for the merger's READY. A handoff is a committed file, `.legion/<issue>/<phase>.json`: the pushed commit " + "carrying it is found here, with the pane's jj, and reported, so write it, commit it and push the branch (`jj git push`) before calling; this call " + "refuses, posting nothing, a handoff still in the working copy, missing, not committed on this branch, carried by another pane's commit, or not yet " + "on `legion/<issue>@origin`, each naming the remedy. The daemon refuses HANDOFF_NOT_NEW (the commit is the one you reported for your previous " + "phase: write and commit this phase's handoff) and, for READY, each refusal the merger's role names; a refused completion changed " + "nothing, so fix what it names and complete again. An accepted READY whose `note` begins MANUAL_MERGE_REQUIRED: is one a person merges: arm nothing. " + "The controller's read_state returns the daemon's whole state and set_status moves an issue to todo, backlog or icebox. " + "What a later phase needs goes in your handoff; a question for another live role goes to its role topic with envoy_publish.",
17303
+ description: "Perform the workflow operation the Legion daemon assigned this role. The daemon advances phases; this tool cannot spawn workers. " + "handoff_complete (phase workers and sub-architects; the daemon accepts a completion only from the role working the issue's current phase) " + "reports this phase complete: `summary` (two sentences for the architect, or the merger's READY packet), `verdict` pass|fail from the tester alone " + "(the daemon refuses a verdict from any other role), `ready: true` for the merger's READY. A handoff is a committed file, `.legion/<issue>/<phase>.json`: the pushed commit " + "carrying it is found here, with the pane's jj, and reported, so write it, commit it and push the branch (`jj git push`) before calling; this call " + "refuses, posting nothing, a handoff still in the working copy, missing, not committed on this branch, carried by another pane's commit, or not yet " + "on `legion/<issue>@origin`, each naming the remedy. The daemon refuses HANDOFF_NOT_NEW (the commit is the one you reported for your previous " + "phase: write and commit this phase's handoff) and, for READY, each refusal the merger's role names; a refused completion changed " + "nothing, so fix what it names and complete again. An accepted READY whose `note` begins MANUAL_MERGE_REQUIRED: is one a person merges: arm nothing. " + "The controller's read_state returns the daemon's whole state and set_status moves an issue to todo, backlog or icebox. " + "What a later phase needs goes in your handoff; a question for another live role goes to its role topic with envoy_publish.",
17299
17304
  defaultInactive: true,
17300
17305
  parameters: toolSchema(pi),
17301
17306
  execute: async (_id, parameters, _signal, _onUpdate, context) => {
@@ -16,13 +16,15 @@ The Legion extension registers this session with the daemon as the controller an
16
16
  `legion-<project>-controller` during session startup. Do not handle a wake unless that startup
17
17
  succeeded.
18
18
 
19
- The session's `gh` acts as the review App's bot account (`gh api graphql -f query='{viewer{login}}'` names it) from
20
- `GH_CONFIG_DIR`, a directory Legion keeps current: in the pod the daemon launches, through the
21
- controller's role Secret the daemon refreshes every minute; in `legion controller start`'s session,
22
- through that command's own refresh loop. `gh pr view`, `gh api` and the rest read and write GitHub
23
- as that App. Against a daemon with no GitHub App, `legion controller start` says so when it starts
24
- (`[legion] the daemon has no GitHub App to act as; the controller's gh acts as nobody`), and `gh`
25
- acts as nobody. The controller reads Dispatch and the daemon's state, and applies its controller
19
+ The session's `gh` acts as the review App's bot account (`gh api graphql -f query='{viewer{login}}'`
20
+ names it) from `LEGION_GH_OWNERS_DIR`, one read-only directory per GitHub owner of the project,
21
+ which Legion refreshes every minute (the daemon through the controller pod's role Secret, `legion
22
+ controller start` through its own loop). That `gh` is Legion's wrapper: it reads with the token of
23
+ the owner a command names (`-R owner/repo`, `repos/owner/repo` in `gh api`, a GitHub URL) and with
24
+ the first owner's (`GH_CONFIG_DIR`) otherwise, so name the repository. Against a daemon with no
25
+ GitHub App, `legion controller start` says so when it starts (`[legion] the daemon has no GitHub
26
+ App to act as; the controller's gh acts as nobody`), and `gh` acts as nobody. The controller reads
27
+ Dispatch and the daemon's state, and applies its controller
26
28
  capability, through the `legion` tool alone: `op: "read_state"` returns the daemon's whole state
27
29
  (`issues.<KEY>.phase`, `issues.<KEY>.holdReason`, `issues.<KEY>.architect`, `admission.cap`,
28
30
  `admission.active`, `admission.waiting`, `capabilities[]`, `daemon.project`), and `op: "set_status"`
@@ -58,7 +60,7 @@ The command checks that `LEGION_PROJECT` is the daemon's project, registers this
58
60
  daemon, and claims the Envoy role for it; the `legion` tool's controller operations then work in
59
61
  this session, each `set_status` minting its grant in-process from the registration's secret. A
60
62
  takeover session gets no token file from Legion: `legion controller start` and the pod write
61
- `GH_CONFIG_DIR`'s token, but a hand-started session's `gh` is whatever its own environment gives it.
63
+ `LEGION_GH_OWNERS_DIR`'s tokens, but a hand-started session's `gh` is what its environment gives it.
62
64
  The takeover moves the role and the daemon's recorded session id to this session. Never pass a
63
65
  secret as a command argument or copy it into a transcript. The Envoy
64
66
  registration heartbeat keeps the role afterwards, so `/legion-claim-controller` is the manual
@@ -198,21 +198,21 @@ Your pane's `gh` and `git` already carry your role's GitHub identity; invoke Git
198
198
  gh <gh args…>
199
199
  ```
200
200
 
201
- How `gh` works in a worker pane. `GH_CONFIG_DIR` names a read-only directory holding gh's own
202
- `hosts.yml` (your role's GitHub App installation token, user `x-access-token`) and `config.yml`,
203
- which the daemon rewrites in place from its cached lease so it never expires under you; the `gh`
204
- on your `PATH` is the ordinary binary and reads it on every call, `git` reads the same file
205
- through the clone's `credential.helper` (`!gh auth git-credential`), and `GH_TOKEN`,
206
- `GITHUB_TOKEN` and `GH_HOST` are set empty so nothing outranks it. Never run `gh auth login`,
207
- `gh auth setup-git`, `gh config set` or `gh alias set`: there is no login state to create, and the
208
- directory is read-only. One rule nothing enforces: **Legion's issues live on Dispatch, never on
201
+ How `gh` works in a worker pane. `LEGION_GH_OWNERS_DIR` holds one read-only gh directory per
202
+ GitHub owner of the project (`hosts.yml`, an App installation token, and `config.yml`), which the
203
+ daemon rewrites in place so no token expires under you: your issue's owner's, which `GH_CONFIG_DIR`
204
+ names, holds your role's App token; every other owner's the same App's, read-only. Your `gh` picks
205
+ the directory of the owner a command names (`-R owner/repo`, `repos/owner/repo` in `gh api`, a
206
+ GitHub URL), your issue's owner's otherwise, so name the repository whenever it is not your
207
+ issue's; `git` picks by the remote URL's owner (`!legion git-credential`). `GH_TOKEN`,
208
+ `GITHUB_TOKEN` and `GH_HOST` are empty so nothing outranks the files. Never run `gh auth login`,
209
+ `gh auth switch`, `gh auth setup-git`, `gh config set` or `gh alias set`: there is no login state
210
+ to create. One rule nothing enforces: **Legion's issues live on Dispatch, never on
209
211
  GitHub issues** — no `gh issue` write and no non-GET `gh api` call to an `/issues` path
210
212
  (pull-request conversation comments live on that path too; edit them with `gh pr comment`); use
211
213
  `dispatch message` or `dispatch comment` on your `LEGION_ISSUE`. The `legion` tool's own daemon
212
214
  calls carry a credential of their own, minted in-process; `gh` and `git` never see it, and you
213
- never `cat`, `echo`, copy, or `export` a token. A `task` subagent, an `eval` subprocess, or a
214
- background job in your pane inherits the same `GH_CONFIG_DIR` and so the same credential, for as
215
- long as it runs.
215
+ never `cat`, `echo`, copy, or `export` a token; a `task` subagent or background job inherits them.
216
216
 
217
217
  **Other credentials your pod may already carry.** Before reporting that a read is unreachable,
218
218
  check for them rather than assuming none exist: `AGENT_SECRETS_URL` and `AGENT_SECRETS_KEY_DIR`
@@ -265,8 +265,8 @@ The implementer opens the pull request. After its implementation commit and veri
265
265
  pushes the issue branch under this exact name with the one push procedure every role uses
266
266
  (*Every role pushes its own commits*, below).
267
267
 
268
- The shared clone's `credential.helper` is `!gh auth git-credential`, so `jj git push`
269
- authenticates as your role's App from the same file your `gh` reads. Never handle a token.
268
+ `jj git push` authenticates as your role's App through the clone's `!legion git-credential`, from
269
+ the file your `gh` reads for your issue's repository. Never handle a token.
270
270
 
271
271
  Then open the pull request with `gh pr create`. The PR body **must** contain the
272
272
  line `Dispatch: <KEY>` — the daemon's fallback link from a PR to its Dispatch issue when the
@@ -328,7 +328,7 @@ or `review`), with the ordinary `write` tool: a JSON object with `schemaVersion:
328
328
  phase's own fields exactly as your role prompt spells them — the planner's `requiredSkills`,
329
329
  `gapAnalysis`, `planReview` and `specDepartures` records; the implementer's `proof` array; the
330
330
  tester's `implementerProof`, `failures` and, whenever it reports no failure, its own `proof`; the
331
- reviewer's `verdict`, counts and `keyFindings`. Nothing stamps the first four for you. Each `proof`
331
+ reviewer's `decision`, counts and `keyFindings`. Nothing stamps the first four for you. Each `proof`
332
332
  entry, in either phase, is an object of six non-empty strings: `criterion` (the acceptance line it
333
333
  proves), `surface`, `command`, `observed`, `headSha` (the commit it ran at) and `negativeControl`.
334
334
  A handoff built from the one already on disk (a test handoff that accumulates review rounds) is
@@ -342,10 +342,10 @@ cd -- "$LEGION_WORKSPACE" && \
342
342
  ```
343
343
 
344
344
  Then push (below) and report completion: call the `legion` tool with `op: "handoff_complete"` and
345
- `summary` (two sentences for the architect; the tester adds `verdict: "pass"` or `"fail"`, the
346
- merger `ready: true`). **Push before you complete is a hard rule.** `handoff_complete` finds, with
347
- your pane's jj, the newest commit on the issue branch that carries `.legion/<KEY>/<phase>.json`
348
- and reports it to the daemon, and refuses — posting nothing — while:
345
+ `summary` (two sentences for the architect; the tester, and no other role, adds `verdict: "pass"`
346
+ or `"fail"`; the merger `ready: true`). **Push before you complete is a hard rule.**
347
+ `handoff_complete` finds, with your pane's jj, the newest commit on the issue branch that carries
348
+ `.legion/<KEY>/<phase>.json` and reports it to the daemon, and refuses — posting nothing — while:
349
349
 
350
350
  - the file has a change still in the working copy: commit it, then complete again;
351
351
  - the file is missing from the workspace: write it, then commit and push it;
@@ -429,8 +429,9 @@ Report completion to the architect: call the `legion` tool with `op: "handoff_co
429
429
  (`handoff_complete`; `read_record`, your issue's record, which is what "re-read your issue
430
430
  record" means; and `request_backward_move`), and nothing of yours runs `legion` from bash. The
431
431
  tool call is what the extension's phase stall records, and a turn that ends with the phase still
432
- open gets one reminder; do not complete again on that reminder when your first completion was
433
- accepted — it stands, and a second is refused because the issue has already left your phase.
432
+ open gets one reminder (two when the first is answered with the tool call written as text again);
433
+ do not complete again on a reminder when your first completion was accepted — it stands, and a
434
+ second is refused because the issue has already left your phase.
434
435
 
435
436
  This publishes your phase's completion to the architect's role and clears the daemon's
436
437
  record of this issue's active phase. Do not add pipeline labels, run a controller loop, or
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/pi-legion",
3
- "version": "10.7.2",
3
+ "version": "10.8.1",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "omp": {
@@ -12,7 +12,7 @@
12
12
  ]
13
13
  },
14
14
  "legion": {
15
- "daemonApiVersion": 29
15
+ "daemonApiVersion": 30
16
16
  },
17
17
  "repository": {
18
18
  "type": "git",