@sjawhar/pi-legion-envoy 5.30.4 → 5.30.5

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 (3) hide show
  1. package/dist/envoy.js +124 -23
  2. package/dist/legion.js +125 -24
  3. package/package.json +1 -1
package/dist/envoy.js CHANGED
@@ -32463,6 +32463,7 @@ class DispatchServiceError extends Error {
32463
32463
  current;
32464
32464
  mismatches;
32465
32465
  name = "DispatchServiceError";
32466
+ fromDispatch = true;
32466
32467
  constructor(code, status, message, candidates, current, mismatches) {
32467
32468
  super(message);
32468
32469
  this.code = code;
@@ -32472,6 +32473,21 @@ class DispatchServiceError extends Error {
32472
32473
  this.mismatches = mismatches;
32473
32474
  }
32474
32475
  }
32476
+
32477
+ class DispatchGatewayError extends DispatchServiceError {
32478
+ answer;
32479
+ advice;
32480
+ fromDispatch = false;
32481
+ transient;
32482
+ mayHaveReachedDispatch;
32483
+ constructor(status, answer, advice, message = `${answer}, so ${advice}.`) {
32484
+ super(`HTTP_${status}`, status, message);
32485
+ this.answer = answer;
32486
+ this.advice = advice;
32487
+ this.transient = transientStatus(status);
32488
+ this.mayHaveReachedDispatch = reachesDispatch(status);
32489
+ }
32490
+ }
32475
32491
  function asErrorShape(value) {
32476
32492
  return typeof value === "object" && value !== null ? value : {};
32477
32493
  }
@@ -32485,7 +32501,7 @@ function requestSignal(signal) {
32485
32501
  }
32486
32502
  function whyNotAPage(answer) {
32487
32503
  if (typeof answer === "string") {
32488
- 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.";
32504
+ return `text that is not a JSON page, ${GATEWAY_PAGE}, so a retry may succeed.`;
32489
32505
  }
32490
32506
  let what;
32491
32507
  if (Array.isArray(answer)) {
@@ -32504,6 +32520,53 @@ function whyNotAPage(answer) {
32504
32520
  }
32505
32521
  return `${what}. Retrying will not help: the same request gets the same answer until that ` + "Dispatch is upgraded or fixed.";
32506
32522
  }
32523
+ var GATEWAY_PAGE = "which looks like a proxy or gateway page rather than Dispatch's own answer";
32524
+ function gatewayAdvice(method, status) {
32525
+ if (!transientStatus(status)) {
32526
+ return "a retry gets the same answer until the Dispatch URL, or whatever answers in its place, is fixed";
32527
+ }
32528
+ if (method === "GET")
32529
+ return "a retry may succeed";
32530
+ 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";
32531
+ }
32532
+ function transientStatus(status) {
32533
+ return status >= 500 || status === 408 || status === 429;
32534
+ }
32535
+ function reachesDispatch(status) {
32536
+ return status >= 500;
32537
+ }
32538
+ var REDACTED = "[redacted]";
32539
+ var EXCERPT_SCAN_LIMIT = 64 * 1024;
32540
+ var BEARER_PIECE = 8;
32541
+ function redactBearer(text, bearer) {
32542
+ if (bearer === "")
32543
+ return text.slice(0, EXCERPT_SCAN_LIMIT);
32544
+ const length = Math.min(BEARER_PIECE, bearer.length);
32545
+ const scanned = text.slice(0, EXCERPT_SCAN_LIMIT + length - 1);
32546
+ const covered = new Uint8Array(scanned.length);
32547
+ const pieces = new Set;
32548
+ for (let at = 0;at + length <= bearer.length; at++)
32549
+ pieces.add(bearer.slice(at, at + length));
32550
+ for (const piece of pieces) {
32551
+ let found = scanned.indexOf(piece);
32552
+ while (found !== -1) {
32553
+ covered.fill(1, found, found + length);
32554
+ found = scanned.indexOf(piece, found + 1);
32555
+ }
32556
+ }
32557
+ let redacted = "";
32558
+ let kept = 0;
32559
+ for (let start = covered.indexOf(1);start !== -1; start = covered.indexOf(1, kept)) {
32560
+ redacted += `${scanned.slice(kept, start)}${REDACTED}`;
32561
+ const end = covered.indexOf(0, start);
32562
+ kept = end === -1 ? scanned.length : end;
32563
+ }
32564
+ return `${redacted}${scanned.slice(kept, EXCERPT_SCAN_LIMIT)}`.slice(0, EXCERPT_SCAN_LIMIT);
32565
+ }
32566
+ function excerpt(text, token) {
32567
+ const plain = redactBearer(text, token.trim()).replace(/<(script|style)\b[^<>]*>(?:[\s\S]*?<\/\1\s*>|[\s\S]*$)/gi, " ").replace(/<[^<>]*>/g, " ");
32568
+ return textHead(plain.replace(/(\bauthorization["']?\s*[:=]\s*["']?(?:(?:bearer|basic|digest|token)\s+)?)[^\s"'<>,;]+/gi, `$1${REDACTED}`).replace(/(\bbearer\s+)[^\s"'<>,;]+/gi, `$1${REDACTED}`));
32569
+ }
32507
32570
 
32508
32571
  class DispatchClient {
32509
32572
  token;
@@ -32761,22 +32824,24 @@ class DispatchClient {
32761
32824
  };
32762
32825
  if (body !== undefined)
32763
32826
  headers["Content-Type"] = "application/json";
32764
- const response = await this.fetchImpl(this.#url(path2, query), {
32827
+ const url2 = this.#url(path2, query);
32828
+ const response = await this.fetchImpl(url2, {
32765
32829
  method,
32766
32830
  headers,
32767
32831
  signal: this.#signal,
32768
32832
  ...body === undefined ? {} : { body: JSON.stringify(body) }
32769
32833
  });
32770
- return this.#response(response);
32834
+ return this.#response(method, url2, response);
32771
32835
  }
32772
32836
  async#form(method, path2, body) {
32773
- const response = await this.fetchImpl(this.#url(path2), {
32837
+ const url2 = this.#url(path2);
32838
+ const response = await this.fetchImpl(url2, {
32774
32839
  method,
32775
32840
  headers: { Accept: "application/json", Authorization: `Bearer ${this.token}` },
32776
32841
  body,
32777
32842
  signal: this.#signal
32778
32843
  });
32779
- return this.#response(response);
32844
+ return this.#response(method, url2, response);
32780
32845
  }
32781
32846
  #url(path2, query) {
32782
32847
  const url2 = new URL(`${path2.map((segment) => encodeURIComponent(segment)).join("/")}`, `${this.#baseUrl}/`);
@@ -32792,7 +32857,7 @@ class DispatchClient {
32792
32857
  }
32793
32858
  return url2.toString();
32794
32859
  }
32795
- async#response(response) {
32860
+ async#response(method, url2, response) {
32796
32861
  const text = await response.text();
32797
32862
  let payload = text;
32798
32863
  if (text && isJson(response)) {
@@ -32804,7 +32869,23 @@ class DispatchClient {
32804
32869
  }
32805
32870
  if (!response.ok) {
32806
32871
  const error48 = asErrorShape(payload);
32807
- 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);
32872
+ if (typeof error48.error === "string") {
32873
+ throw new DispatchServiceError(error48.code ?? `HTTP_${response.status}`, response.status, error48.error, error48.candidates, error48.current, error48.mismatches);
32874
+ }
32875
+ const quoted = excerpt(text, this.token);
32876
+ let body;
32877
+ if (quoted !== "") {
32878
+ body = `a body that is not Dispatch's error JSON (${JSON.stringify(quoted)})`;
32879
+ } else if (text === "") {
32880
+ body = "an empty body";
32881
+ } else {
32882
+ const within = text.length > EXCERPT_SCAN_LIMIT ? ` in its first ${EXCERPT_SCAN_LIMIT / 1024} KiB` : "";
32883
+ const size = Buffer.byteLength(text).toLocaleString("en-US");
32884
+ body = `a body of ${size} bytes and no readable text${within}`;
32885
+ }
32886
+ const reasonPhrase = excerpt(response.statusText, this.token);
32887
+ const reason = reasonPhrase === "" ? "" : ` ${reasonPhrase}`;
32888
+ throw new DispatchGatewayError(response.status, `${method} ${url2} answered ${response.status}${reason} with ${body}, ${GATEWAY_PAGE}`, gatewayAdvice(method, response.status));
32808
32889
  }
32809
32890
  return payload;
32810
32891
  }
@@ -33524,7 +33605,7 @@ async function resolveArtifact(client, owner, artifactReference, { canonical = f
33524
33605
  throw new Error("artifact is required for a project document");
33525
33606
  }
33526
33607
  const routed = await client.getProjectArtifact(owner.project, artifactReference).catch((error48) => {
33527
- if (!(error48 instanceof DispatchServiceError) || error48.status !== 404)
33608
+ if (!dispatchAnswered(error48, 404))
33528
33609
  throw error48;
33529
33610
  return;
33530
33611
  });
@@ -33695,12 +33776,12 @@ function referenceLines(edges) {
33695
33776
  if (edges.length === 0)
33696
33777
  return ["- none"];
33697
33778
  return edges.map((edge) => {
33698
- const excerpt = edge.excerpt === undefined ? "" : `${textHead(edge.excerpt.text)} \xB7 `;
33699
- return `- ${edge.kind} ${edge.node.kind} ${edge.node.ref ?? edge.node.id} (${excerpt}${edge.created_at})`;
33779
+ const excerpt2 = edge.excerpt === undefined ? "" : `${textHead(edge.excerpt.text)} \xB7 `;
33780
+ return `- ${edge.kind} ${edge.node.kind} ${edge.node.ref ?? edge.node.id} (${excerpt2}${edge.created_at})`;
33700
33781
  });
33701
33782
  }
33702
33783
  function unavailableReason(error48) {
33703
- return error48 instanceof DispatchServiceError && error48.status === 404 ? "unavailable" : `unavailable: ${messageFor(error48)}`;
33784
+ return dispatchAnswered(error48, 404) ? "unavailable" : `unavailable: ${messageFor(error48)}`;
33704
33785
  }
33705
33786
  async function graphEdges(client, query) {
33706
33787
  try {
@@ -33890,9 +33971,8 @@ async function openArtifactMarks(client, resolved) {
33890
33971
  const marks = asks.filter((ask) => ask.state === "open" && ask.anchor?.artifact_id === resolved.artifact.id).map((ask) => `ask ${ask.id}`);
33891
33972
  const commentsResult = await commentsResultPromise;
33892
33973
  if (commentsResult.status === "rejected") {
33893
- if (commentsResult.reason instanceof DispatchServiceError && commentsResult.reason.status === 404) {
33974
+ if (dispatchAnswered(commentsResult.reason, 404))
33894
33975
  return marks;
33895
- }
33896
33976
  throw commentsResult.reason;
33897
33977
  }
33898
33978
  return [
@@ -33979,10 +34059,30 @@ async function refuseRemovingOpenDecisionBlocks(client, tool, resolved, ops) {
33979
34059
  `));
33980
34060
  }
33981
34061
  function refusalWithCode(error48, suffix = "") {
34062
+ if (error48 instanceof DispatchGatewayError) {
34063
+ let told = error48.message;
34064
+ if (suffix !== "") {
34065
+ told = error48.mayHaveReachedDispatch ? `${error48.answer}${suffix}` : `${error48.answer}, so ${error48.advice}${suffix}`;
34066
+ }
34067
+ return new DispatchGatewayError(error48.status, error48.answer, error48.advice, `${error48.code}: ${told}`);
34068
+ }
33982
34069
  if (!(error48 instanceof DispatchServiceError))
33983
34070
  return error48;
33984
34071
  return new DispatchServiceError(error48.code, error48.status, `${error48.code}: ${error48.message}${suffix}`, error48.candidates, error48.current, error48.mismatches);
33985
34072
  }
34073
+ function dispatchAnswered(error48, status) {
34074
+ return error48 instanceof DispatchServiceError && error48.fromDispatch && error48.status === status;
34075
+ }
34076
+ function writeMayHaveLanded(error48) {
34077
+ if (error48 instanceof DispatchGatewayError)
34078
+ return error48.mayHaveReachedDispatch;
34079
+ return !(error48 instanceof DispatchServiceError) || error48.status >= 500;
34080
+ }
34081
+ function withAccount(error48, account) {
34082
+ const message = messageFor(error48);
34083
+ const told = /[.!?]$/.test(message) ? `${message} ${account.charAt(0).toUpperCase()}${account.slice(1)}` : `${message}; ${account}`;
34084
+ return new Error(told, { cause: error48 });
34085
+ }
33986
34086
  async function executeDispatchTool(input) {
33987
34087
  const configUrl = input.config.url;
33988
34088
  const configToken = input.config.token;
@@ -34152,7 +34252,10 @@ async function executeDispatchTool(input) {
34152
34252
  ref: dispatchChildRef(dispatchIssueRef(issueKey), "message", message.id)
34153
34253
  };
34154
34254
  } catch (error48) {
34155
- throw refusalWithCode(error48, "; the reason was not posted, so the close was not sent");
34255
+ 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";
34256
+ if (error48 instanceof DispatchServiceError)
34257
+ throw refusalWithCode(error48, `; ${told}`);
34258
+ throw withAccount(error48, told);
34156
34259
  }
34157
34260
  }
34158
34261
  const linked = before.external_links.map((link) => link.url);
@@ -34171,17 +34274,15 @@ async function executeDispatchTool(input) {
34171
34274
  actor
34172
34275
  });
34173
34276
  } catch (error48) {
34174
- 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)` : "";
34277
+ 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)` : "";
34175
34278
  if (closingNote === undefined)
34176
34279
  throw refusalWithCode(error48, taken);
34177
- const refused = error48 instanceof DispatchServiceError && error48.status < 500;
34178
- const posted = `; the reason already landed as message ${closingNote.id} (${closingNote.ref})`;
34179
- 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`;
34280
+ const posted = `the reason already landed as message ${closingNote.id} (${closingNote.ref})`;
34281
+ const fix = error48 instanceof DispatchGatewayError && error48.transient ? "" : "fix what refused the close, then ";
34282
+ 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}`;
34180
34283
  if (error48 instanceof DispatchServiceError)
34181
- throw refusalWithCode(error48, taken + landed);
34182
- throw new Error(`${error48 instanceof Error ? error48.message : String(error48)}${landed}`, {
34183
- cause: error48
34184
- });
34284
+ throw refusalWithCode(error48, `${taken}; ${landed}`);
34285
+ throw withAccount(error48, landed);
34185
34286
  }
34186
34287
  const linkCount = `(${after.external_links.length} ${after.external_links.length === 1 ? "link" : "links"})`;
34187
34288
  const changes = [
@@ -34785,7 +34886,7 @@ async function resolveExistingIssue(client, issueReference) {
34785
34886
  try {
34786
34887
  return await client.resolveIssue(issueReference);
34787
34888
  } catch (error48) {
34788
- if (error48 instanceof DispatchServiceError && error48.status === 404) {
34889
+ if (dispatchAnswered(error48, 404)) {
34789
34890
  throw new Error(`no Dispatch issue is linked to ${issueReference}; create it first with ` + `dispatch_issue({ external: "${issueReference}", ... })`);
34790
34891
  }
34791
34892
  throw error48;
package/dist/legion.js CHANGED
@@ -31308,6 +31308,7 @@ class DispatchServiceError extends Error {
31308
31308
  current;
31309
31309
  mismatches;
31310
31310
  name = "DispatchServiceError";
31311
+ fromDispatch = true;
31311
31312
  constructor(code, status, message, candidates, current, mismatches) {
31312
31313
  super(message);
31313
31314
  this.code = code;
@@ -31317,6 +31318,21 @@ class DispatchServiceError extends Error {
31317
31318
  this.mismatches = mismatches;
31318
31319
  }
31319
31320
  }
31321
+
31322
+ class DispatchGatewayError extends DispatchServiceError {
31323
+ answer;
31324
+ advice;
31325
+ fromDispatch = false;
31326
+ transient;
31327
+ mayHaveReachedDispatch;
31328
+ constructor(status, answer, advice, message = `${answer}, so ${advice}.`) {
31329
+ super(`HTTP_${status}`, status, message);
31330
+ this.answer = answer;
31331
+ this.advice = advice;
31332
+ this.transient = transientStatus(status);
31333
+ this.mayHaveReachedDispatch = reachesDispatch(status);
31334
+ }
31335
+ }
31320
31336
  function asErrorShape(value) {
31321
31337
  return typeof value === "object" && value !== null ? value : {};
31322
31338
  }
@@ -31330,7 +31346,7 @@ function requestSignal(signal) {
31330
31346
  }
31331
31347
  function whyNotAPage(answer) {
31332
31348
  if (typeof answer === "string") {
31333
- 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.";
31349
+ return `text that is not a JSON page, ${GATEWAY_PAGE}, so a retry may succeed.`;
31334
31350
  }
31335
31351
  let what;
31336
31352
  if (Array.isArray(answer)) {
@@ -31349,6 +31365,53 @@ function whyNotAPage(answer) {
31349
31365
  }
31350
31366
  return `${what}. Retrying will not help: the same request gets the same answer until that ` + "Dispatch is upgraded or fixed.";
31351
31367
  }
31368
+ var GATEWAY_PAGE = "which looks like a proxy or gateway page rather than Dispatch's own answer";
31369
+ function gatewayAdvice(method, status) {
31370
+ if (!transientStatus(status)) {
31371
+ return "a retry gets the same answer until the Dispatch URL, or whatever answers in its place, is fixed";
31372
+ }
31373
+ if (method === "GET")
31374
+ return "a retry may succeed";
31375
+ 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";
31376
+ }
31377
+ function transientStatus(status) {
31378
+ return status >= 500 || status === 408 || status === 429;
31379
+ }
31380
+ function reachesDispatch(status) {
31381
+ return status >= 500;
31382
+ }
31383
+ var REDACTED = "[redacted]";
31384
+ var EXCERPT_SCAN_LIMIT = 64 * 1024;
31385
+ var BEARER_PIECE = 8;
31386
+ function redactBearer(text, bearer) {
31387
+ if (bearer === "")
31388
+ return text.slice(0, EXCERPT_SCAN_LIMIT);
31389
+ const length = Math.min(BEARER_PIECE, bearer.length);
31390
+ const scanned = text.slice(0, EXCERPT_SCAN_LIMIT + length - 1);
31391
+ const covered = new Uint8Array(scanned.length);
31392
+ const pieces = new Set;
31393
+ for (let at = 0;at + length <= bearer.length; at++)
31394
+ pieces.add(bearer.slice(at, at + length));
31395
+ for (const piece of pieces) {
31396
+ let found = scanned.indexOf(piece);
31397
+ while (found !== -1) {
31398
+ covered.fill(1, found, found + length);
31399
+ found = scanned.indexOf(piece, found + 1);
31400
+ }
31401
+ }
31402
+ let redacted = "";
31403
+ let kept = 0;
31404
+ for (let start = covered.indexOf(1);start !== -1; start = covered.indexOf(1, kept)) {
31405
+ redacted += `${scanned.slice(kept, start)}${REDACTED}`;
31406
+ const end = covered.indexOf(0, start);
31407
+ kept = end === -1 ? scanned.length : end;
31408
+ }
31409
+ return `${redacted}${scanned.slice(kept, EXCERPT_SCAN_LIMIT)}`.slice(0, EXCERPT_SCAN_LIMIT);
31410
+ }
31411
+ function excerpt(text, token) {
31412
+ const plain = redactBearer(text, token.trim()).replace(/<(script|style)\b[^<>]*>(?:[\s\S]*?<\/\1\s*>|[\s\S]*$)/gi, " ").replace(/<[^<>]*>/g, " ");
31413
+ return textHead(plain.replace(/(\bauthorization["']?\s*[:=]\s*["']?(?:(?:bearer|basic|digest|token)\s+)?)[^\s"'<>,;]+/gi, `$1${REDACTED}`).replace(/(\bbearer\s+)[^\s"'<>,;]+/gi, `$1${REDACTED}`));
31414
+ }
31352
31415
 
31353
31416
  class DispatchClient {
31354
31417
  token;
@@ -31606,22 +31669,24 @@ class DispatchClient {
31606
31669
  };
31607
31670
  if (body !== undefined)
31608
31671
  headers["Content-Type"] = "application/json";
31609
- const response = await this.fetchImpl(this.#url(path2, query), {
31672
+ const url2 = this.#url(path2, query);
31673
+ const response = await this.fetchImpl(url2, {
31610
31674
  method,
31611
31675
  headers,
31612
31676
  signal: this.#signal,
31613
31677
  ...body === undefined ? {} : { body: JSON.stringify(body) }
31614
31678
  });
31615
- return this.#response(response);
31679
+ return this.#response(method, url2, response);
31616
31680
  }
31617
31681
  async#form(method, path2, body) {
31618
- const response = await this.fetchImpl(this.#url(path2), {
31682
+ const url2 = this.#url(path2);
31683
+ const response = await this.fetchImpl(url2, {
31619
31684
  method,
31620
31685
  headers: { Accept: "application/json", Authorization: `Bearer ${this.token}` },
31621
31686
  body,
31622
31687
  signal: this.#signal
31623
31688
  });
31624
- return this.#response(response);
31689
+ return this.#response(method, url2, response);
31625
31690
  }
31626
31691
  #url(path2, query) {
31627
31692
  const url2 = new URL(`${path2.map((segment) => encodeURIComponent(segment)).join("/")}`, `${this.#baseUrl}/`);
@@ -31637,7 +31702,7 @@ class DispatchClient {
31637
31702
  }
31638
31703
  return url2.toString();
31639
31704
  }
31640
- async#response(response) {
31705
+ async#response(method, url2, response) {
31641
31706
  const text = await response.text();
31642
31707
  let payload = text;
31643
31708
  if (text && isJson(response)) {
@@ -31649,7 +31714,23 @@ class DispatchClient {
31649
31714
  }
31650
31715
  if (!response.ok) {
31651
31716
  const error48 = asErrorShape(payload);
31652
- 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);
31717
+ if (typeof error48.error === "string") {
31718
+ throw new DispatchServiceError(error48.code ?? `HTTP_${response.status}`, response.status, error48.error, error48.candidates, error48.current, error48.mismatches);
31719
+ }
31720
+ const quoted = excerpt(text, this.token);
31721
+ let body;
31722
+ if (quoted !== "") {
31723
+ body = `a body that is not Dispatch's error JSON (${JSON.stringify(quoted)})`;
31724
+ } else if (text === "") {
31725
+ body = "an empty body";
31726
+ } else {
31727
+ const within = text.length > EXCERPT_SCAN_LIMIT ? ` in its first ${EXCERPT_SCAN_LIMIT / 1024} KiB` : "";
31728
+ const size = Buffer.byteLength(text).toLocaleString("en-US");
31729
+ body = `a body of ${size} bytes and no readable text${within}`;
31730
+ }
31731
+ const reasonPhrase = excerpt(response.statusText, this.token);
31732
+ const reason = reasonPhrase === "" ? "" : ` ${reasonPhrase}`;
31733
+ throw new DispatchGatewayError(response.status, `${method} ${url2} answered ${response.status}${reason} with ${body}, ${GATEWAY_PAGE}`, gatewayAdvice(method, response.status));
31653
31734
  }
31654
31735
  return payload;
31655
31736
  }
@@ -32392,7 +32473,7 @@ async function resolveArtifact(client, owner, artifactReference, { canonical = f
32392
32473
  throw new Error("artifact is required for a project document");
32393
32474
  }
32394
32475
  const routed = await client.getProjectArtifact(owner.project, artifactReference).catch((error48) => {
32395
- if (!(error48 instanceof DispatchServiceError) || error48.status !== 404)
32476
+ if (!dispatchAnswered(error48, 404))
32396
32477
  throw error48;
32397
32478
  return;
32398
32479
  });
@@ -32563,12 +32644,12 @@ function referenceLines(edges) {
32563
32644
  if (edges.length === 0)
32564
32645
  return ["- none"];
32565
32646
  return edges.map((edge) => {
32566
- const excerpt = edge.excerpt === undefined ? "" : `${textHead(edge.excerpt.text)} \xB7 `;
32567
- return `- ${edge.kind} ${edge.node.kind} ${edge.node.ref ?? edge.node.id} (${excerpt}${edge.created_at})`;
32647
+ const excerpt2 = edge.excerpt === undefined ? "" : `${textHead(edge.excerpt.text)} \xB7 `;
32648
+ return `- ${edge.kind} ${edge.node.kind} ${edge.node.ref ?? edge.node.id} (${excerpt2}${edge.created_at})`;
32568
32649
  });
32569
32650
  }
32570
32651
  function unavailableReason(error48) {
32571
- return error48 instanceof DispatchServiceError && error48.status === 404 ? "unavailable" : `unavailable: ${messageFor(error48)}`;
32652
+ return dispatchAnswered(error48, 404) ? "unavailable" : `unavailable: ${messageFor(error48)}`;
32572
32653
  }
32573
32654
  async function graphEdges(client, query) {
32574
32655
  try {
@@ -32758,9 +32839,8 @@ async function openArtifactMarks(client, resolved) {
32758
32839
  const marks = asks.filter((ask) => ask.state === "open" && ask.anchor?.artifact_id === resolved.artifact.id).map((ask) => `ask ${ask.id}`);
32759
32840
  const commentsResult = await commentsResultPromise;
32760
32841
  if (commentsResult.status === "rejected") {
32761
- if (commentsResult.reason instanceof DispatchServiceError && commentsResult.reason.status === 404) {
32842
+ if (dispatchAnswered(commentsResult.reason, 404))
32762
32843
  return marks;
32763
- }
32764
32844
  throw commentsResult.reason;
32765
32845
  }
32766
32846
  return [
@@ -32847,10 +32927,30 @@ async function refuseRemovingOpenDecisionBlocks(client, tool, resolved, ops) {
32847
32927
  `));
32848
32928
  }
32849
32929
  function refusalWithCode(error48, suffix = "") {
32930
+ if (error48 instanceof DispatchGatewayError) {
32931
+ let told = error48.message;
32932
+ if (suffix !== "") {
32933
+ told = error48.mayHaveReachedDispatch ? `${error48.answer}${suffix}` : `${error48.answer}, so ${error48.advice}${suffix}`;
32934
+ }
32935
+ return new DispatchGatewayError(error48.status, error48.answer, error48.advice, `${error48.code}: ${told}`);
32936
+ }
32850
32937
  if (!(error48 instanceof DispatchServiceError))
32851
32938
  return error48;
32852
32939
  return new DispatchServiceError(error48.code, error48.status, `${error48.code}: ${error48.message}${suffix}`, error48.candidates, error48.current, error48.mismatches);
32853
32940
  }
32941
+ function dispatchAnswered(error48, status) {
32942
+ return error48 instanceof DispatchServiceError && error48.fromDispatch && error48.status === status;
32943
+ }
32944
+ function writeMayHaveLanded(error48) {
32945
+ if (error48 instanceof DispatchGatewayError)
32946
+ return error48.mayHaveReachedDispatch;
32947
+ return !(error48 instanceof DispatchServiceError) || error48.status >= 500;
32948
+ }
32949
+ function withAccount(error48, account) {
32950
+ const message = messageFor(error48);
32951
+ const told = /[.!?]$/.test(message) ? `${message} ${account.charAt(0).toUpperCase()}${account.slice(1)}` : `${message}; ${account}`;
32952
+ return new Error(told, { cause: error48 });
32953
+ }
32854
32954
  async function executeDispatchTool(input) {
32855
32955
  const configUrl = input.config.url;
32856
32956
  const configToken = input.config.token;
@@ -33020,7 +33120,10 @@ async function executeDispatchTool(input) {
33020
33120
  ref: dispatchChildRef(dispatchIssueRef(issueKey), "message", message.id)
33021
33121
  };
33022
33122
  } catch (error48) {
33023
- throw refusalWithCode(error48, "; the reason was not posted, so the close was not sent");
33123
+ 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";
33124
+ if (error48 instanceof DispatchServiceError)
33125
+ throw refusalWithCode(error48, `; ${told}`);
33126
+ throw withAccount(error48, told);
33024
33127
  }
33025
33128
  }
33026
33129
  const linked = before.external_links.map((link) => link.url);
@@ -33039,17 +33142,15 @@ async function executeDispatchTool(input) {
33039
33142
  actor
33040
33143
  });
33041
33144
  } catch (error48) {
33042
- 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)` : "";
33145
+ 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)` : "";
33043
33146
  if (closingNote === undefined)
33044
33147
  throw refusalWithCode(error48, taken);
33045
- const refused = error48 instanceof DispatchServiceError && error48.status < 500;
33046
- const posted = `; the reason already landed as message ${closingNote.id} (${closingNote.ref})`;
33047
- 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`;
33148
+ const posted = `the reason already landed as message ${closingNote.id} (${closingNote.ref})`;
33149
+ const fix = error48 instanceof DispatchGatewayError && error48.transient ? "" : "fix what refused the close, then ";
33150
+ 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}`;
33048
33151
  if (error48 instanceof DispatchServiceError)
33049
- throw refusalWithCode(error48, taken + landed);
33050
- throw new Error(`${error48 instanceof Error ? error48.message : String(error48)}${landed}`, {
33051
- cause: error48
33052
- });
33152
+ throw refusalWithCode(error48, `${taken}; ${landed}`);
33153
+ throw withAccount(error48, landed);
33053
33154
  }
33054
33155
  const linkCount = `(${after.external_links.length} ${after.external_links.length === 1 ? "link" : "links"})`;
33055
33156
  const changes = [
@@ -33653,7 +33754,7 @@ async function resolveExistingIssue(client, issueReference) {
33653
33754
  try {
33654
33755
  return await client.resolveIssue(issueReference);
33655
33756
  } catch (error48) {
33656
- if (error48 instanceof DispatchServiceError && error48.status === 404) {
33757
+ if (dispatchAnswered(error48, 404)) {
33657
33758
  throw new Error(`no Dispatch issue is linked to ${issueReference}; create it first with ` + `dispatch_issue({ external: "${issueReference}", ... })`);
33658
33759
  }
33659
33760
  throw error48;
@@ -33702,7 +33803,7 @@ import { logger } from "@oh-my-pi/pi-utils";
33702
33803
  // package.json
33703
33804
  var package_default = {
33704
33805
  name: "@sjawhar/pi-legion-envoy",
33705
- version: "5.30.4",
33806
+ version: "5.30.5",
33706
33807
  type: "module",
33707
33808
  omp: {
33708
33809
  extensions: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/pi-legion-envoy",
3
- "version": "5.30.4",
3
+ "version": "5.30.5",
4
4
  "type": "module",
5
5
  "omp": {
6
6
  "extensions": [