@sjawhar/opencode-legion-envoy 3.19.3 → 3.19.4

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.
Files changed (2) hide show
  1. package/dist/src/server.js +124 -23
  2. package/package.json +1 -1
@@ -15144,6 +15144,7 @@ class DispatchServiceError extends Error {
15144
15144
  current;
15145
15145
  mismatches;
15146
15146
  name = "DispatchServiceError";
15147
+ fromDispatch = true;
15147
15148
  constructor(code, status, message, candidates, current, mismatches) {
15148
15149
  super(message);
15149
15150
  this.code = code;
@@ -15153,6 +15154,21 @@ class DispatchServiceError extends Error {
15153
15154
  this.mismatches = mismatches;
15154
15155
  }
15155
15156
  }
15157
+
15158
+ class DispatchGatewayError extends DispatchServiceError {
15159
+ answer;
15160
+ advice;
15161
+ fromDispatch = false;
15162
+ transient;
15163
+ mayHaveReachedDispatch;
15164
+ constructor(status, answer, advice, message = `${answer}, so ${advice}.`) {
15165
+ super(`HTTP_${status}`, status, message);
15166
+ this.answer = answer;
15167
+ this.advice = advice;
15168
+ this.transient = transientStatus(status);
15169
+ this.mayHaveReachedDispatch = reachesDispatch(status);
15170
+ }
15171
+ }
15156
15172
  function asErrorShape(value) {
15157
15173
  return typeof value === "object" && value !== null ? value : {};
15158
15174
  }
@@ -15166,7 +15182,7 @@ function requestSignal(signal) {
15166
15182
  }
15167
15183
  function whyNotAPage(answer) {
15168
15184
  if (typeof answer === "string") {
15169
- return "text that is not a JSON page, which looks like a proxy or gateway page rather than " + "Dispatch's own answer, so a retry may succeed.";
15185
+ return `text that is not a JSON page, ${GATEWAY_PAGE}, so a retry may succeed.`;
15170
15186
  }
15171
15187
  let what;
15172
15188
  if (Array.isArray(answer)) {
@@ -15185,6 +15201,53 @@ function whyNotAPage(answer) {
15185
15201
  }
15186
15202
  return `${what}. Retrying will not help: the same request gets the same answer until that ` + "Dispatch is upgraded or fixed.";
15187
15203
  }
15204
+ var GATEWAY_PAGE = "which looks like a proxy or gateway page rather than Dispatch's own answer";
15205
+ function gatewayAdvice(method, status) {
15206
+ if (!transientStatus(status)) {
15207
+ return "a retry gets the same answer until the Dispatch URL, or whatever answers in its place, is fixed";
15208
+ }
15209
+ if (method === "GET")
15210
+ return "a retry may succeed";
15211
+ return reachesDispatch(status) ? "the write may or may not have reached Dispatch: check whether it took effect before retrying it" : "the write did not reach Dispatch, and a retry may succeed";
15212
+ }
15213
+ function transientStatus(status) {
15214
+ return status >= 500 || status === 408 || status === 429;
15215
+ }
15216
+ function reachesDispatch(status) {
15217
+ return status >= 500;
15218
+ }
15219
+ var REDACTED = "[redacted]";
15220
+ var EXCERPT_SCAN_LIMIT = 64 * 1024;
15221
+ var BEARER_PIECE = 8;
15222
+ function redactBearer(text, bearer) {
15223
+ if (bearer === "")
15224
+ return text.slice(0, EXCERPT_SCAN_LIMIT);
15225
+ const length = Math.min(BEARER_PIECE, bearer.length);
15226
+ const scanned = text.slice(0, EXCERPT_SCAN_LIMIT + length - 1);
15227
+ const covered = new Uint8Array(scanned.length);
15228
+ const pieces = new Set;
15229
+ for (let at = 0;at + length <= bearer.length; at++)
15230
+ pieces.add(bearer.slice(at, at + length));
15231
+ for (const piece of pieces) {
15232
+ let found = scanned.indexOf(piece);
15233
+ while (found !== -1) {
15234
+ covered.fill(1, found, found + length);
15235
+ found = scanned.indexOf(piece, found + 1);
15236
+ }
15237
+ }
15238
+ let redacted = "";
15239
+ let kept = 0;
15240
+ for (let start = covered.indexOf(1);start !== -1; start = covered.indexOf(1, kept)) {
15241
+ redacted += `${scanned.slice(kept, start)}${REDACTED}`;
15242
+ const end = covered.indexOf(0, start);
15243
+ kept = end === -1 ? scanned.length : end;
15244
+ }
15245
+ return `${redacted}${scanned.slice(kept, EXCERPT_SCAN_LIMIT)}`.slice(0, EXCERPT_SCAN_LIMIT);
15246
+ }
15247
+ function excerpt(text, token) {
15248
+ const plain = redactBearer(text, token.trim()).replace(/<(script|style)\b[^<>]*>(?:[\s\S]*?<\/\1\s*>|[\s\S]*$)/gi, " ").replace(/<[^<>]*>/g, " ");
15249
+ return textHead(plain.replace(/(\bauthorization["']?\s*[:=]\s*["']?(?:(?:bearer|basic|digest|token)\s+)?)[^\s"'<>,;]+/gi, `$1${REDACTED}`).replace(/(\bbearer\s+)[^\s"'<>,;]+/gi, `$1${REDACTED}`));
15250
+ }
15188
15251
 
15189
15252
  class DispatchClient {
15190
15253
  token;
@@ -15442,22 +15505,24 @@ class DispatchClient {
15442
15505
  };
15443
15506
  if (body !== undefined)
15444
15507
  headers["Content-Type"] = "application/json";
15445
- const response = await this.fetchImpl(this.#url(path2, query), {
15508
+ const url2 = this.#url(path2, query);
15509
+ const response = await this.fetchImpl(url2, {
15446
15510
  method,
15447
15511
  headers,
15448
15512
  signal: this.#signal,
15449
15513
  ...body === undefined ? {} : { body: JSON.stringify(body) }
15450
15514
  });
15451
- return this.#response(response);
15515
+ return this.#response(method, url2, response);
15452
15516
  }
15453
15517
  async#form(method, path2, body) {
15454
- const response = await this.fetchImpl(this.#url(path2), {
15518
+ const url2 = this.#url(path2);
15519
+ const response = await this.fetchImpl(url2, {
15455
15520
  method,
15456
15521
  headers: { Accept: "application/json", Authorization: `Bearer ${this.token}` },
15457
15522
  body,
15458
15523
  signal: this.#signal
15459
15524
  });
15460
- return this.#response(response);
15525
+ return this.#response(method, url2, response);
15461
15526
  }
15462
15527
  #url(path2, query) {
15463
15528
  const url2 = new URL(`${path2.map((segment) => encodeURIComponent(segment)).join("/")}`, `${this.#baseUrl}/`);
@@ -15473,7 +15538,7 @@ class DispatchClient {
15473
15538
  }
15474
15539
  return url2.toString();
15475
15540
  }
15476
- async#response(response) {
15541
+ async#response(method, url2, response) {
15477
15542
  const text = await response.text();
15478
15543
  let payload = text;
15479
15544
  if (text && isJson(response)) {
@@ -15485,7 +15550,23 @@ class DispatchClient {
15485
15550
  }
15486
15551
  if (!response.ok) {
15487
15552
  const error48 = asErrorShape(payload);
15488
- throw new DispatchServiceError(error48.code ?? `HTTP_${response.status}`, response.status, error48.error ?? (typeof payload === "string" && payload ? payload : response.statusText), error48.candidates, error48.current, error48.mismatches);
15553
+ if (typeof error48.error === "string") {
15554
+ throw new DispatchServiceError(error48.code ?? `HTTP_${response.status}`, response.status, error48.error, error48.candidates, error48.current, error48.mismatches);
15555
+ }
15556
+ const quoted = excerpt(text, this.token);
15557
+ let body;
15558
+ if (quoted !== "") {
15559
+ body = `a body that is not Dispatch's error JSON (${JSON.stringify(quoted)})`;
15560
+ } else if (text === "") {
15561
+ body = "an empty body";
15562
+ } else {
15563
+ const within = text.length > EXCERPT_SCAN_LIMIT ? ` in its first ${EXCERPT_SCAN_LIMIT / 1024} KiB` : "";
15564
+ const size = Buffer.byteLength(text).toLocaleString("en-US");
15565
+ body = `a body of ${size} bytes and no readable text${within}`;
15566
+ }
15567
+ const reasonPhrase = excerpt(response.statusText, this.token);
15568
+ const reason = reasonPhrase === "" ? "" : ` ${reasonPhrase}`;
15569
+ throw new DispatchGatewayError(response.status, `${method} ${url2} answered ${response.status}${reason} with ${body}, ${GATEWAY_PAGE}`, gatewayAdvice(method, response.status));
15489
15570
  }
15490
15571
  return payload;
15491
15572
  }
@@ -16228,7 +16309,7 @@ async function resolveArtifact(client, owner, artifactReference, { canonical = f
16228
16309
  throw new Error("artifact is required for a project document");
16229
16310
  }
16230
16311
  const routed = await client.getProjectArtifact(owner.project, artifactReference).catch((error48) => {
16231
- if (!(error48 instanceof DispatchServiceError) || error48.status !== 404)
16312
+ if (!dispatchAnswered(error48, 404))
16232
16313
  throw error48;
16233
16314
  return;
16234
16315
  });
@@ -16396,12 +16477,12 @@ function referenceLines(edges) {
16396
16477
  if (edges.length === 0)
16397
16478
  return ["- none"];
16398
16479
  return edges.map((edge) => {
16399
- const excerpt = edge.excerpt === undefined ? "" : `${textHead(edge.excerpt.text)} \xB7 `;
16400
- return `- ${edge.kind} ${edge.node.kind} ${edge.node.ref ?? edge.node.id} (${excerpt}${edge.created_at})`;
16480
+ const excerpt2 = edge.excerpt === undefined ? "" : `${textHead(edge.excerpt.text)} \xB7 `;
16481
+ return `- ${edge.kind} ${edge.node.kind} ${edge.node.ref ?? edge.node.id} (${excerpt2}${edge.created_at})`;
16401
16482
  });
16402
16483
  }
16403
16484
  function unavailableReason(error48) {
16404
- return error48 instanceof DispatchServiceError && error48.status === 404 ? "unavailable" : `unavailable: ${messageFor(error48)}`;
16485
+ return dispatchAnswered(error48, 404) ? "unavailable" : `unavailable: ${messageFor(error48)}`;
16405
16486
  }
16406
16487
  async function graphEdges(client, query) {
16407
16488
  try {
@@ -16591,9 +16672,8 @@ async function openArtifactMarks(client, resolved) {
16591
16672
  const marks = asks.filter((ask) => ask.state === "open" && ask.anchor?.artifact_id === resolved.artifact.id).map((ask) => `ask ${ask.id}`);
16592
16673
  const commentsResult = await commentsResultPromise;
16593
16674
  if (commentsResult.status === "rejected") {
16594
- if (commentsResult.reason instanceof DispatchServiceError && commentsResult.reason.status === 404) {
16675
+ if (dispatchAnswered(commentsResult.reason, 404))
16595
16676
  return marks;
16596
- }
16597
16677
  throw commentsResult.reason;
16598
16678
  }
16599
16679
  return [
@@ -16680,10 +16760,30 @@ async function refuseRemovingOpenDecisionBlocks(client, tool, resolved, ops) {
16680
16760
  `));
16681
16761
  }
16682
16762
  function refusalWithCode(error48, suffix = "") {
16763
+ if (error48 instanceof DispatchGatewayError) {
16764
+ let told = error48.message;
16765
+ if (suffix !== "") {
16766
+ told = error48.mayHaveReachedDispatch ? `${error48.answer}${suffix}` : `${error48.answer}, so ${error48.advice}${suffix}`;
16767
+ }
16768
+ return new DispatchGatewayError(error48.status, error48.answer, error48.advice, `${error48.code}: ${told}`);
16769
+ }
16683
16770
  if (!(error48 instanceof DispatchServiceError))
16684
16771
  return error48;
16685
16772
  return new DispatchServiceError(error48.code, error48.status, `${error48.code}: ${error48.message}${suffix}`, error48.candidates, error48.current, error48.mismatches);
16686
16773
  }
16774
+ function dispatchAnswered(error48, status) {
16775
+ return error48 instanceof DispatchServiceError && error48.fromDispatch && error48.status === status;
16776
+ }
16777
+ function writeMayHaveLanded(error48) {
16778
+ if (error48 instanceof DispatchGatewayError)
16779
+ return error48.mayHaveReachedDispatch;
16780
+ return !(error48 instanceof DispatchServiceError) || error48.status >= 500;
16781
+ }
16782
+ function withAccount(error48, account) {
16783
+ const message = messageFor(error48);
16784
+ const told = /[.!?]$/.test(message) ? `${message} ${account.charAt(0).toUpperCase()}${account.slice(1)}` : `${message}; ${account}`;
16785
+ return new Error(told, { cause: error48 });
16786
+ }
16687
16787
  async function executeDispatchTool(input) {
16688
16788
  const configUrl = input.config.url;
16689
16789
  const configToken = input.config.token;
@@ -16853,7 +16953,10 @@ async function executeDispatchTool(input) {
16853
16953
  ref: dispatchChildRef(dispatchIssueRef(issueKey), "message", message.id)
16854
16954
  };
16855
16955
  } catch (error48) {
16856
- throw refusalWithCode(error48, "; the reason was not posted, so the close was not sent");
16956
+ const told = writeMayHaveLanded(error48) ? "the reason may or may not have been posted, and the close was not sent: read the issue's messages before retrying, since retrying this call posts its reason again" : "the reason was not posted, so the close was not sent";
16957
+ if (error48 instanceof DispatchServiceError)
16958
+ throw refusalWithCode(error48, `; ${told}`);
16959
+ throw withAccount(error48, told);
16857
16960
  }
16858
16961
  }
16859
16962
  const linked = before.external_links.map((link) => link.url);
@@ -16872,17 +16975,15 @@ async function executeDispatchTool(input) {
16872
16975
  actor
16873
16976
  });
16874
16977
  } catch (error48) {
16875
- const taken = error48 instanceof DispatchServiceError && error48.status === 500 && newLinks.length > 0 ? `; one of ${newLinks.join(", ")} may already be linked from another issue (a URL links exactly one issue)` : "";
16978
+ const taken = dispatchAnswered(error48, 500) && newLinks.length > 0 ? `; one of ${newLinks.join(", ")} may already be linked from another issue (a URL links exactly one issue)` : "";
16876
16979
  if (closingNote === undefined)
16877
16980
  throw refusalWithCode(error48, taken);
16878
- const refused = error48 instanceof DispatchServiceError && error48.status < 500;
16879
- const posted = `; the reason already landed as message ${closingNote.id} (${closingNote.ref})`;
16880
- const landed = refused ? `${posted} but the issue did not close. Retrying this call posts its reason again, so fix what refused the close, then retry with a reason that points at message ${closingNote.id}` : `${posted}, and the close may or may not have taken effect. Read the issue's status before retrying: done means it closed; otherwise retry with a reason that points at message ${closingNote.id}, since retrying this call posts its reason again`;
16981
+ const posted = `the reason already landed as message ${closingNote.id} (${closingNote.ref})`;
16982
+ const fix = error48 instanceof DispatchGatewayError && error48.transient ? "" : "fix what refused the close, then ";
16983
+ const landed = writeMayHaveLanded(error48) ? `${posted}, and the close may or may not have taken effect. Read the issue's status before retrying: done means it closed; otherwise retry with a reason that points at message ${closingNote.id}, since retrying this call posts its reason again` : `${posted} but the issue did not close. Retrying this call posts its reason again, so ${fix}retry with a reason that points at message ${closingNote.id}`;
16881
16984
  if (error48 instanceof DispatchServiceError)
16882
- throw refusalWithCode(error48, taken + landed);
16883
- throw new Error(`${error48 instanceof Error ? error48.message : String(error48)}${landed}`, {
16884
- cause: error48
16885
- });
16985
+ throw refusalWithCode(error48, `${taken}; ${landed}`);
16986
+ throw withAccount(error48, landed);
16886
16987
  }
16887
16988
  const linkCount = `(${after.external_links.length} ${after.external_links.length === 1 ? "link" : "links"})`;
16888
16989
  const changes = [
@@ -17486,7 +17587,7 @@ async function resolveExistingIssue(client, issueReference) {
17486
17587
  try {
17487
17588
  return await client.resolveIssue(issueReference);
17488
17589
  } catch (error48) {
17489
- if (error48 instanceof DispatchServiceError && error48.status === 404) {
17590
+ if (dispatchAnswered(error48, 404)) {
17490
17591
  throw new Error(`no Dispatch issue is linked to ${issueReference}; create it first with ` + `dispatch_issue({ external: "${issueReference}", ... })`);
17491
17592
  }
17492
17593
  throw error48;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/opencode-legion-envoy",
3
- "version": "3.19.3",
3
+ "version": "3.19.4",
4
4
  "type": "module",
5
5
  "main": "dist/src/server.js",
6
6
  "exports": {