@sjawhar/opencode-legion-envoy 9.1.0 → 9.3.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/dispatch.js CHANGED
@@ -13602,6 +13602,15 @@ var ArtifactVersionEventPayloadSchema = exports_external.object({
13602
13602
  version: exports_external.object({ number: exports_external.number().optional(), summary: exports_external.string().nullish() }).optional(),
13603
13603
  diff: exports_external.string().optional()
13604
13604
  });
13605
+ var ArtifactHumanEditedEventPayloadSchema = exports_external.object({
13606
+ artifact_id: exports_external.string().optional(),
13607
+ name: exports_external.string().optional(),
13608
+ from_version: exports_external.number().int().optional(),
13609
+ to_version: exports_external.number().int().optional(),
13610
+ editors: exports_external.array(exports_external.string()).optional(),
13611
+ diff: exports_external.string().optional(),
13612
+ diff_truncated: exports_external.boolean().optional()
13613
+ });
13605
13614
  var ArtifactReviewEventPayloadSchema = exports_external.object({
13606
13615
  artifact_id: exports_external.string().optional(),
13607
13616
  name: exports_external.string().optional(),
@@ -13857,6 +13866,44 @@ function itemFromSearch(search) {
13857
13866
  }
13858
13867
  return ask === null ? { id, kind: "comment" } : { id, kind: "ask" };
13859
13868
  }
13869
+
13870
+ // ../contracts/src/dispatch-block-ref.ts
13871
+ var encoder = new TextEncoder;
13872
+ function encodeBlockId(id) {
13873
+ let encoded = "";
13874
+ for (const character of id) {
13875
+ for (const byte of encoder.encode(character)) {
13876
+ const bare = byte >= 48 && byte <= 57 || byte >= 65 && byte <= 90 || byte >= 97 && byte <= 122 || byte === 45;
13877
+ encoded += bare ? String.fromCharCode(byte) : `%${byte.toString(16).toUpperCase().padStart(2, "0")}`;
13878
+ }
13879
+ }
13880
+ return encoded;
13881
+ }
13882
+ function decodeBlockId(encoded) {
13883
+ if (encoded === "" || encoded.includes("@")) {
13884
+ return;
13885
+ }
13886
+ let id;
13887
+ try {
13888
+ id = decodeURIComponent(encoded);
13889
+ } catch {
13890
+ return;
13891
+ }
13892
+ return id === "" || hasControlCharacter(id) ? undefined : id;
13893
+ }
13894
+ var MAX_BLOCK_PIN = 9223372036854775807n;
13895
+ function parseBlockFragment(encoded) {
13896
+ const at = encoded.indexOf("@");
13897
+ const pin = at === -1 ? undefined : encoded.slice(at + 1).match(/^v([1-9][0-9]*)$/)?.[1];
13898
+ if (at !== -1 && (pin === undefined || BigInt(pin) > MAX_BLOCK_PIN)) {
13899
+ return;
13900
+ }
13901
+ const block = decodeBlockId(at === -1 ? encoded : encoded.slice(0, at));
13902
+ if (block === undefined) {
13903
+ return;
13904
+ }
13905
+ return pin === undefined ? { block } : { block, pin };
13906
+ }
13860
13907
  // ../contracts/src/dispatch-snippet.ts
13861
13908
  var HTML_ENTITIES = [
13862
13909
  ["&lt;", "<"],
@@ -14129,7 +14176,7 @@ var dispatchToolSpecs = [
14129
14176
  issue: z2.string().describe(ISSUE_REFERENCE).optional(),
14130
14177
  project: z2.string().describe("Project key owning the document.").optional(),
14131
14178
  artifact: z2.string().describe("Project document artifact id, slug, or filename.").optional(),
14132
- ref: z2.string().describe("Optional dispatch:// reference (issue, document, message, or ask); appended to the question and rendered as a link.").optional(),
14179
+ ref: z2.string().describe("Optional dispatch:// reference (issue, document, block, message, or ask); appended to the question and rendered as a link.").optional(),
14133
14180
  question: z2.string({ max: ASK_QUESTION_MAX }).describe(`${ASK_QUESTION_CONTRACT} At most ${ASK_QUESTION_MAX} characters.`),
14134
14181
  options: z2.array(z2.object({
14135
14182
  label: z2.string().describe("Selectable option label."),
@@ -14274,7 +14321,7 @@ var dispatchToolSpecs = [
14274
14321
  ops: [{ op: "delete_column", block: "table-123", index: 1 }],
14275
14322
  precondition: { blocks: [{ id: "table-123", token: "sha256:current-table-token" }] }
14276
14323
  },
14277
- description: "Apply deterministic document edits: replace or delete quoted text, insert markdown at an anchor, retype an identified paragraph or typed block into a schema-declared typed block, delete or move a whole block by its id, or delete a table row or column in place. " + "Do not use it for review feedback or for reading; use dispatch comment, dispatch suggest, or dispatch doc-read instead. " + `For replace, delete, and quote anchors, find text as rendered: inline Markdown (**bold**, \`code\`) is tolerated and must be balanced; a leading '# ' matches a heading at any level. replace is inline: with is the new text of the matched span, so a marker of a different kind from the block's own stays literal text ('4. Design' written into a heading). A with that opens with a marker of the same kind as the matched block's own would write it twice and is INVALID_OP - including prose that merely looks like one ('1999. was a year' into an ordered item), which you write as text by escaping it ('1999\\. was a year'). The exception is a heading rename whose find carried a heading marker: replace(find="## Old", with="## New") gives '## New', and a different level applies only when find named the heading's actual level (find "## Old" with "### New" makes it an h3), since '# ' selects a heading without naming its level. A task item (\`- [ ]\` / \`- [x]\`) is ticked the same way: when find matches from the start of the item's text and with opens with '[x] ' or '[ ] ', the opening sets the item's box and the rest is its new text (replace(find="Write it", with="[x] Write it") ticks it); the same opening over a plain list item or a paragraph stays literal text. Any non-empty with that renders to no text - a line indented four spaces or a tab, which markdown reads as a code block, or whitespace alone - is INVALID_OP rather than a silent deletion; pass an empty with to delete the matched text on purpose - a list item, quote, typed block or footnote definition left holding only the emptied paragraph keeps it. ` + "with cannot open a new block: after a hard line break inside with (two trailing spaces, or a backslash, before the newline) a heading, bullet, '1.'/'1)' ordered, or '>' blockquote marker is INVALID_OP too, since that line would stay escaped text inside the matched block - use insert, plus delete for what it replaces, to add the block. A hard break in with is itself INVALID_OP when the matched text is in a heading or a table cell, which are written on one line. " + "A delete whose find is a block's entire text removes the block (a list emptied of its items goes too); delete with block removes any block by id, and move with block relocates one. A delete or retype that would take an ask block out of the document while its ask is open is refused, with nothing sent. delete_row and delete_column take a table block and a zero-based index, preserving the table block id and refusing to remove cells with open asks or unresolved comments. " + `Insert and move anchors also accept "start", "end", "heading:<exact heading text>", and "block:<id>"; dispatch doc-read --blocks lists every block's id, type, opening words and token, and each table's rows and cells with the indexes delete_row and delete_column take (GET /api/v1/artifacts/{artifact UUID}/blocks serves the same list). ` + "Optionally require the state just read: precondition selects exactly one of a document token from dispatch doc-read, or block {id, token} values from dispatch doc-read --blocks. A block guard must include every block the batch changes; Dispatch resolves quote targets and rejects an uncovered batch rather than applying it. Use a document token for insert or move, which depend on document order. Prefer block tokens when the covered content blocks are independent sections. Tokens include inline marks, so a fresh human comment also makes a stale edit fail. PRECONDITION_FAILED means re-read; EDIT_QUEUE_FULL means back off before retrying. " + "The result carries the document token this edit produced, so a chain of guarded edits passes each result's token as the next edit's precondition with no dispatch doc-read between them. " + "A batch that leaves the document exactly as it was mints no version, named or not, and the result says nothing changed and names each operation that did nothing. " + "A change a browser removes while the edit is in flight is never reported as applied: EDIT_LOST_TO_CONCURRENT_CHANGE means the write was refused and nothing was written, so re-read the document and decide again, as with PRECONDITION_FAILED; lost_ops on a successful result names operations whose text the live document no longer has, because the deletion landed after the version was written. " + "One replace per paragraph: more new paragraphs go in one insert after the last one you rewrote, since two inserts at one anchor come out reversed; read the passage back with dispatch doc-read afterwards. " + `The spec holds requirements, design, and decisions - never progress, status, or timestamps; a record of open items or progress is a document of its own. ${OWNER_REFERENCE} ${SPEC_WRITING_POINTER}`,
14324
+ description: "Apply deterministic document edits: replace or delete quoted text, insert markdown at an anchor, retype an identified paragraph or typed block into a schema-declared typed block, delete or move a whole block by its id, or delete a table row or column in place. " + "Do not use it for review feedback or for reading; use dispatch comment, dispatch suggest, or dispatch doc-read instead. " + `For replace, delete, and quote anchors, find text as rendered: inline Markdown (**bold**, \`code\`) is tolerated and must be balanced; a leading '# ' matches a heading at any level. replace is inline: with is the new text of the matched span, so a marker of a different kind from the block's own stays literal text ('4. Design' written into a heading). A with that opens with a marker of the same kind as the matched block's own would write it twice and is INVALID_OP - including prose that merely looks like one ('1999. was a year' into an ordered item), which you write as text by escaping it ('1999\\. was a year'). The exception is a heading rename whose find carried a heading marker: replace(find="## Old", with="## New") gives '## New', and a different level applies only when find named the heading's actual level (find "## Old" with "### New" makes it an h3), since '# ' selects a heading without naming its level. A task item (\`- [ ]\` / \`- [x]\`) is ticked the same way: when find matches from the start of the item's text and with opens with '[x] ' or '[ ] ', the opening sets the item's box and the rest is its new text (replace(find="Write it", with="[x] Write it") ticks it); the same opening over a plain list item or a paragraph stays literal text. Any non-empty with that renders to no text - a line indented four spaces or a tab, which markdown reads as a code block, or whitespace alone - is INVALID_OP rather than a silent deletion; pass an empty with to delete the matched text on purpose - a list item, quote, typed block or footnote definition left holding only the emptied paragraph keeps it. ` + "with cannot open a new block: after a hard line break inside with (two trailing spaces, or a backslash, before the newline) a heading, bullet, '1.'/'1)' ordered, or '>' blockquote marker is INVALID_OP too, since that line would stay escaped text inside the matched block - use insert, plus delete for what it replaces, to add the block. A hard break in with is itself INVALID_OP when the matched text is in a heading or a table cell, which are written on one line. In a table cell, with is the cell's text: write a | in it bare, in code too, since code whose text has an odd run of backslashes right before a | cannot be stored in a cell and is INVALID_OP, naming the cell. " + "A delete whose find is a block's entire text removes the block (a list emptied of its items goes too); delete with block removes any block by id, and move with block relocates one. A delete or retype that would take an ask block out of the document while its ask is open is refused, with nothing sent. delete_row and delete_column take a table block and a zero-based index, preserving the table block id and refusing to remove cells with open asks or unresolved comments. " + `Insert and move anchors also accept "start", "end", "heading:<exact heading text>", and "block:<id>"; dispatch doc-read --blocks lists every block's id, type, opening words and token, and each table's rows and cells with the indexes delete_row and delete_column take (GET /api/v1/artifacts/{artifact UUID}/blocks serves the same list). ` + "Optionally require the state just read: precondition selects exactly one of a document token from dispatch doc-read, or block {id, token} values from dispatch doc-read --blocks. A block guard must include every block the batch changes; Dispatch resolves quote targets and rejects an uncovered batch rather than applying it. Use a document token for insert or move, which depend on document order. Prefer block tokens when the covered content blocks are independent sections. Tokens include inline marks, so a fresh human comment also makes a stale edit fail. PRECONDITION_FAILED means re-read; EDIT_QUEUE_FULL means back off before retrying. " + "The result carries the document token this edit produced, so a chain of guarded edits passes each result's token as the next edit's precondition with no dispatch doc-read between them. " + "A batch that leaves the document exactly as it was mints no version, named or not, and the result says nothing changed and names each operation that did nothing. " + "A change a browser removes while the edit is in flight is never reported as applied: EDIT_LOST_TO_CONCURRENT_CHANGE means the write was refused and nothing was written, so re-read the document and decide again, as with PRECONDITION_FAILED; lost_ops on a successful result names operations whose text the live document no longer has, because the deletion landed after the version was written. " + "One replace per paragraph: more new paragraphs go in one insert after the last one you rewrote, since two inserts at one anchor come out reversed; read the passage back with dispatch doc-read afterwards. " + "A block id written as {#id} longer than 256 bytes is stored, but its block is no graph node, so no reference reaches it. " + `The spec holds requirements, design, and decisions - never progress, status, or timestamps; a record of open items or progress is a document of its own. ${OWNER_REFERENCE} ${SPEC_WRITING_POINTER}`,
14278
14325
  arguments: (z2) => ({
14279
14326
  issue: z2.string().describe(ISSUE_REFERENCE).optional(),
14280
14327
  project: z2.string().describe("Project key owning the document.").optional(),
@@ -14334,7 +14381,7 @@ var dispatchToolSpecs = [
14334
14381
  name: "dispatch_artifact",
14335
14382
  example: { issue: "DSP-1", name: "design.md", content: `# Design
14336
14383
  ` },
14337
- description: "Attach a local file or inline text as an issue artifact or project document. Do not use it to edit a live document; use " + "dispatch doc-edit instead. Never upload a document's own name to change it (spec.md replaces the spec whole); edit it with dispatch doc-edit. Uploading the same name again makes its next version. Exactly one of path or content is required; a markdown document is at most 1 MiB and any other file at most 25 MiB. " + "Markdown is also refused with 413 CAP_EXCEEDED when it makes more than 65,536 elements (a block weighs 3, and an empty list item, quote, footnote definition or typed block 3 more for the empty paragraph it holds; a table cell 4, a hard line break 3, an image 3, a piece of inline HTML 2, an autolink 2, a footnote reference 2; inline syntax, marks, lines of text and backslash escapes or character references 1 each), or when a new version would leave the document's stored markdown (what its text reads back as) longer than 1 MiB or making more than 65,536 elements, and longer or heavier than before; shorten it or split it across documents. " + "Markdown holding an ask block whose body breaks its content rule (one or more question paragraphs, then at most one bullet list of options, last) is refused with 400 INVALID_ASK_BLOCK; a new version of a document is held to it only for the asks it writes or changes. " + `${OWNER_REFERENCE}`,
14384
+ description: "Attach a local file or inline text as an issue artifact or project document. Do not use it to edit a live document; use " + "dispatch doc-edit instead. Never upload a document's own name to change it (spec.md replaces the spec whole); edit it with dispatch doc-edit. Uploading the same name again makes its next version. Exactly one of path or content is required; a markdown document is at most 1 MiB and any other file at most 25 MiB. " + "Markdown is also refused with 413 CAP_EXCEEDED when it makes more than 65,536 elements (a block weighs 3, and an empty list item, quote, footnote definition or typed block 3 more for the empty paragraph it holds; a table cell 4, a hard line break 3, an image 3, a piece of inline HTML 2, an autolink 2, a footnote reference 2; inline syntax, marks, lines of text and backslash escapes or character references 1 each), or when a new version would leave the document's stored markdown (what its text reads back as) longer than 1 MiB or making more than 65,536 elements, and longer or heavier than before; shorten it or split it across documents. " + "In a table row, write every | in a cell as \\|, in code too: a | after two backslashes ends the cell, and a row with more cells than its table is refused. " + "Markdown holding an ask block whose body breaks its content rule (one or more question paragraphs, then at most one bullet list of options, last) is refused with 400 INVALID_ASK_BLOCK; a new version of a document is held to it only for the asks it writes or changes. " + `${OWNER_REFERENCE}`,
14338
14385
  arguments: (z2) => ({
14339
14386
  issue: z2.string().describe(ISSUE_REFERENCE).optional(),
14340
14387
  project: z2.string().describe("Project key for an unlinked document.").optional(),
@@ -14354,12 +14401,12 @@ var dispatchToolSpecs = [
14354
14401
  {
14355
14402
  name: "dispatch_read",
14356
14403
  example: { issue: "DSP-1" },
14357
- description: "Read an issue or document summary, targeted ask, or targeted comment reply chain, or the conversation " + "a message belongs to. Do not use it for document contents; use dispatch doc-read instead. Supply ref, issue, " + "issue plus artifact (one of its documents), or project plus artifact; or message alone, which reads a human's direct message to this session and every " + "reply to it (they belong to no issue). A document summary names its issue, reference, versions and approval. " + "An anchored comment or ask also says where its quote sits, as `Position:`: the block's path from the top, " + "and in a table the row (0 is the header), the cells before the anchored one, and the column's header; " + "`Position: unavailable (<code>)` when Dispatch could not read the document: `DOC_SERVICE_UNAVAILABLE` " + "(try again shortly), `DOC_SCHEMA` (the document needs repair), `DOCUMENT_UNLOADABLE` (the document " + "needs a rebuild) or `INTERNAL`. " + "An issue read carries a `Progress:` line, `tasks 3/7, children 2/5`: its spec's task-list items " + "(`- [ ]` / `- [x]`, nested lists included) and its direct children, each done of total and each only " + "when it has any (`Progress: none` otherwise). " + `An issue read, and its dispatch://KEY/log, lists the issue's last ${DEFAULT_ISSUE_READ_EVENTS} events, oldest first; ` + "--events N lists the last N, and --all-events every one. " + "Every read ends with `Referenced by:` (what cites or hangs off this node, each with its dispatch:// address, " + "an excerpt, and when) and `Links:` (what it cites), so tracing provenance is one call. " + "The pictures the shown messages, asks and comments embed come back as images you see, newest first, at most 8 " + "and 10 MiB of them per read, each a PNG, JPEG, GIF or WebP of at most 3,750,000 bytes (5 MB of base64); a `Pictures:` section names each " + "one shown, in order, and the rest by reference, for dispatch doc-read. A picture this session was already shown is named, not sent again " + "(every request carries the session's history, and the provider refuses one over 32 MB); dispatch doc-read shows it again. " + OWNER_REFERENCE,
14404
+ description: "Read an issue or document summary, targeted ask, or targeted comment reply chain, or the conversation " + "a message belongs to. Do not use it for document contents; use dispatch doc-read instead. Supply ref, issue, " + "issue plus artifact (one of its documents), or project plus artifact; or message alone, which reads a human's direct message to this session and every " + "reply to it (they belong to no issue). A document summary names its issue, reference, versions and approval. " + "An anchored comment or ask also says where its quote sits, as `Position:`: the block's path from the top, " + "and in a table the row (0 is the header), the cells before the anchored one, and the column's header; " + "`Position: unavailable (<code>)` when Dispatch could not read the document: `DOC_SERVICE_UNAVAILABLE` " + "(try again shortly), `DOC_SCHEMA` (the document needs repair), `DOCUMENT_UNLOADABLE` (the document " + "needs a rebuild) or `INTERNAL`. " + "An issue read carries a `Progress:` line, `tasks 3/7, children 2/5`: its spec's task-list items " + "(`- [ ]` / `- [x]`, nested lists included) and its direct children, each done of total and each only " + "when it has any (`Progress: none` otherwise). " + `An issue read, and its dispatch://KEY/log, lists the issue's last ${DEFAULT_ISSUE_READ_EVENTS} events, oldest first; ` + "--events N lists the last N, and --all-events every one. " + "Every read ends with `Referenced by:` (what cites or hangs off this node, each with its dispatch:// address, " + "an excerpt, and when) and `Links:` (what it cites), so tracing provenance is one call. " + "A block read prints `Block:`, `Document:`, `Changed in version:` and `Text:`, then those two sections; a pinned one adds " + "the block's text at that version and whether it is unchanged, changed or removed since, and an absent block, unpinned, " + "reads `No block <id> in <document>.` " + "The pictures the shown messages, asks and comments embed come back as images you see, newest first, at most 8 " + "and 10 MiB of them per read, each a PNG, JPEG, GIF or WebP of at most 3,750,000 bytes (5 MB of base64); a `Pictures:` section names each " + "one shown, in order, and the rest by reference, for dispatch doc-read. A picture this session was already shown is named, not sent again " + "(every request carries the session's history, and the provider refuses one over 32 MB); dispatch doc-read shows it again. " + OWNER_REFERENCE,
14358
14405
  arguments: (z2) => ({
14359
14406
  issue: z2.string().describe(ISSUE_REFERENCE).optional(),
14360
14407
  project: z2.string().describe("Project key owning the document.").optional(),
14361
14408
  artifact: z2.string().describe("Document artifact id, slug, or filename: one of the issue's, or a project document.").optional(),
14362
- ref: z2.string().describe("Optional dispatch:// reference: dispatch://KEY, dispatch://KEY/spec, " + "dispatch://KEY/artifact/<slug>[@vN], dispatch://KEY/ask/<id>, dispatch://KEY/comment/<id>, " + "dispatch://KEY/message/<id>, dispatch://PROJECT/artifact/<slug>[@vN], and its " + "/ask/<id> and /comment/<id>; or the dashboard URL of one.").optional(),
14409
+ ref: z2.string().describe("Optional dispatch:// reference: dispatch://KEY, dispatch://KEY/spec, " + "dispatch://KEY/artifact/<slug>[@vN], dispatch://KEY/ask/<id>, dispatch://KEY/comment/<id>, " + "dispatch://KEY/message/<id>, dispatch://PROJECT/artifact/<slug>[@vN], and its " + "/ask/<id> and /comment/<id>; or the dashboard URL of one. A block: " + "dispatch://KEY/spec#b-<block id>[@vN], dispatch://KEY/artifact/<slug>#b-<block id>[@vN] or " + "dispatch://PROJECT/artifact/<slug>#b-<block id>[@vN], the id percent-encoded except ASCII " + "letters, digits and -.").optional(),
14363
14410
  message: z2.string().describe("A message id (uuid): reads the conversation it belongs to, the root message and every " + "reply. Name nothing else with it.").optional(),
14364
14411
  events: z2.number({ int: true, min: 1 }).describe(`How many of the issue's newest events an issue read or its dispatch://KEY/log lists, oldest first; default ${DEFAULT_ISSUE_READ_EVENTS}. Name no --all-events with it.`).optional(),
14365
14412
  all_events: z2.boolean().describe(`List every one of the issue's events, oldest first, on an issue read or its dispatch://KEY/log, in place of the last ${DEFAULT_ISSUE_READ_EVENTS}. Name no --events with it.`).optional()
@@ -15723,7 +15770,27 @@ function dispatchDocumentRef(owner, slug) {
15723
15770
  function dispatchChildRef(ownerRef, kind, id) {
15724
15771
  return `${ownerRef}/${kind}/${id}`;
15725
15772
  }
15773
+ function parseBlockRef(document, fragment) {
15774
+ const target = parseBlockFragment(fragment);
15775
+ const parsed = parseDispatchRef(document);
15776
+ if (target === undefined || parsed === null || parsed.version !== undefined || parsed.kind !== "spec" && parsed.kind !== "artifact") {
15777
+ return null;
15778
+ }
15779
+ return {
15780
+ owner: parsed.owner,
15781
+ kind: "block",
15782
+ id: parsed.id,
15783
+ block: target.block,
15784
+ document,
15785
+ ...parsed.owner.kind === "project" ? { artifact: parsed.id } : {},
15786
+ ...target.pin === undefined ? {} : { pin: target.pin }
15787
+ };
15788
+ }
15726
15789
  function parseDispatchRef(ref) {
15790
+ const hash2 = ref.indexOf("#");
15791
+ if (hash2 !== -1 && ref.startsWith("b-", hash2 + 1)) {
15792
+ return parseBlockRef(ref.slice(0, hash2), ref.slice(hash2 + 3));
15793
+ }
15727
15794
  const projectDocument = ref.match(/^dispatch:\/\/([A-Z][A-Z0-9]{1,9})\/artifact\/([^/@]+)(?:@v(\d+))?(?:\/(ask|comment)\/([^/]+))?$/);
15728
15795
  if (projectDocument) {
15729
15796
  const [, project, artifact2, version3, targetKind, targetID] = projectDocument;
@@ -16260,6 +16327,13 @@ function subscriptionCovers(topic, owner) {
16260
16327
  return false;
16261
16328
  }
16262
16329
 
16330
+ // ../envoy-client/src/human-edit.ts
16331
+ function humanEditHead(edit) {
16332
+ const editors = edit.editors ?? [];
16333
+ const by = editors.length === 0 ? "" : `, edited by ${editors.join(", ")}`;
16334
+ return `${edit.name ?? "document"} v${edit.from_version ?? "?"} to v${edit.to_version ?? "?"}${by}`;
16335
+ }
16336
+
16263
16337
  // ../envoy-client/src/search-answer.ts
16264
16338
  function pageSummaryText(offset, count, total) {
16265
16339
  return `showing ${offset + 1}-${offset + count} of ${total}`;
@@ -16856,9 +16930,13 @@ function dispatchRefFromUrl(value, serverUrl) {
16856
16930
  const value2 = url2.searchParams.get(name);
16857
16931
  return value2 !== null && /^[1-9][0-9]*$/.test(value2) ? `@v${value2}` : "";
16858
16932
  };
16933
+ const blockOf = (versionName) => {
16934
+ const id = url2.hash.startsWith("#b-") ? decodeBlockId(url2.hash.slice(3)) : undefined;
16935
+ return id === undefined || url2.searchParams.get(versionName) ? "" : `#b-${encodeBlockId(id)}`;
16936
+ };
16859
16937
  const issuePage = url2.pathname.match(/^\/issues\/([A-Z][A-Z0-9]{1,9}-[1-9][0-9]*)(?:\/(spec|log|conversation|activity|children)|\/artifacts\/([^/]+)|\/asks\/([^/]+)|\/comments\/([^/]+)|\/messages\/([^/]+))?\/?$/);
16860
16938
  if (issuePage) {
16861
- const [, key, page, artifact, ask, comment, message] = issuePage;
16939
+ const [, key = "", page, artifact, ask, comment, message] = issuePage;
16862
16940
  const version2 = versionOf("v");
16863
16941
  if (page === "spec" || artifact !== undefined) {
16864
16942
  const item2 = itemFromSearch(url2.search);
@@ -16869,10 +16947,13 @@ function dispatchRefFromUrl(value, serverUrl) {
16869
16947
  }
16870
16948
  if (page === "spec" && version2 !== "")
16871
16949
  return `dispatch://${key}/artifact/spec${version2}`;
16950
+ if (page === "spec")
16951
+ return `dispatch://${key}/spec${blockOf("v")}`;
16872
16952
  if (page !== undefined)
16873
16953
  return `dispatch://${key}/${page === "conversation" ? "log" : page}`;
16874
16954
  if (artifact !== undefined) {
16875
- return `dispatch://${key}/artifact/${decodeURIComponent(artifact)}${version2}`;
16955
+ const slug2 = decodeURIComponent(artifact);
16956
+ return `dispatch://${key}/artifact/${slug2}${version2}${blockOf("v")}`;
16876
16957
  }
16877
16958
  if (ask !== undefined)
16878
16959
  return `dispatch://${key}/ask/${ask}`;
@@ -16895,25 +16976,26 @@ function dispatchRefFromUrl(value, serverUrl) {
16895
16976
  if (!documentPage)
16896
16977
  return;
16897
16978
  const [, project, slug] = documentPage;
16898
- const document = `dispatch://${project}/artifact/${decodeURIComponent(slug ?? "")}`;
16979
+ const decodedSlug = decodeURIComponent(slug ?? "");
16980
+ const document = `dispatch://${project}/artifact/${decodedSlug}`;
16899
16981
  const item = itemFromSearch(url2.search);
16900
16982
  if (item === null)
16901
16983
  return;
16902
- return item === undefined ? `${document}${versionOf("version")}` : `${document}/${item.kind}/${item.id}`;
16984
+ return item === undefined ? `${document}${versionOf("version")}${blockOf("version")}` : `${document}/${item.kind}/${item.id}`;
16903
16985
  }
16904
- var refGrammarProblem = "ref must be a valid dispatch:// reference such as dispatch://KEY-1, " + "dispatch://KEY-1/ask/<uuid>, dispatch://KEY-1/comment/<uuid>, " + "dispatch://KEY-1/message/<uuid>, dispatch://KEY-1/artifact/<slug>, " + "dispatch://PROJECT/artifact/<document-ref> (an artifact id, slug, or filename), or " + "dispatch://agent/<session id>/artifact/<slug> (a picture in a conversation on the Agents page, " + "for dispatch doc-read); a dashboard URL on this Dispatch server is accepted too";
16986
+ var refGrammarProblem = "ref must be a valid dispatch:// reference such as dispatch://KEY-1, " + "dispatch://KEY-1/ask/<uuid>, dispatch://KEY-1/comment/<uuid>, " + "dispatch://KEY-1/message/<uuid>, dispatch://KEY-1/artifact/<slug>, " + "dispatch://PROJECT/artifact/<document-ref> (an artifact id, slug, or filename), " + "dispatch://KEY-1/spec#b-<block id>, dispatch://KEY-1/artifact/<slug>#b-<block id> or " + "dispatch://PROJECT/artifact/<slug>#b-<block id> (one block of a document, its id " + "percent-encoded, optionally pinned to a version of the document with @v<N> after it; for " + "dispatch read), or " + "dispatch://agent/<session id>/artifact/<slug> (a picture in a conversation on the Agents page, " + "for dispatch doc-read); a dashboard URL on this Dispatch server is accepted too";
16905
16987
  var ownerRequiredProblem = "issue is required; supply issue or set LEGION_ISSUE";
16906
16988
  function innerRefProblem(tool, ref, refText) {
16907
- if (ref.kind !== "ask" && ref.kind !== "comment" && ref.kind !== "message" && ref.kind !== "log" && ref.kind !== "children") {
16989
+ if (ref.kind !== "ask" && ref.kind !== "comment" && ref.kind !== "message" && ref.kind !== "log" && ref.kind !== "children" && ref.kind !== "block") {
16908
16990
  return;
16909
16991
  }
16910
16992
  if (tool === "dispatch_read" || tool === "dispatch_ask")
16911
16993
  return;
16912
16994
  const owner = refOwnerName(ref);
16913
- const suggestTool = tool === "dispatch_suggest";
16914
- const ownerArguments = ref.owner.kind === "issue" ? `issue ${ref.owner.issue}${suggestTool ? " and artifact <the document's slug>" : ""}` : `project ${ref.owner.project} and artifact ${ref.artifact}`;
16995
+ const documentArgument = ref.kind === "block" && tool !== "dispatch_comment" ? ` and artifact ${ref.id}` : tool === "dispatch_suggest" ? " and artifact <the document's slug>" : "";
16996
+ const ownerArguments = ref.owner.kind === "issue" ? `issue ${ref.owner.issue}${documentArgument}` : `project ${ref.owner.project} and artifact ${ref.artifact}`;
16915
16997
  const fix = tool === "dispatch_comment" && ref.kind === "ask" ? `reply under it with reply_to_ask ${ref.id} and ${ownerArguments}` : tool === "dispatch_comment" && ref.kind === "comment" ? `reply in its thread with reply_to ${ref.id} and ${ownerArguments}` : `read it with dispatch read, or name ${owner} with ${ownerArguments}`;
16916
- const item = ref.kind === "ask" ? "an ask" : ref.kind === "log" ? "the event log" : ref.kind === "children" ? "the children" : `a ${ref.kind}`;
16998
+ const item = ref.kind === "ask" ? "an ask" : ref.kind === "log" ? "the event log" : ref.kind === "children" ? "the children" : ref.kind === "block" ? `block ${ref.block}` : `a ${ref.kind}`;
16917
16999
  return `ref ${refText} names ${item} on ${owner}; dispatch ${commandName(tool)} would act on ${owner} and drop the ${ref.kind}: ${fix}`;
16918
17000
  }
16919
17001
  var documentItemPattern = /^dispatch:\/\/([A-Z][A-Z0-9]{1,9}-[1-9][0-9]*)\/artifact\/[^/@]+(?:@v\d+)?\/(ask|comment)\/([^/]+)$/;
@@ -16940,7 +17022,7 @@ function ownerConflictProblems(tool, args, parsedRef, refText) {
16940
17022
  const artifact = optionalString(args, "artifact");
16941
17023
  const version2 = optionalNumber(args, "version");
16942
17024
  const ref = tool === "dispatch_ask" && (issue2 !== undefined || project !== undefined) ? null : parsedRef;
16943
- const refDocument = ref === null ? undefined : ref.owner.kind === "project" ? ref.artifact ?? ref.id : ref.kind === "spec" || ref.kind === "artifact" ? ref.id : undefined;
17025
+ const refDocument = ref === null ? undefined : ref.owner.kind === "project" ? ref.artifact ?? ref.id : ref.kind === "spec" || ref.kind === "artifact" || ref.kind === "block" ? ref.id : undefined;
16944
17026
  if (ref !== null && refText !== undefined) {
16945
17027
  const refOwner = refOwnerName(ref);
16946
17028
  if (issue2 !== undefined && (ref.owner.kind !== "issue" || nativeIssueKeyPattern.test(issue2) && issue2 !== ref.owner.issue)) {
@@ -16968,7 +17050,7 @@ function ownerConflictProblems(tool, args, parsedRef, refText) {
16968
17050
  }
16969
17051
  const projectOwned = project !== undefined || ref?.owner.kind === "project";
16970
17052
  if (tool === "dispatch_comment" && !projectOwned && optionalString(args, "quote") === undefined) {
16971
- const document = artifact ?? refDocument;
17053
+ const document = artifact ?? (ref?.kind === "block" ? undefined : refDocument);
16972
17054
  if (document !== undefined) {
16973
17055
  problems.push(`${command} posts on the issue unless quote anchors it in a document, so document ${document} would be dropped: add --quote to comment on a passage of it, or name only the issue`);
16974
17056
  }
@@ -17341,22 +17423,23 @@ function issueSummary(issue2, events, references, graph, linkBase, titles) {
17341
17423
  ].join(`
17342
17424
  `);
17343
17425
  }
17344
- function referenceLines(edges) {
17345
- if (typeof edges === "string")
17346
- return [`- ${edges}`];
17347
- if (edges.length === 0)
17426
+ function referenceLines(read) {
17427
+ if (typeof read === "string")
17428
+ return [`- ${read}`];
17429
+ if (read.edges.length === 0)
17348
17430
  return ["- none"];
17349
- return edges.map((edge) => {
17431
+ return read.edges.map((edge) => {
17350
17432
  const excerpt2 = edge.excerpt === undefined ? "" : `${textHead(edge.excerpt.text)} \xB7 `;
17351
- return `- ${edge.kind} ${edge.node.kind} ${edge.node.ref ?? edge.node.id} (${excerpt2}${edge.created_at})`;
17433
+ const block = edge.block === undefined ? "" : ` (block ${edge.block.ref ?? edge.block.id})`;
17434
+ return `- ${edge.kind} ${edge.node.kind} ${edge.node.ref ?? edge.node.id} (${excerpt2}${edge.created_at})${block}`;
17352
17435
  });
17353
17436
  }
17354
17437
  function unavailableReason(error48) {
17355
17438
  return dispatchAnswered(error48, 404) ? "unavailable" : `unavailable: ${messageFor(error48)}`;
17356
17439
  }
17357
- async function graphEdges(client, query) {
17440
+ async function graphReferences(client, query) {
17358
17441
  try {
17359
- return (await client.getReferences(query)).edges;
17442
+ return await client.getReferences(query);
17360
17443
  } catch (error48) {
17361
17444
  return unavailableReason(error48);
17362
17445
  }
@@ -17370,10 +17453,50 @@ async function issueReferencesOrUnavailable(client, key) {
17370
17453
  }
17371
17454
  async function graphSections(client, ref) {
17372
17455
  const [incoming, outgoing] = await Promise.all([
17373
- graphEdges(client, { to: ref }),
17374
- graphEdges(client, { from: ref })
17456
+ graphReferences(client, { to: ref }),
17457
+ graphReferences(client, { from: ref })
17375
17458
  ]);
17376
- return ["Referenced by:", ...referenceLines(incoming), "Links:", ...referenceLines(outgoing)];
17459
+ return graphSectionLines(incoming, outgoing);
17460
+ }
17461
+ function graphSectionLines(incoming, outgoing) {
17462
+ return [
17463
+ ...typeof incoming === "string" ? [] : nodeLine(incoming.node),
17464
+ "Referenced by:",
17465
+ ...referenceLines(incoming),
17466
+ "Links:",
17467
+ ...referenceLines(outgoing)
17468
+ ];
17469
+ }
17470
+ function nodeLine(node) {
17471
+ if (node.made_by === undefined)
17472
+ return [];
17473
+ const fact = (by, time3) => `${by === undefined || by.length === 0 ? "nobody recorded" : by.map(actorText).join(", ")}${time3 == null ? "" : ` at ${time3}`}`;
17474
+ const made = fact(node.made_by, node.made_at);
17475
+ const changed = fact(node.changed_by, node.changed_at);
17476
+ const { parent } = node;
17477
+ const partOf = parent == null ? "" : ` \xB7 part of ${parent.ref ?? `${parent.kind} ${parent.id}`}${node.parent_version == null ? "" : ` (version ${node.parent_version})`}`;
17478
+ return [
17479
+ `Node: ${node.kind} ${node.ref ?? node.id} \xB7 made by ${made} \xB7 changed by ${changed}${partOf}`
17480
+ ];
17481
+ }
17482
+ function blockLines(node, ref) {
17483
+ const head = [
17484
+ `Block: ${node.block_id ?? ref.block}`,
17485
+ `Document: ${node.document ?? ref.document}`,
17486
+ ...node.changed_version === undefined ? [] : [`Changed in version: ${node.changed_version}`],
17487
+ ...node.text === undefined ? [] : ["Text:", node.text]
17488
+ ];
17489
+ const { pin } = node;
17490
+ if (pin === undefined)
17491
+ return head;
17492
+ const state = pin.state === "unchanged" ? `Unchanged since version ${pin.version}.` : pin.state === "changed" ? `Changed since version ${pin.version}; last changed in version ${node.changed_version}.` : `Removed in version ${pin.removed_version}.`;
17493
+ return [
17494
+ ...head,
17495
+ `Pinned to version: ${pin.version}`,
17496
+ state,
17497
+ `Text at version ${pin.version}:`,
17498
+ pin.text
17499
+ ];
17377
17500
  }
17378
17501
  function logSummary(issue2, events) {
17379
17502
  return [
@@ -17408,6 +17531,8 @@ function eventHead(event) {
17408
17531
  return textHead(event.payload.body);
17409
17532
  case "artifact.version":
17410
17533
  return textHead(`${event.payload.name} v${event.payload.version.number}${event.payload.version.summary ? `: ${event.payload.version.summary}` : ""}`);
17534
+ case "artifact.human_edited":
17535
+ return textHead(humanEditHead(event.payload));
17411
17536
  case "issue.created":
17412
17537
  case "issue.updated":
17413
17538
  case "issue.closed":
@@ -18740,6 +18865,27 @@ ${trailer.join(`
18740
18865
  ...showImages(pictures3)
18741
18866
  };
18742
18867
  }
18868
+ if (ownerArguments.ref?.kind === "block") {
18869
+ const ref = ownerArguments.ref;
18870
+ const blockRef = stringArg(args, "ref");
18871
+ const [incoming, outgoing] = await Promise.all([
18872
+ client.getReferences({ to: blockRef }).catch((error48) => {
18873
+ if (ref.pin === undefined && dispatchAnswered(error48, 404)) {
18874
+ throw new Error(`No block ${ref.block} in ${ref.document}.`);
18875
+ }
18876
+ throw error48;
18877
+ }),
18878
+ graphReferences(client, { from: blockRef })
18879
+ ]);
18880
+ return {
18881
+ text: [...blockLines(incoming.node, ref), ...graphSectionLines(incoming, outgoing)].join(`
18882
+ `),
18883
+ details: {
18884
+ ...ref.owner.kind === "project" ? { project: ref.owner.project } : { issue: ref.owner.issue },
18885
+ block: incoming.node.block_id ?? ref.block
18886
+ }
18887
+ };
18888
+ }
18743
18889
  const documentReference = optionalString(args, "artifact");
18744
18890
  if (documentOwner().kind === "project" || documentReference !== undefined) {
18745
18891
  const resolved = await resolveDocument(documentOwner(), documentReference);
@@ -19680,6 +19826,7 @@ function sessionEnvoySettings(file2, env, options) {
19680
19826
  // ../envoy-client/src/transport.ts
19681
19827
  var DEFAULT_TIMEOUT_MS = 5000;
19682
19828
  var RETRY_DELAY_MS = 250;
19829
+ var REPLAY_TIMEOUT_MS = 1e4;
19683
19830
  var InterestWireSchema = exports_external.object({
19684
19831
  session_id: exports_external.string(),
19685
19832
  machine_id: exports_external.string(),
@@ -19714,12 +19861,33 @@ var RoleHeldWireSchema = exports_external.object({
19714
19861
  });
19715
19862
  var ErrorWireSchema = exports_external.object({
19716
19863
  error: exports_external.string(),
19864
+ code: exports_external.string().optional(),
19717
19865
  expected: exports_external.array(exports_external.string()).optional()
19718
19866
  });
19719
19867
  var EnvelopeResponseSchema = EnvelopeSchema.extend({
19720
19868
  recipient: exports_external.string().optional(),
19721
19869
  holder: exports_external.string().optional()
19722
19870
  });
19871
+ var ReplayWireSchema = exports_external.object({
19872
+ stream: exports_external.object({
19873
+ id: exports_external.string().min(1).optional(),
19874
+ created: exports_external.string(),
19875
+ first_seq: exports_external.number().int().nonnegative(),
19876
+ last_seq: exports_external.number().int().nonnegative()
19877
+ }),
19878
+ subjects: exports_external.array(exports_external.object({
19879
+ subject: exports_external.string(),
19880
+ messages: exports_external.array(exports_external.object({
19881
+ seq: exports_external.number().int().positive(),
19882
+ subject: exports_external.string(),
19883
+ time: exports_external.string(),
19884
+ data: exports_external.string()
19885
+ })),
19886
+ next_after: exports_external.number().int().nonnegative(),
19887
+ more: exports_external.boolean(),
19888
+ refused: exports_external.string().optional()
19889
+ }))
19890
+ });
19723
19891
  function expandSubscriptionTopics(topics) {
19724
19892
  const expanded = new Set;
19725
19893
  for (const topic of topics) {
@@ -19742,6 +19910,7 @@ class EnvoyApiError extends Error {
19742
19910
  details;
19743
19911
  name = "EnvoyApiError";
19744
19912
  expected;
19913
+ code;
19745
19914
  constructor(details) {
19746
19915
  let parsed;
19747
19916
  try {
@@ -19752,16 +19921,18 @@ class EnvoyApiError extends Error {
19752
19921
  super(parsed === undefined ? `${details.method} ${details.url} failed with ${details.status}: ${details.responseBody}` : `${parsed.error}${parsed.expected === undefined ? "" : ` (expected: ${parsed.expected.join(", ")})`}`);
19753
19922
  this.details = details;
19754
19923
  this.expected = parsed?.expected;
19924
+ this.code = parsed?.code;
19755
19925
  }
19756
19926
  }
19757
19927
  function createEnvoyClient(config2) {
19758
19928
  const baseUrl = normalizeEnvoyUrl(config2.baseUrl);
19759
19929
  const timeoutMs = config2.timeoutMs ?? DEFAULT_TIMEOUT_MS;
19760
19930
  const apiToken = config2.token;
19761
- const request = async (path2, init) => {
19931
+ const request = async (path2, init, options = {}) => {
19762
19932
  const url2 = `${baseUrl}${path2}`;
19763
19933
  const attempts = config2.retry === false ? 1 : 2;
19764
- for (let attempt = 0;attempt < attempts; attempt += 1) {
19934
+ const tries = options.retry === false ? 1 : attempts;
19935
+ for (let attempt = 0;attempt < tries; attempt += 1) {
19765
19936
  let response;
19766
19937
  try {
19767
19938
  const headers = new Headers(init.headers);
@@ -19770,10 +19941,10 @@ function createEnvoyClient(config2) {
19770
19941
  response = await config2.fetch(url2, {
19771
19942
  ...init,
19772
19943
  headers,
19773
- signal: AbortSignal.timeout(timeoutMs)
19944
+ signal: AbortSignal.timeout(options.timeoutMs ?? timeoutMs)
19774
19945
  });
19775
19946
  } catch (error49) {
19776
- if (attempt + 1 < attempts) {
19947
+ if (attempt + 1 < tries) {
19777
19948
  await waitForRetry();
19778
19949
  continue;
19779
19950
  }
@@ -19788,7 +19959,7 @@ function createEnvoyClient(config2) {
19788
19959
  status: response.status,
19789
19960
  responseBody
19790
19961
  });
19791
- if (response.status >= 500 && attempt + 1 < attempts) {
19962
+ if (response.status >= 500 && attempt + 1 < tries) {
19792
19963
  await waitForRetry();
19793
19964
  continue;
19794
19965
  }
@@ -19796,11 +19967,11 @@ function createEnvoyClient(config2) {
19796
19967
  }
19797
19968
  throw new Error("Envoy request exhausted retries");
19798
19969
  };
19799
- const post = (path2, body) => request(path2, {
19970
+ const post = (path2, body, options) => request(path2, {
19800
19971
  method: "POST",
19801
19972
  headers: { "Content-Type": "application/json" },
19802
19973
  body: JSON.stringify(body)
19803
- });
19974
+ }, options);
19804
19975
  return {
19805
19976
  subscribe: async (input) => InterestWireSchema.parse(JSON.parse(await post("/v1/interests/subscribe", {
19806
19977
  session_id: input.sessionID,
@@ -19886,7 +20057,12 @@ function createEnvoyClient(config2) {
19886
20057
  search.set("title", input.title);
19887
20058
  const query = search.size === 0 ? "" : `?${search.toString()}`;
19888
20059
  return SessionWireSchema.array().parse(JSON.parse(await request(`/v1/sessions${query}`, {})));
19889
- }
20060
+ },
20061
+ replay: async (input) => ReplayWireSchema.parse(JSON.parse(await post("/v1/replay", {
20062
+ session_id: input.sessionID,
20063
+ subjects: input.subjects.map((cursor) => cursor.after === undefined ? { subject: cursor.subject } : { subject: cursor.subject, after: cursor.after }),
20064
+ ...input.maxMessages === undefined ? {} : { max_messages: input.maxMessages }
20065
+ }, { timeoutMs: REPLAY_TIMEOUT_MS, retry: false })))
19890
20066
  };
19891
20067
  }
19892
20068
  function waitForRetry() {