@sjawhar/pi-legion 10.4.1 → 10.5.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.
package/dist/legion.js
CHANGED
|
@@ -14090,7 +14090,17 @@ function dispatchSettings(env, config2) {
|
|
|
14090
14090
|
}
|
|
14091
14091
|
const url2 = (explicitUrl ?? (source.kind === "file" ? source.server : own.serverUrl ?? DEFAULT_SERVER_URL)).replace(/\/+$/, "");
|
|
14092
14092
|
const token = source.kind === "variable" ? source.token : fileBearer(source, url2, "DISPATCH", env);
|
|
14093
|
-
|
|
14093
|
+
const publicUrl = nonEmpty(env.DISPATCH_PUBLIC_URL);
|
|
14094
|
+
const publicParsed = publicUrl === undefined ? undefined : httpUrl2(publicUrl);
|
|
14095
|
+
if (publicUrl !== undefined && (publicParsed === undefined || publicParsed.port === "0")) {
|
|
14096
|
+
return offWithError("DISPATCH_PUBLIC_URL must be an http or https URL");
|
|
14097
|
+
}
|
|
14098
|
+
return {
|
|
14099
|
+
enabled: true,
|
|
14100
|
+
url: url2,
|
|
14101
|
+
token,
|
|
14102
|
+
...publicUrl === undefined ? {} : { publicUrl: publicUrl.replace(/\/+$/, "") }
|
|
14103
|
+
};
|
|
14094
14104
|
} catch (error48) {
|
|
14095
14105
|
return offWithError(messageFor(error48));
|
|
14096
14106
|
}
|
|
@@ -14902,6 +14912,11 @@ function controllerToken(project) {
|
|
|
14902
14912
|
assertLegionProjectToken(project);
|
|
14903
14913
|
return `legion-${project}-controller`;
|
|
14904
14914
|
}
|
|
14915
|
+
// ../contracts/src/slack.ts
|
|
14916
|
+
var SLACK_CHANNEL_ID_PATTERN = /^[CG][A-Z0-9]+$/;
|
|
14917
|
+
function slackMentionTopic(channel) {
|
|
14918
|
+
return `notifications.slack.*.${channel}.mention`;
|
|
14919
|
+
}
|
|
14905
14920
|
// ../envoy-client/src/ask-answer.ts
|
|
14906
14921
|
var HEAD_LENGTH = 120;
|
|
14907
14922
|
function textHead(text) {
|
|
@@ -15540,7 +15555,7 @@ function sessionDispatchSettings(file2, env, options) {
|
|
|
15540
15555
|
|
|
15541
15556
|
// ../pi-shared/src/interface.ts
|
|
15542
15557
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
15543
|
-
var ENVOY_PLUGIN_INTERFACE_VERSION =
|
|
15558
|
+
var ENVOY_PLUGIN_INTERFACE_VERSION = 3;
|
|
15544
15559
|
var ENVOY_PLUGIN_INTERFACE_KEY = Symbol.for("legion.pi-shared.envoy-plugin-interface");
|
|
15545
15560
|
var LEGION_PLUGIN_LOADED_KEY = Symbol.for("legion.pi-legion.loaded");
|
|
15546
15561
|
var LEGACY_LEGION_LOADED_KEY = Symbol.for("legion.pi-envoy.legion-loaded");
|
|
@@ -15553,7 +15568,12 @@ function envoyPluginInterface() {
|
|
|
15553
15568
|
const created = {
|
|
15554
15569
|
version: ENVOY_PLUGIN_INTERFACE_VERSION,
|
|
15555
15570
|
publishers: [],
|
|
15556
|
-
roleClaim: {
|
|
15571
|
+
roleClaim: {
|
|
15572
|
+
instances: [],
|
|
15573
|
+
managedSessions: new Set,
|
|
15574
|
+
regained: undefined,
|
|
15575
|
+
slackRoleOwned: false
|
|
15576
|
+
},
|
|
15557
15577
|
injectedUserTurns: new Map,
|
|
15558
15578
|
bootstrappedSession: { file: undefined }
|
|
15559
15579
|
};
|
|
@@ -15627,6 +15647,29 @@ function messageTag(message) {
|
|
|
15627
15647
|
return typeof message.tag === "string" ? message.tag : undefined;
|
|
15628
15648
|
}
|
|
15629
15649
|
|
|
15650
|
+
// ../pi-shared/src/role-claim-bridge.ts
|
|
15651
|
+
function legionRoleClaimBridge() {
|
|
15652
|
+
return envoyPluginInterface().roleClaim;
|
|
15653
|
+
}
|
|
15654
|
+
async function claimEnvoyRole(sessionID, role, context) {
|
|
15655
|
+
const bridge = legionRoleClaimBridge();
|
|
15656
|
+
bridge.managedSessions.add(sessionID);
|
|
15657
|
+
const instance = bridge.instances.findLast((candidate) => candidate.sessionID() === sessionID) ?? bridge.instances.at(-1);
|
|
15658
|
+
if (instance === undefined)
|
|
15659
|
+
throw new Error("Envoy has no bound instance for a role claim");
|
|
15660
|
+
await instance.claim(sessionID, role, context);
|
|
15661
|
+
}
|
|
15662
|
+
async function subscribeLegionNotice(sessionID, topic, context, whileHolding) {
|
|
15663
|
+
const bridge = legionRoleClaimBridge();
|
|
15664
|
+
const instance = bridge.instances.findLast((candidate) => candidate.sessionID() === sessionID) ?? bridge.instances.at(-1);
|
|
15665
|
+
if (instance === undefined)
|
|
15666
|
+
throw new Error("Envoy has no bound instance for a notice subscription");
|
|
15667
|
+
await instance.subscribe(sessionID, topic, context, whileHolding);
|
|
15668
|
+
}
|
|
15669
|
+
function onEnvoyRoleRegained(listener) {
|
|
15670
|
+
legionRoleClaimBridge().regained = listener;
|
|
15671
|
+
}
|
|
15672
|
+
|
|
15630
15673
|
// ../pi-shared/src/subagent-session.ts
|
|
15631
15674
|
import fs from "fs";
|
|
15632
15675
|
import path2 from "path";
|
|
@@ -15701,7 +15744,8 @@ var ENVOY_TOOL_NAMES = [
|
|
|
15701
15744
|
"envoy_role_set",
|
|
15702
15745
|
"envoy_role_get",
|
|
15703
15746
|
"envoy_whoami",
|
|
15704
|
-
"envoy_sessions"
|
|
15747
|
+
"envoy_sessions",
|
|
15748
|
+
"slack_post"
|
|
15705
15749
|
];
|
|
15706
15750
|
var LIVE_CAPABILITIES = [
|
|
15707
15751
|
"subagents",
|
|
@@ -15907,7 +15951,7 @@ var checkDispatchEnvoyTools = async (host2, input, signal) => {
|
|
|
15907
15951
|
});
|
|
15908
15952
|
if (result.code !== 0)
|
|
15909
15953
|
return failed(`dispatch read failed: ${commandFailure(result)}`);
|
|
15910
|
-
return ok(
|
|
15954
|
+
return ok(`${ENVOY_TOOL_NAMES.length} Envoy tools registered; dispatch read ${input.issue} answered from ${whichOnPath("dispatch")}`);
|
|
15911
15955
|
};
|
|
15912
15956
|
var checkGitHub = async (host2, input, signal) => {
|
|
15913
15957
|
const result = await host2.run("gh", ["api", "graphql", "-f", "query={viewer{login}}", "--jq", ".data.viewer.login"], { cwd: input.cwd, signal });
|
|
@@ -16122,33 +16166,10 @@ function ompCapabilityHost() {
|
|
|
16122
16166
|
run: runCommand2
|
|
16123
16167
|
};
|
|
16124
16168
|
}
|
|
16125
|
-
|
|
16126
|
-
// ../pi-shared/src/role-claim-bridge.ts
|
|
16127
|
-
function legionRoleClaimBridge() {
|
|
16128
|
-
return envoyPluginInterface().roleClaim;
|
|
16129
|
-
}
|
|
16130
|
-
async function claimEnvoyRole(sessionID, role, context) {
|
|
16131
|
-
const bridge = legionRoleClaimBridge();
|
|
16132
|
-
bridge.managedSessions.add(sessionID);
|
|
16133
|
-
const instance = bridge.instances.findLast((candidate) => candidate.sessionID() === sessionID) ?? bridge.instances.at(-1);
|
|
16134
|
-
if (instance === undefined)
|
|
16135
|
-
throw new Error("Envoy has no bound instance for a role claim");
|
|
16136
|
-
await instance.claim(sessionID, role, context);
|
|
16137
|
-
}
|
|
16138
|
-
async function subscribeLegionNotice(sessionID, topic, context, whileHolding) {
|
|
16139
|
-
const bridge = legionRoleClaimBridge();
|
|
16140
|
-
const instance = bridge.instances.findLast((candidate) => candidate.sessionID() === sessionID) ?? bridge.instances.at(-1);
|
|
16141
|
-
if (instance === undefined)
|
|
16142
|
-
throw new Error("Envoy has no bound instance for a notice subscription");
|
|
16143
|
-
await instance.subscribe(sessionID, topic, context, whileHolding);
|
|
16144
|
-
}
|
|
16145
|
-
function onEnvoyRoleRegained(listener) {
|
|
16146
|
-
legionRoleClaimBridge().regained = listener;
|
|
16147
|
-
}
|
|
16148
16169
|
// package.json
|
|
16149
16170
|
var package_default = {
|
|
16150
16171
|
name: "@sjawhar/pi-legion",
|
|
16151
|
-
version: "10.
|
|
16172
|
+
version: "10.5.0",
|
|
16152
16173
|
license: "Apache-2.0",
|
|
16153
16174
|
type: "module",
|
|
16154
16175
|
omp: {
|
|
@@ -16160,7 +16181,7 @@ var package_default = {
|
|
|
16160
16181
|
]
|
|
16161
16182
|
},
|
|
16162
16183
|
legion: {
|
|
16163
|
-
daemonApiVersion:
|
|
16184
|
+
daemonApiVersion: 29
|
|
16164
16185
|
},
|
|
16165
16186
|
repository: {
|
|
16166
16187
|
type: "git",
|
|
@@ -16841,6 +16862,26 @@ function createClaimSession(deps) {
|
|
|
16841
16862
|
};
|
|
16842
16863
|
}
|
|
16843
16864
|
|
|
16865
|
+
// src/slack-channels.ts
|
|
16866
|
+
function daemonLaunchedController(env) {
|
|
16867
|
+
return env.LEGION_CONTROLLER === "1" && env.LEGION_TREE === undefined && (env.LEGION_BOOT_TOKEN_FILE !== undefined || env.LEGION_BOOT_TOKEN !== undefined);
|
|
16868
|
+
}
|
|
16869
|
+
function daemonControllerSlackChannels(env) {
|
|
16870
|
+
const { LEGION_SLACK_CHANNELS: named } = env;
|
|
16871
|
+
if (named === undefined) {
|
|
16872
|
+
throw new Error("LEGION_SLACK_CHANNELS is required for the controller the daemon launches: the project's slack_channels, comma-separated, empty for none");
|
|
16873
|
+
}
|
|
16874
|
+
if (named.trim() === "")
|
|
16875
|
+
return [];
|
|
16876
|
+
return named.split(",").map((entry) => {
|
|
16877
|
+
const channel = entry.trim();
|
|
16878
|
+
if (!SLACK_CHANNEL_ID_PATTERN.test(channel)) {
|
|
16879
|
+
throw new Error(`LEGION_SLACK_CHANNELS entry ${JSON.stringify(channel)} is not a Slack channel id (C or G, then capital letters and digits)`);
|
|
16880
|
+
}
|
|
16881
|
+
return channel;
|
|
16882
|
+
});
|
|
16883
|
+
}
|
|
16884
|
+
|
|
16844
16885
|
// src/controller-session.ts
|
|
16845
16886
|
function createControllerSession(deps) {
|
|
16846
16887
|
const { daemon, persistedTranscript, pi } = deps;
|
|
@@ -16850,7 +16891,7 @@ function createControllerSession(deps) {
|
|
|
16850
16891
|
let startMessageSent = false;
|
|
16851
16892
|
let controllerTranscript;
|
|
16852
16893
|
const claim = async (context) => {
|
|
16853
|
-
const supervised =
|
|
16894
|
+
const supervised = daemonLaunchedController(process.env);
|
|
16854
16895
|
if (!supervised)
|
|
16855
16896
|
return claimWith(context, false);
|
|
16856
16897
|
try {
|
|
@@ -16870,6 +16911,7 @@ function createControllerSession(deps) {
|
|
|
16870
16911
|
if (launched)
|
|
16871
16912
|
recordBootstrappedSession(sessionFile);
|
|
16872
16913
|
const project = requiredEnvironment(process.env, "LEGION_PROJECT");
|
|
16914
|
+
const slackChannels = supervised ? daemonControllerSlackChannels(process.env) : [];
|
|
16873
16915
|
const client = daemon();
|
|
16874
16916
|
const { daemon: served } = await client.state();
|
|
16875
16917
|
const servedRole = controllerToken(legionProjectToken(served.project, "the daemon's project"));
|
|
@@ -16898,6 +16940,9 @@ function createControllerSession(deps) {
|
|
|
16898
16940
|
startMessageSent = true;
|
|
16899
16941
|
}
|
|
16900
16942
|
await subscribeLegionNotice(sessionID, legionControllerNoticeSubject(project), envoyContext, registration.claimToken);
|
|
16943
|
+
for (const channel of slackChannels) {
|
|
16944
|
+
await subscribeLegionNotice(sessionID, slackMentionTopic(channel), envoyContext, registration.claimToken);
|
|
16945
|
+
}
|
|
16901
16946
|
if (supervised) {
|
|
16902
16947
|
await client.ready({
|
|
16903
16948
|
claimToken: registration.claimToken,
|
|
@@ -17464,6 +17509,9 @@ function legionExtension(pi) {
|
|
|
17464
17509
|
from: import.meta.url,
|
|
17465
17510
|
envoyInterface: ENVOY_PLUGIN_INTERFACE_VERSION
|
|
17466
17511
|
};
|
|
17512
|
+
if (daemonLaunchedController(process.env)) {
|
|
17513
|
+
legionRoleClaimBridge().slackRoleOwned = true;
|
|
17514
|
+
}
|
|
17467
17515
|
const checkSubagentSession = subagentSessionCheck();
|
|
17468
17516
|
let phaseStall = "closed";
|
|
17469
17517
|
const phaseWorkerSession = (context) => {
|
|
@@ -404,7 +404,8 @@ priority first, then board rank ([Keeping the slots full](#keeping-the-slots-ful
|
|
|
404
404
|
| `todo on <KEY>` from the Go daemon (payload `{kind: "todo"}`) | an issue not handed to Legion that changed while in `todo` and a slot stood free, sent half a minute later | Verify a free slot, then walk the whole `todo` list ([Keeping the slots full](#keeping-the-slots-full-go-daemon)) |
|
|
405
405
|
| `tick on <PROJECT>` from the Go daemon (payload `{kind: "tick", openCapabilities?: [<name>, …]}`) | the project key; the daemon's periodic wake, whatever the slots; `openCapabilities` names the deployment capabilities with no decision, each with its detail and `capabilities.decided.<name>` line under `capabilities` in `read_state`, and the live capabilities a running session's check failed, each with the detail naming the session (pod or pane), role, issue and fact, and no `configLine` | Recheck the trees waiting on a claim, then walk if a slot is free — a gap never stops the walk; post the day's report if this is the day's first turn, naming each open capability in it (the operator closes or decides a deployment row; the operator or the issue's architect fixes a live row's session; you take no other action) |
|
|
406
406
|
| Architect escalation (controller-actionable only: re-file a child as a root issue, capacity, cross-tree conflicts) | request + context | Judge and act; the owning architect writes an issue-design decision as a decision block and opens `dispatch ask` only for a human to-do |
|
|
407
|
-
| Mention |
|
|
407
|
+
| Mention | GitHub PR @mention text | Answer, or route to the owning issue's architect role |
|
|
408
|
+
| Slack mention (`from: slack`) | the mention's `channel_id`, `user_id`, `text`, `ts`, `thread_ts` | Nothing when your `Slack channels:` line does not name its `channel_id`; otherwise answer in its thread with `slack_post`: a state question from `read_state`, a work request filed as a Dispatch issue, an architect's question routed to that architect ([Slack mentions](skill://legion-controller/references/slack-mentions.md)) |
|
|
408
409
|
| `held on <KEY>` from the Go daemon (payload `{kind: "held", phase, role?, reason?}`) | the held issue, the phase it left, and the role whose claim failed, or `reason: "escalated"` | Verify the hold in `read_state` (the issue's phase is `held`). Without `reason`, a phase worker's launches or prompts ran out and the tree's architect decides retry or escalate: no action. With `reason: "escalated"` (on the record, `issues.<KEY>.holdReason` is `escalated`), the architect sent it to you: handle it as an architect escalation below. Parking the tree is `set_status` of the root to `backlog`; setting the root back to `todo` later re-admits it as a new generation, which starts again from its architect |
|
|
409
410
|
| `worker-died on <KEY>` from the Go daemon with `role: "architect"` | the tree root whose architect's claim failed, and the phase the root was in | The tree's architect ran out of launches or prompts and the daemon relaunches nothing; every other notice of the tree goes to that architect, so nobody inside the tree can act. Verify in `read_state` (`issues.<KEY>.architect.state` is `failed`); if the root's phase is `done`, the tree is already parked: no action. Otherwise re-admit the tree (`set_status` of the root to `backlog`, then to `todo`: a new generation, whose architect starts again with fresh budgets) or leave it parked and say why on the issue |
|
|
410
411
|
| Direct user message | — | Always first |
|
|
@@ -482,12 +483,13 @@ judgments.
|
|
|
482
483
|
|
|
483
484
|
## Mentions
|
|
484
485
|
|
|
485
|
-
Read the mention and its artifact
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
gate
|
|
491
|
-
|
|
492
|
-
|
|
486
|
+
Read the mention and its artifact; a Slack mention follows [Slack
|
|
487
|
+
mentions](skill://legion-controller/references/slack-mentions.md). Answer it when it asks the
|
|
488
|
+
controller for triage or human-facing information. A human asking how to let a root proceed past
|
|
489
|
+
its design gate approves the root issue's spec document in Dispatch — the `Approve` control in the
|
|
490
|
+
document's header, or the approval question the architect's request opened in the Inbox. The
|
|
491
|
+
controller never opens a gate and there is no operator command for it; a project that does not
|
|
492
|
+
want the gate at all runs `gates.design: off` in its `legion.yaml`. Otherwise resolve the
|
|
493
|
+
authoritative owning architect role and route the verified context with `envoy_publish`. Do not
|
|
494
|
+
route raw event traffic or invent a role token from a partial issue reference.
|
|
493
495
|
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Slack mentions
|
|
2
|
+
|
|
3
|
+
A person who mentions the Legion Slack app in one of the project's Slack channels is talking to
|
|
4
|
+
you, and you answer in that thread. The `Slack channels:` line of your system prompt names each
|
|
5
|
+
channel `legion.yaml` gives the project (`projects.<KEY>.slack_channels`). Only the controller the
|
|
6
|
+
daemon launches has one: the Slack bot token is the Legion daemon service's, which the secrets
|
|
7
|
+
broker grants to that service's own pods alone. With no such line the project has no channel, and
|
|
8
|
+
nothing here applies.
|
|
9
|
+
|
|
10
|
+
## Your session subscribes for you
|
|
11
|
+
|
|
12
|
+
Your session hears each listed channel's mentions by itself, on
|
|
13
|
+
`notifications.slack.*.<channel>.mention`, for as long as it holds the controller role: a resumed or
|
|
14
|
+
relaunched session hears the channels its launch names and no others, and drops every Slack topic an
|
|
15
|
+
earlier session subscribed to by hand. Never `envoy_subscribe` a Slack topic yourself.
|
|
16
|
+
|
|
17
|
+
A delivery from Slack whose `channel_id` the line does not name is not yours: do nothing with it.
|
|
18
|
+
|
|
19
|
+
A mention published while no controller ran never arrives: Envoy hands a session no copy of an
|
|
20
|
+
event from before it subscribed. Nothing tells you about one, so there is nothing to catch up. When
|
|
21
|
+
the project's `slack_channels` change, the daemon relaunches you into this session with the new
|
|
22
|
+
line.
|
|
23
|
+
|
|
24
|
+
## What a mention carries
|
|
25
|
+
|
|
26
|
+
The delivery's `message` holds Slack's fields: `channel_id`, `user_id` (a Slack user id such as
|
|
27
|
+
`U0123ABCD`), `text` (the mention itself appears in it as `<@U…>`, the app's own id), `ts` (the
|
|
28
|
+
mention's own timestamp), `thread_ts` (the thread's root, only when the mention was posted inside a
|
|
29
|
+
thread), and `bot_id` and `bot_name` when a bot posted it.
|
|
30
|
+
|
|
31
|
+
## Answer in the thread
|
|
32
|
+
|
|
33
|
+
Every answer is one `slack_post` call:
|
|
34
|
+
|
|
35
|
+
- `channel` is the mention's `channel_id`.
|
|
36
|
+
- `thread_ts` is the mention's `thread_ts`, or its `ts` when it has none. Either keeps the reply in
|
|
37
|
+
the thread the mention opened or joined, never at the channel's top level.
|
|
38
|
+
- `text` is what you say, at most 4,000 characters, written as `skill://dispatch`'s "Writing for
|
|
39
|
+
the human" says. Address the author as `<@user_id>` (by `bot_name` when a bot posted the mention
|
|
40
|
+
with no `user_id`) and give every issue as its key, its title and its Dispatch link exactly as
|
|
41
|
+
`dispatch` printed it (the `Link:` line of `dispatch read`, the link on a `Created` line or a
|
|
42
|
+
search hit): it points at Dispatch's dashboard, and `slack_post` refuses a reply linking an
|
|
43
|
+
address inside the cluster. Never build a link yourself. Say only what Dispatch shows: an issue's
|
|
44
|
+
assignee is the one `dispatch` names, never one you assume.
|
|
45
|
+
|
|
46
|
+
`slack_post` is sent once and never retried. When it answers that Slack did not answer, the reply
|
|
47
|
+
may already be in the thread: do not post it again. When it answers with Slack's refusal, the
|
|
48
|
+
reply is not posted: `not_in_channel` means the app has not been added to that channel, which only
|
|
49
|
+
a person in the workspace can fix, so say so in your next report rather than retrying. When it
|
|
50
|
+
answers that the secrets broker has not granted `LEGION_SLACK_BOT_TOKEN` (its request waits for
|
|
51
|
+
approval, or was denied), nothing was posted and no reply can be until the operator stores the
|
|
52
|
+
token with owner `legion-daemon` and tier `agent`: name the error in your next report.
|
|
53
|
+
|
|
54
|
+
## Decide what the person wants
|
|
55
|
+
|
|
56
|
+
Read the mention's `text`. When it names an issue key, read the issue with `dispatch read` before
|
|
57
|
+
answering. Then take the first of these that fits.
|
|
58
|
+
|
|
59
|
+
- **A question about state.** "What is Legion working on?", "Where is ACME-12?", "Why is it held?"
|
|
60
|
+
Answer from `read_state` and Dispatch as they stand now, never from memory: each issue's
|
|
61
|
+
`issues.<KEY>.phase`, its pull request, `admission.active` and `admission.waiting`. Say what you
|
|
62
|
+
read, not what you infer.
|
|
63
|
+
- **A request for work.** Someone wants a change made, a bug fixed, something investigated. Search
|
|
64
|
+
first (`skill://dispatch-first`): when an open issue already tracks it, reply with that issue and
|
|
65
|
+
file nothing. Otherwise file one issue in the project:
|
|
66
|
+
|
|
67
|
+
```text
|
|
68
|
+
dispatch issue --project <PROJECT> --title '<the request, as a title>' --spec-file - <<'EOF'
|
|
69
|
+
## Summary
|
|
70
|
+
|
|
71
|
+
<what the person asked for, in your words, and why if they said>
|
|
72
|
+
|
|
73
|
+
Asked in Slack by <@user_id> in channel <channel_id>, message <ts>.
|
|
74
|
+
EOF
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Add `--label legion` only when the person asks Legion to do the work ("Legion, fix…", "can
|
|
78
|
+
Legion take…"); a request for someone to look at something, or one that does not say who, gets
|
|
79
|
+
no label and waits for a person to hand it over. Never label a request outside the scope the
|
|
80
|
+
deployment instructions state. Reply with the new issue's key, its title, the link and assignee
|
|
81
|
+
the `Created` line names (Dispatch assigns the issue to your token's owner when you name nobody),
|
|
82
|
+
and whether Legion will take it.
|
|
83
|
+
- **Something an issue's architect owns.** A question about a tree's design, a change of direction,
|
|
84
|
+
a decision only the tree's architect can make about an issue Legion runs. Find the tree: follow
|
|
85
|
+
`Links:` up through each `child_of` parent with `dispatch read` until you reach the issue
|
|
86
|
+
`read_state` records with an `architect`. When `issues.<ROOT>.architect.locator.claim` is there,
|
|
87
|
+
`envoy_publish` the person's words to `notifications.role.` followed by it, with the channel id
|
|
88
|
+
and `thread_ts` you reply on, and reply in the thread that you passed it to the architect that
|
|
89
|
+
owns `<ROOT>`, which answers on the issue. When the architect is not running (no locator), say
|
|
90
|
+
that in the thread instead and what `read_state` shows for the tree. Never invent a role token.
|
|
91
|
+
- **Anything else** (a greeting, a question about Legion itself): answer it briefly in the thread.
|
|
92
|
+
|
|
93
|
+
A request to approve a design is answered as the skill's "Mentions" section says: a person
|
|
94
|
+
approves the spec in Dispatch, and you never open a gate.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjawhar/pi-legion",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.5.0",
|
|
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":
|
|
15
|
+
"daemonApiVersion": 29
|
|
16
16
|
},
|
|
17
17
|
"repository": {
|
|
18
18
|
"type": "git",
|