@sjawhar/opencode-legion-envoy 5.5.2 → 5.6.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/src/server.js
CHANGED
|
@@ -13962,6 +13962,7 @@ var ASK_URGENCIES = ["low", "med", "high", "blocking"];
|
|
|
13962
13962
|
var ASK_QUESTION_MAX = 800;
|
|
13963
13963
|
var SEARCH_QUERY_MAX = 1000;
|
|
13964
13964
|
var SEARCH_QUERY_HINT = "search with a short phrase of a few words, not a passage";
|
|
13965
|
+
var SEARCH_KIND_DEPTH = 100;
|
|
13965
13966
|
var PROJECT_KEY_PATTERN = /^[A-Z][A-Z0-9]{1,9}$/;
|
|
13966
13967
|
var ISSUE_STATUSES = [
|
|
13967
13968
|
"triage",
|
|
@@ -14297,7 +14298,7 @@ var dispatchToolSpecs = [
|
|
|
14297
14298
|
{
|
|
14298
14299
|
name: "dispatch_search",
|
|
14299
14300
|
example: { query: "astrolabe" },
|
|
14300
|
-
description: "Search every issue, document, comment, ask, and message for a keyword or phrase and get deep links. " + "Use it before creating an issue or a design document, and to find where a word was written. " + 'Websearch syntax: "quoted phrase", -excluded, OR.',
|
|
14301
|
+
description: "Search every issue, document, comment, ask, and message for a keyword or phrase and get deep links. " + "Use it before creating an issue or a design document, and to find where a word was written. " + 'Websearch syntax: "quoted phrase", -excluded, OR. ' + "Each kind of content is ranked on its own and the lists are merged, so the top holds the best " + "issue, document, ask, comment and message; an issue key searched alone lists that issue first. " + `The answer names how many results match; offset pages through them, and each kind lists at most its best ${SEARCH_KIND_DEPTH}, ` + "so narrow the query or name a project to reach the rest. Paging is exact only while the " + "corpus holds still: content added, changed, or removed between two offsets can shift rows " + "across a page boundary, so one hit can come back twice and another never.",
|
|
14301
14302
|
arguments: (z2) => ({
|
|
14302
14303
|
query: z2.string({
|
|
14303
14304
|
min: 2,
|
|
@@ -14305,7 +14306,8 @@ var dispatchToolSpecs = [
|
|
|
14305
14306
|
maxHint: SEARCH_QUERY_HINT
|
|
14306
14307
|
}).describe(`Keyword, phrase, or websearch expression; 2 to ${SEARCH_QUERY_MAX} characters.`),
|
|
14307
14308
|
project: z2.string().describe("Optional project key to search within.").optional(),
|
|
14308
|
-
limit: z2.number({ int: true, min: 1, max: 50 }).describe("Maximum results, 1-50; default 20.").optional()
|
|
14309
|
+
limit: z2.number({ int: true, min: 1, max: 50 }).describe("Maximum results, 1-50; default 20.").optional(),
|
|
14310
|
+
offset: z2.number({ int: true, min: 0 }).describe("Results to skip before the page; nonnegative integer; default 0.").optional()
|
|
14309
14311
|
}),
|
|
14310
14312
|
validation: {
|
|
14311
14313
|
check: (value) => {
|
|
@@ -15206,6 +15208,71 @@ function dispatchChildRef(ownerRef, kind, id) {
|
|
|
15206
15208
|
return `${ownerRef}/${kind}/${id}`;
|
|
15207
15209
|
}
|
|
15208
15210
|
|
|
15211
|
+
// ../envoy-client/src/search-answer.ts
|
|
15212
|
+
function pageSummaryText(offset, count, total) {
|
|
15213
|
+
return `showing ${offset + 1}-${offset + count} of ${total}`;
|
|
15214
|
+
}
|
|
15215
|
+
function searchResultLine(result, baseUrl) {
|
|
15216
|
+
const href = new URL(result.href, baseUrl).toString();
|
|
15217
|
+
const { owner } = result;
|
|
15218
|
+
if (owner.kind === "document") {
|
|
15219
|
+
const reference = dispatchDocumentRef(owner.project, owner.slug);
|
|
15220
|
+
return `${reference} [document] ${owner.name} - ${result.kind}: ${snippetText(result.snippet)} -> ${href}`;
|
|
15221
|
+
}
|
|
15222
|
+
const artifactName = result.artifact ? ` ${result.artifact.name}` : "";
|
|
15223
|
+
const label = `${owner.key} [${owner.status}] ${owner.title} - ${result.kind}${artifactName}`;
|
|
15224
|
+
return `${label}: ${snippetText(result.snippet)} -> ${href}`;
|
|
15225
|
+
}
|
|
15226
|
+
function searchAnswer(search, query, offset, configUrl) {
|
|
15227
|
+
const results = search.results;
|
|
15228
|
+
const count = results.length;
|
|
15229
|
+
const lines = results.map((result) => searchResultLine(result, configUrl));
|
|
15230
|
+
const noun = count === 1 ? "result" : "results";
|
|
15231
|
+
const noResults = `No results for "${query}".`;
|
|
15232
|
+
if (typeof search.total !== "number") {
|
|
15233
|
+
if (offset !== undefined && offset > 0) {
|
|
15234
|
+
throw new Error(`Dispatch answered without a total: it predates search paging and ignored offset ${offset}, so this would be its first page again.`);
|
|
15235
|
+
}
|
|
15236
|
+
return {
|
|
15237
|
+
text: count === 0 ? noResults : [`${count} ${noun} for "${query}" (${search.took_ms} ms)`, ...lines].join(`
|
|
15238
|
+
`),
|
|
15239
|
+
details: { query, results }
|
|
15240
|
+
};
|
|
15241
|
+
}
|
|
15242
|
+
const { total, reachable, offset: pageOffset } = search;
|
|
15243
|
+
const end = pageOffset + count;
|
|
15244
|
+
const cut = reachable < total ? `Each kind lists only its best ${SEARCH_KIND_DEPTH} matches, so ${reachable} of the ${total} can be paged to; narrow the query or name a project to reach the rest.` : undefined;
|
|
15245
|
+
const details = {
|
|
15246
|
+
query,
|
|
15247
|
+
results,
|
|
15248
|
+
total,
|
|
15249
|
+
reachable,
|
|
15250
|
+
offset: pageOffset,
|
|
15251
|
+
limit: search.limit
|
|
15252
|
+
};
|
|
15253
|
+
if (count === 0) {
|
|
15254
|
+
return {
|
|
15255
|
+
text: total === 0 ? noResults : [
|
|
15256
|
+
`No results for "${query}" at offset ${pageOffset}: it matches ${total}, and the pages reach the first ${reachable}.`,
|
|
15257
|
+
...cut === undefined ? [] : [cut]
|
|
15258
|
+
].join(`
|
|
15259
|
+
`),
|
|
15260
|
+
details
|
|
15261
|
+
};
|
|
15262
|
+
}
|
|
15263
|
+
const showing = pageOffset === 0 && count === total ? "" : `${pageSummaryText(pageOffset, count, total)}, `;
|
|
15264
|
+
return {
|
|
15265
|
+
text: [
|
|
15266
|
+
`${count} ${noun} for "${query}" (${showing}${search.took_ms} ms)`,
|
|
15267
|
+
...cut === undefined ? [] : [cut],
|
|
15268
|
+
...lines,
|
|
15269
|
+
...end < reachable ? [`Next page: offset ${end}.`] : []
|
|
15270
|
+
].join(`
|
|
15271
|
+
`),
|
|
15272
|
+
details
|
|
15273
|
+
};
|
|
15274
|
+
}
|
|
15275
|
+
|
|
15209
15276
|
// ../envoy-client/src/tool-input-errors.ts
|
|
15210
15277
|
class ToolInputError extends Error {
|
|
15211
15278
|
tool;
|
|
@@ -15515,17 +15582,6 @@ function duplicateCandidates(error48) {
|
|
|
15515
15582
|
throw error48;
|
|
15516
15583
|
return error48.candidates;
|
|
15517
15584
|
}
|
|
15518
|
-
function searchResultLine(result, baseUrl) {
|
|
15519
|
-
const href = new URL(result.href, baseUrl).toString();
|
|
15520
|
-
const { owner } = result;
|
|
15521
|
-
if (owner.kind === "document") {
|
|
15522
|
-
const reference = dispatchDocumentRef(owner.project, owner.slug);
|
|
15523
|
-
return `${reference} [document] ${owner.name} - ${result.kind}: ${snippetText(result.snippet)} -> ${href}`;
|
|
15524
|
-
}
|
|
15525
|
-
const artifactName = result.artifact ? ` ${result.artifact.name}` : "";
|
|
15526
|
-
const label = `${owner.key} [${owner.status}] ${owner.title} - ${result.kind}${artifactName}`;
|
|
15527
|
-
return `${label}: ${snippetText(result.snippet)} -> ${href}`;
|
|
15528
|
-
}
|
|
15529
15585
|
function askUrgency(args) {
|
|
15530
15586
|
const value = args.urgency;
|
|
15531
15587
|
return ASK_URGENCIES.find((urgency) => urgency === value);
|
|
@@ -16721,20 +16777,13 @@ async function executeDispatchTool(input) {
|
|
|
16721
16777
|
const query = stringArg(args, "query");
|
|
16722
16778
|
const project = optionalString(args, "project");
|
|
16723
16779
|
const limit = optionalNumber(args, "limit");
|
|
16780
|
+
const offset = optionalNumber(args, "offset");
|
|
16724
16781
|
const search = await client.search(query, {
|
|
16725
16782
|
...project === undefined ? {} : { project },
|
|
16726
|
-
...limit === undefined ? {} : { limit }
|
|
16783
|
+
...limit === undefined ? {} : { limit },
|
|
16784
|
+
...offset === undefined ? {} : { offset }
|
|
16727
16785
|
});
|
|
16728
|
-
|
|
16729
|
-
const count = results.length;
|
|
16730
|
-
return {
|
|
16731
|
-
text: count === 0 ? `No results for "${query}".` : [
|
|
16732
|
-
`${count} ${count === 1 ? "result" : "results"} for "${query}" (${search.took_ms} ms)`,
|
|
16733
|
-
...results.map((result) => searchResultLine(result, configUrl))
|
|
16734
|
-
].join(`
|
|
16735
|
-
`),
|
|
16736
|
-
details: { query, results }
|
|
16737
|
-
};
|
|
16786
|
+
return searchAnswer(search, query, offset, configUrl);
|
|
16738
16787
|
}
|
|
16739
16788
|
case "dispatch_issues": {
|
|
16740
16789
|
const project = stringArg(args, "project");
|
|
@@ -16774,7 +16823,7 @@ async function executeDispatchTool(input) {
|
|
|
16774
16823
|
}));
|
|
16775
16824
|
const titles = await liveSessionTitles(client, rows.some((row) => holdsSession(row.claim)));
|
|
16776
16825
|
const isPartial = offset !== 0 || rows.length !== total;
|
|
16777
|
-
const showing = !isPartial ? "" : rows.length === 0 ? `showing 0-0 of ${total}` :
|
|
16826
|
+
const showing = !isPartial ? "" : rows.length === 0 ? `showing 0-0 of ${total}` : pageSummaryText(offset, rows.length, total);
|
|
16778
16827
|
return {
|
|
16779
16828
|
text: rows.length === 0 ? `No issues in ${project}.${isPartial ? ` (${showing})` : ""}` : [
|
|
16780
16829
|
`${rows.length} ${rows.length === 1 ? "issue" : "issues"} in ${project}` + (isPartial ? ` (${showing})` : ""),
|
package/package.json
CHANGED
package/skills/dispatch/SKILL.md
CHANGED
|
@@ -160,15 +160,14 @@ follow [Writing a spec](#writing-a-spec).
|
|
|
160
160
|
you plan, start a design document, file an issue, ask, post a finding or start work, and what to
|
|
161
161
|
do with each hit. What it leaves out:
|
|
162
162
|
```ts
|
|
163
|
-
dispatch_search({ query, project?, limit? })
|
|
163
|
+
dispatch_search({ query, project?, limit?, offset? })
|
|
164
164
|
```
|
|
165
|
-
Websearch syntax applies: `"merge queue"`, `-daemon`, `OR`.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
"no prior issue" in the spec.
|
|
165
|
+
Websearch syntax applies: `"merge queue"`, `-daemon`, `OR`. Issues, documents, asks, comments, and messages rank separately; the
|
|
166
|
+
merged page orders each kind's best in turn — issue, document, ask, comment, message — and a bare issue key ranks first. A page
|
|
167
|
+
holds `limit` hits (20 default, 50 max); the first line gives the total (`showing 1-20 of 312`); see
|
|
168
|
+
[Search paging](skill://dispatch/references/issues.md#search-paging) for more. Queries over 1,000 characters are refused: use the
|
|
169
|
+
few words `skill://dispatch-first` names, never a pasted passage. Issue hits start with the issue key; document hits start with
|
|
170
|
+
`dispatch://PROJECT/artifact/<slug>`, then the link. Cite the hit (`dispatch://KEY` or the doc ref) or say "no prior issue".
|
|
172
171
|
|
|
173
172
|
`dispatch_issue` refuses a title that near-duplicates an issue in the same project and returns the candidates (`POSSIBLE_DUPLICATE`).
|
|
174
173
|
Read them; reference the existing issue, or repeat the call with `force: true` when it is genuinely new work.
|
|
@@ -193,6 +193,16 @@ The audit finds four shapes:
|
|
|
193
193
|
Run the audit as a step of a coordinator's loop, at each checkpoint, not as a habit: these shapes
|
|
194
194
|
are found by running the check, not by noticing them.
|
|
195
195
|
|
|
196
|
+
## Search paging
|
|
197
|
+
|
|
198
|
+
`skill://dispatch` sends you here when a `dispatch_search` page is not enough: more hits remain,
|
|
199
|
+
or a kind runs out before the page does.
|
|
200
|
+
|
|
201
|
+
A page holds `limit` hits (20 by default, 50 at most); the first line names how many match
|
|
202
|
+
(`showing 1-20 of 312`), and, while more can be reached, the last line names the next `offset`.
|
|
203
|
+
Each kind lists at most its best 100, so when the result says the rest cannot be paged to, narrow
|
|
204
|
+
the query or name a `project`.
|
|
205
|
+
|
|
196
206
|
## Syncing a project's architecture model
|
|
197
207
|
|
|
198
208
|
Import a project's architecture model from its configured source repository now (a human configures the source in Settings):
|
|
@@ -25,11 +25,11 @@ reply. A human may reply to your message in turn — the follow-up arrives as a
|
|
|
25
25
|
`in_reply_to` names your message and whose `reply_body` quotes it; answer it the same way,
|
|
26
26
|
`dispatch_message({ issue, in_reply_to: "<their reply id>", body })`, so the exchange reads as one
|
|
27
27
|
thread. `dispatch_message` itself never carries `target` or `delivery`: agent-to-agent traffic goes
|
|
28
|
-
through Envoy or
|
|
29
|
-
(`{kind: "session", id}`), and the card shows that session as the author.
|
|
30
|
-
(any authenticated caller) lists live sessions with their capabilities
|
|
31
|
-
target only a session that advertises the mode you want. Sending to a
|
|
32
|
-
(`POST /api/v1/agents/{session_id}/messages`) stays human-only.
|
|
28
|
+
through Envoy or Oh My Pi's `agent://` messages. A bearer that targets over HTTP names its own
|
|
29
|
+
session in `actor` (`{kind: "session", id}`), and the card shows that session as the author.
|
|
30
|
+
`GET /api/v1/agents` (any authenticated caller) lists live sessions with their capabilities
|
|
31
|
+
(`aside`, `btw`, `steer`); target only a session that advertises the mode you want. Sending to a
|
|
32
|
+
session with no issue (`POST /api/v1/agents/{session_id}/messages`) stays human-only.
|
|
33
33
|
|
|
34
34
|
## Answering a direct message
|
|
35
35
|
|
package/skills/envoy/SKILL.md
CHANGED
|
@@ -122,8 +122,9 @@ envoy_send(
|
|
|
122
122
|
|
|
123
123
|
`envoy_whoami`'s `session_id` is the address a reply to you reaches. Inside a `task` subagent it
|
|
124
124
|
is the session that spawned you: a subagent registers no Envoy session of its own, so a peer
|
|
125
|
-
answering it reaches that session, which relays to you
|
|
126
|
-
output carries your own host session id — it is not an address, so
|
|
125
|
+
answering it reaches that session, which relays to you with a `write` to your `agent://` address.
|
|
126
|
+
The `subagent` field in that output carries your own host session id — it is not an address, so
|
|
127
|
+
never hand it to a peer.
|
|
127
128
|
|
|
128
129
|
`session_id` is empty when nothing can be reached: a process that took no Envoy identity, or a
|
|
129
130
|
subagent whose spawning session this process can no longer place (it forked away, and other
|
|
@@ -134,8 +135,8 @@ would send your peers to an agent that never spawned you.
|
|
|
134
135
|
Your own `envoy_publish` never reaches the agent that spawned you. The listener delivers nothing
|
|
135
136
|
to the session a message names as its source, and inside a subagent that source is your parent,
|
|
136
137
|
so a publish to a role it holds — or to any topic it subscribes to — is accepted and delivered to
|
|
137
|
-
nobody. Use
|
|
138
|
-
unaffected.
|
|
138
|
+
nobody. Use a `write` to its `agent://` address for that one hop. `envoy_send` to any other
|
|
139
|
+
session, including a reply, is unaffected.
|
|
139
140
|
|
|
140
141
|
### Delivery capabilities
|
|
141
142
|
|
|
@@ -62,11 +62,11 @@ workflow table. You may still use ordinary `task` subagents for your own phase w
|
|
|
62
62
|
is a Legion role.
|
|
63
63
|
Escalate a product, scope, design, cross-phase, or lifecycle decision to the owning architect with
|
|
64
64
|
`envoy_publish` to its role topic (`notifications.role.` followed by its encoded token, see
|
|
65
|
-
above), carrying the verified facts and the decision needed. `
|
|
66
|
-
inside your own process, not the architect's separate one. Never write a decision block
|
|
67
|
-
spec yourself: the architect decides whether the human must answer it and writes the block,
|
|
68
|
-
a new version of an approved root spec closes the tree's design gate. A standalone to-do
|
|
69
|
-
human can do is a `dispatch_ask`, and its replies return to your own session.
|
|
65
|
+
above), carrying the verified facts and the decision needed. A `write` to `agent://` only reaches
|
|
66
|
+
agents inside your own process, not the architect's separate one. Never write a decision block
|
|
67
|
+
into a spec yourself: the architect decides whether the human must answer it and writes the block,
|
|
68
|
+
since a new version of an approved root spec closes the tree's design gate. A standalone to-do
|
|
69
|
+
only a human can do is a `dispatch_ask`, and its replies return to your own session.
|
|
70
70
|
|
|
71
71
|
Because the same agent is always resumed for its phase, you may receive more than one
|
|
72
72
|
assignment across your lifetime: once the daemon ends your phase it suspends you, and when a later
|
|
@@ -152,9 +152,10 @@ new work.
|
|
|
152
152
|
clone, so they all share one operation log: `jj undo`, `jj abandon`, and
|
|
153
153
|
`jj op restore|revert|abandon|undo` rewrite it for every tree at once. The extension refuses them in every
|
|
154
154
|
phase-worker pane before they run — a `bash` command in any position of a pipeline or `&&`
|
|
155
|
-
chain, with or without `-R`, judged on the whole argument list
|
|
156
|
-
|
|
157
|
-
|
|
155
|
+
chain, with or without `-R`, judged on the whole argument list (a supervised service's start
|
|
156
|
+
included); `eval` code; and stdin written to a service (a `write` to `proc://<id>`) — from your
|
|
157
|
+
own tool calls and from any `task` subagent you spawn (it runs in your pane, against the same
|
|
158
|
+
log), and a `bash` command whose quoted text merely mentions `jj`
|
|
158
159
|
with one of those words (a heredoc, an echo, a commit message) is refused too: write such text
|
|
159
160
|
with the `write` tool or say "operation-log rollback" instead. `jj restore <paths>`,
|
|
160
161
|
`jj op log`, and `jj op show` stay allowed. Recover forward only: a new commit
|