@sjawhar/opencode-legion-envoy 5.5.2 → 5.6.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.
@@ -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
- const results = search.results;
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}` : `showing ${offset + 1}-${offset + rows.length} 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/opencode-legion-envoy",
3
- "version": "5.5.2",
3
+ "version": "5.6.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "main": "dist/src/server.js",
@@ -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`. It returns the best `limit` hits (20
166
- by default, 50 at most) across issues, documents, comments, asks, and messages. A query over
167
- 1,000 characters is refused before it is sent: search with the few words `skill://dispatch-first`
168
- describes, never a pasted passage. Issue-owned hit lines start with the issue key; standalone
169
- project-document hit lines start with `dispatch://PROJECT/artifact/<slug>`, followed by the
170
- absolute link. Cite the hit you build on (`dispatch://KEY` or the document reference), or state
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):