@sjawhar/pi-envoy 10.16.0 → 10.17.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
@@ -14000,7 +14000,7 @@ function imagesArgument(z2, owner, cap) {
14000
14000
  }
14001
14001
  var ASK_URGENCIES = ["low", "med", "high", "blocking"];
14002
14002
  var ASK_QUESTION_MAX = 800;
14003
- var DISPATCH_BODY_MAX = 2000;
14003
+ var DISPATCH_BODY_MAX = 32000;
14004
14004
  var SEARCH_QUERY_MAX = 1000;
14005
14005
  var SEARCH_QUERY_HINT = "search with a short phrase of a few words, not a passage";
14006
14006
  var SEARCH_KIND_DEPTH = 100;
@@ -14058,7 +14058,7 @@ var dispatchToolSpecs = [
14058
14058
  arguments: (z2) => ({
14059
14059
  issue: z2.string().describe(ISSUE_REFERENCE),
14060
14060
  status: z2.enum(ISSUE_STATUSES).describe("New lifecycle status.").optional(),
14061
- reason: z2.string({ max: 2000 }).describe("Required with status done, and only with it: why the issue is closing, at most 2,000 characters. Posted on the issue as a message before it closes.").optional(),
14061
+ reason: z2.string({ max: DISPATCH_BODY_MAX }).describe(`Required with status done, and only with it: why the issue is closing, at most ${DISPATCH_BODY_MAX} characters. Posted on the issue as a message before it closes.`).optional(),
14062
14062
  title: z2.string({ min: 1, max: ISSUE_TITLE_MAX }).describe("Replacement title, at most 1,000 characters.").optional(),
14063
14063
  labels: z2.array(z2.string({ min: 1, max: 40 }), { max: 20 }).describe("Replacement label set, at most 20 labels of up to 40 characters; replaces every existing label.").optional(),
14064
14064
  priority: z2.number({ int: true, min: 0, max: 3 }).nullable().optional().describe("Coarse priority: 0 is P0 (highest) through 3 is P3 (lowest); null clears it."),
@@ -14198,7 +14198,7 @@ var dispatchToolSpecs = [
14198
14198
  {
14199
14199
  name: "dispatch_comment",
14200
14200
  example: { issue: "DSP-1", body: "Looks good." },
14201
- description: "Add review feedback to an issue or project document quote, or reply to a question asked with dispatch ask. " + "Do not use it for an exact replacement; use " + `dispatch suggest instead. A quote anchor is pinned to its block. Body is at most 2,000 characters, the lines images appends included. ${OWNER_REFERENCE}`,
14201
+ description: "Add review feedback to an issue or project document quote, or reply to a question asked with dispatch ask. " + "Do not use it for an exact replacement; use " + `dispatch suggest instead. A quote anchor is pinned to its block. Body is at most ${DISPATCH_BODY_MAX} characters, the lines images appends included. ${OWNER_REFERENCE}`,
14202
14202
  arguments: (z2) => ({
14203
14203
  issue: z2.string().describe(ISSUE_REFERENCE).optional(),
14204
14204
  project: z2.string().describe("Project key owning the document.").optional(),
@@ -14206,11 +14206,11 @@ var dispatchToolSpecs = [
14206
14206
  ref: z2.string().describe("Optional dispatch:// issue or document reference.").optional(),
14207
14207
  quote: z2.string().describe("Optional exact quoted document text.").optional(),
14208
14208
  occurrence: z2.number({ int: true, min: 0 }).describe("Optional zero-based occurrence of quote.").optional(),
14209
- body: z2.string({ max: DISPATCH_BODY_MAX }).describe("Review comment, at most 2,000 characters."),
14209
+ body: z2.string({ max: DISPATCH_BODY_MAX }).describe(`Review comment, at most ${DISPATCH_BODY_MAX} characters.`),
14210
14210
  reply_to: z2.string().describe("A comment id (uuid); replying to any comment in a thread continues that thread (an " + "ask's clarification thread included). To reply to an ask, use reply_to_ask with the " + "ask id instead.").optional(),
14211
14211
  reply_to_ask: z2.string().describe("Optional ask id to reply to, threading this comment under that question. Mutually " + "exclusive with reply_to.").optional(),
14212
14212
  turn: z2.enum(["agent", "human"]).describe("Only with reply_to_ask: who holds the turn after this reply. agent: a progress note - " + "you keep the turn and the ask stays 'Waiting on agents' for the human; human (default): " + "you need the human to act - the ask returns to 'Waiting on you'.").optional(),
14213
- images: imagesArgument(z2, "the issue (or the project document's project)", "the 2,000-character body limit")
14213
+ images: imagesArgument(z2, "the issue (or the project document's project)", `the ${DISPATCH_BODY_MAX}-character body limit`)
14214
14214
  }),
14215
14215
  validation: commentValidation
14216
14216
  },
@@ -14222,7 +14222,7 @@ var dispatchToolSpecs = [
14222
14222
  quote: "old wording",
14223
14223
  replace_with: "new wording"
14224
14224
  },
14225
- description: "Propose an exact replacement for quoted document text. Do not use it for general feedback; use " + `dispatch comment instead. Optional explanation is at most 2,000 characters. ${OWNER_REFERENCE}`,
14225
+ description: "Propose an exact replacement for quoted document text. Do not use it for general feedback; use " + `dispatch comment instead. Optional explanation is at most ${DISPATCH_BODY_MAX} characters. ${OWNER_REFERENCE}`,
14226
14226
  arguments: (z2) => ({
14227
14227
  issue: z2.string().describe(ISSUE_REFERENCE).optional(),
14228
14228
  project: z2.string().describe("Project key owning the document.").optional(),
@@ -14230,7 +14230,7 @@ var dispatchToolSpecs = [
14230
14230
  ref: z2.string().describe("Optional dispatch:// issue or document reference.").optional(),
14231
14231
  quote: z2.string().describe("Exact document text to replace."),
14232
14232
  replace_with: z2.string().describe("Replacement text; a CR LF or a lone carriage return in it is written as a line feed."),
14233
- body: z2.string({ max: 2000 }).describe("Optional rationale, at most 2,000 characters.").optional(),
14233
+ body: z2.string({ max: DISPATCH_BODY_MAX }).describe(`Optional rationale, at most ${DISPATCH_BODY_MAX} characters.`).optional(),
14234
14234
  occurrence: z2.number({ int: true, min: 0 }).describe("Optional zero-based occurrence of quote.").optional()
14235
14235
  }),
14236
14236
  validation: documentOwnerValidation(true, true)
@@ -14238,12 +14238,12 @@ var dispatchToolSpecs = [
14238
14238
  {
14239
14239
  name: "dispatch_message",
14240
14240
  example: { issue: "DSP-1", body: "Implementation started." },
14241
- description: "Post a note humans must read now: a reply to a human's message or a deliverable that landed. A to-do only a human can " + "complete is an ask (dispatch ask), so it reaches their inbox. Never progress or status updates - Dispatch is a high-signal " + "record, not a log. Not a design decision (write it as a decision block in the document) or document feedback (dispatch comment). " + "To answer a human's direct message to this session - one sent from the Agents page, which names no issue - pass that message's bare id as " + "in_reply_to and no issue; the reply lands in that conversation. Another call with the same in_reply_to and new text posts a follow-up, " + "threaded under this session's first reply; the same text again posts nothing. dispatch read --message <id> reads " + "that conversation back. Every other message names its issue. " + `Body is at most 2,000 characters, the lines images appends included. ${ISSUE_REFERENCE}`,
14241
+ description: "Post a note humans must read now: a reply to a human's message or a deliverable that landed. A to-do only a human can " + "complete is an ask (dispatch ask), so it reaches their inbox. Never progress or status updates - Dispatch is a high-signal " + "record, not a log. Not a design decision (write it as a decision block in the document) or document feedback (dispatch comment). " + "To answer a human's direct message to this session - one sent from the Agents page, which names no issue - pass that message's bare id as " + "in_reply_to and no issue; the reply lands in that conversation. Another call with the same in_reply_to and new text posts a follow-up, " + "threaded under this session's first reply; the same text again posts nothing. dispatch read --message <id> reads " + "that conversation back. Every other message names its issue. " + `Body is at most ${DISPATCH_BODY_MAX} characters, the lines images appends included. ${ISSUE_REFERENCE}`,
14242
14242
  arguments: (z2) => ({
14243
14243
  issue: z2.string().describe(`${ISSUE_REFERENCE} Omit it only when in_reply_to answers a human's direct message to this session.`).optional(),
14244
- body: z2.string({ max: DISPATCH_BODY_MAX }).describe("Update text, at most 2,000 characters."),
14244
+ body: z2.string({ max: DISPATCH_BODY_MAX }).describe(`Update text, at most ${DISPATCH_BODY_MAX} characters.`),
14245
14245
  in_reply_to: z2.string().describe("Optional message id or dispatch://KEY/message/<id> reference to reply to, threading " + "this message under it so the reply stays with the original in the Conversation. " + "A bare id with no issue answers a human's direct message to this session; a " + "dispatch://KEY/message/<id> names the issue its message lives on, so that form is a " + "reply on that issue.").optional(),
14246
- images: imagesArgument(z2, "the issue (a reply to a direct message: your own conversation, dispatch://agent/<your session id>)", "the 2,000-character body limit")
14246
+ images: imagesArgument(z2, "the issue (a reply to a direct message: your own conversation, dispatch://agent/<your session id>)", `the ${DISPATCH_BODY_MAX}-character body limit`)
14247
14247
  }),
14248
14248
  validation: messageValidation
14249
14249
  },
@@ -14335,11 +14335,11 @@ var dispatchToolSpecs = [
14335
14335
  {
14336
14336
  name: "dispatch_read",
14337
14337
  example: { issue: "DSP-1" },
14338
- description: "Read an issue or project-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, " + "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). " + "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). " + "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,
14338
+ 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). " + "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,
14339
14339
  arguments: (z2) => ({
14340
14340
  issue: z2.string().describe(ISSUE_REFERENCE).optional(),
14341
14341
  project: z2.string().describe("Project key owning the document.").optional(),
14342
- artifact: z2.string().describe("Project document artifact id, slug, or filename.").optional(),
14342
+ artifact: z2.string().describe("Document artifact id, slug, or filename: one of the issue's, or a project document.").optional(),
14343
14343
  ref: z2.string().describe("Optional dispatch:// issue or document reference.").optional(),
14344
14344
  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()
14345
14345
  }),
@@ -15585,6 +15585,8 @@ function parseDispatchRef(ref) {
15585
15585
  ...version3 === undefined ? {} : { version: Number(version3) }
15586
15586
  };
15587
15587
  }
15588
+ if (version3 !== undefined)
15589
+ return null;
15588
15590
  if (targetID === undefined || targetKind !== "ask" && targetKind !== "comment")
15589
15591
  return null;
15590
15592
  return {
@@ -16455,7 +16457,27 @@ function messageIdOf(value) {
16455
16457
  }
16456
16458
  var idPrefixPattern = /^[0-9a-f][0-9a-f-]{7,}$/i;
16457
16459
  function refOwnerName(ref) {
16458
- return ref.owner.kind === "issue" ? ref.owner.issue : `${ref.owner.project}/${ref.artifact}`;
16460
+ return ref.owner.kind === "issue" ? ref.owner.issue : `${ref.owner.project}/${ref.artifact ?? ref.id}`;
16461
+ }
16462
+ async function refuseItemOffRef(tool, kind, ref, refText, item, documentId) {
16463
+ if (ref.owner.kind === "issue") {
16464
+ if (item.issue_key === ref.owner.issue)
16465
+ return;
16466
+ } else if (item.issue_key === null) {
16467
+ const id = await documentId();
16468
+ if ([
16469
+ item.artifact_id,
16470
+ item.anchor?.artifact_id,
16471
+ item.block_artifact?.id,
16472
+ item.approval?.artifact_id
16473
+ ].includes(id)) {
16474
+ return;
16475
+ }
16476
+ }
16477
+ const where = item.issue_key === null ? "another document" : `${item.issue_key}; name it dispatch://${item.issue_key}/${kind}/${item.id}`;
16478
+ throw new ToolInputError(tool, [
16479
+ `${refText} names ${kind} ${item.id} on ${refOwnerName(ref)}, but it is on ${where}`
16480
+ ]);
16459
16481
  }
16460
16482
  async function resolveIdPrefix(tool, kind, id, ownerName, list) {
16461
16483
  const fullID = normalizeUUID(id);
@@ -16528,7 +16550,17 @@ async function resolveAskArgument(tool, args, client, sessionId) {
16528
16550
  asks === undefined ? askIdShapeProblem : `${askIdShapeProblem}; ${openAskHints(asks)}`
16529
16551
  ]);
16530
16552
  }
16531
- return resolveIdPrefix(tool, "ask", id, "this session", () => sessionOpenAsks(client, sessionId));
16553
+ const resolved = await resolveIdPrefix(tool, "ask", id, "this session", () => sessionOpenAsks(client, sessionId));
16554
+ const reference = stringArg(args, "ask");
16555
+ const ref = reference.startsWith("dispatch://") ? parseDispatchRef(reference) : null;
16556
+ if (ref === null)
16557
+ return { id: resolved };
16558
+ const read = await client.getAsk(resolved);
16559
+ await refuseItemOffRef(tool, "ask", ref, reference, read.ask, async () => {
16560
+ const document = await resolveArtifact(client, ref.owner, ref.artifact, { canonical: true });
16561
+ return document.artifact.id;
16562
+ });
16563
+ return { id: resolved, read };
16532
16564
  }
16533
16565
  function messageInReplyTo(args) {
16534
16566
  const inReplyTo = optionalString(args, "in_reply_to");
@@ -16552,14 +16584,14 @@ function argumentProblems(tool, args) {
16552
16584
  case "dispatch_follow": {
16553
16585
  const ask = optionalString(args, "ask");
16554
16586
  if (ask?.startsWith("dispatch://") && parseDispatchRef(ask)?.kind !== "ask") {
16555
- problems.push(askIdProblem);
16587
+ problems.push(itemRefProblem(ask) ?? askIdProblem);
16556
16588
  }
16557
16589
  break;
16558
16590
  }
16559
16591
  case "dispatch_resolve_comment": {
16560
16592
  const comment = optionalString(args, "comment");
16561
16593
  if (comment?.startsWith("dispatch://") && parseDispatchRef(comment)?.kind !== "comment") {
16562
- problems.push(commentIdProblem);
16594
+ problems.push(itemRefProblem(comment) ?? commentIdProblem);
16563
16595
  }
16564
16596
  break;
16565
16597
  }
@@ -16649,26 +16681,96 @@ function dispatchRefFromUrl(value, serverUrl) {
16649
16681
  if (!documentPage)
16650
16682
  return;
16651
16683
  const [, project, slug] = documentPage;
16652
- const document = `dispatch://${project}/artifact/${decodeURIComponent(slug ?? "")}${versionOf("version")}`;
16684
+ const document = `dispatch://${project}/artifact/${decodeURIComponent(slug ?? "")}`;
16653
16685
  const item = itemFromSearch(url2.search);
16654
16686
  if (item === null)
16655
16687
  return;
16656
- return item === undefined ? document : `${document}/${item.kind}/${item.id}`;
16688
+ return item === undefined ? `${document}${versionOf("version")}` : `${document}/${item.kind}/${item.id}`;
16657
16689
  }
16658
16690
  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";
16659
16691
  var ownerRequiredProblem = "issue is required; supply issue or set LEGION_ISSUE";
16660
16692
  function innerRefProblem(tool, ref, refText) {
16661
- if (ref.kind !== "ask" && ref.kind !== "comment" && ref.kind !== "message")
16693
+ if (ref.kind !== "ask" && ref.kind !== "comment" && ref.kind !== "message" && ref.kind !== "log" && ref.kind !== "children") {
16662
16694
  return;
16695
+ }
16663
16696
  if (tool === "dispatch_read" || tool === "dispatch_ask")
16664
16697
  return;
16665
16698
  const owner = refOwnerName(ref);
16666
16699
  const documentTool = tool === "dispatch_suggest" || tool === "dispatch_doc_edit";
16667
16700
  const ownerArguments = ref.owner.kind === "issue" ? `issue ${ref.owner.issue}${documentTool ? " and artifact <the document's slug>" : ""}` : `project ${ref.owner.project} and artifact ${ref.artifact}`;
16668
16701
  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}`;
16669
- const item = ref.kind === "ask" ? "an ask" : `a ${ref.kind}`;
16702
+ const item = ref.kind === "ask" ? "an ask" : ref.kind === "log" ? "the event log" : ref.kind === "children" ? "the children" : `a ${ref.kind}`;
16670
16703
  return `ref ${refText} names ${item} on ${owner}; dispatch ${commandName(tool)} would act on ${owner} and drop the ${ref.kind}: ${fix}`;
16671
16704
  }
16705
+ var documentItemPattern = /^dispatch:\/\/([A-Z][A-Z0-9]{1,9}-[1-9][0-9]*)\/artifact\/[^/@]+(?:@v\d+)?\/(ask|comment)\/([^/]+)$/;
16706
+ var versionedProjectItemPattern = /^dispatch:\/\/([A-Z][A-Z0-9]{1,9})\/artifact\/([^/@]+)@v\d+\/(ask|comment)\/([^/]+)$/;
16707
+ function itemRefProblem(value) {
16708
+ const throughDocument = value.match(documentItemPattern);
16709
+ if (throughDocument !== null) {
16710
+ const [, issue2, kind, id] = throughDocument;
16711
+ return `${value} names ${kind === "ask" ? "an ask" : "a comment"} through its document, which Dispatch does not address; name it dispatch://${issue2}/${kind}/${id}`;
16712
+ }
16713
+ const versioned = value.match(versionedProjectItemPattern);
16714
+ if (versioned !== null) {
16715
+ const [, project, slug, kind, id] = versioned;
16716
+ const item = kind === "ask" ? "an ask" : "a comment";
16717
+ return `${value} names ${item} at a document version, which ${item} does not have; name it dispatch://${project}/artifact/${slug}/${kind}/${id}`;
16718
+ }
16719
+ return;
16720
+ }
16721
+ function ownerConflictProblems(tool, args, parsedRef, refText) {
16722
+ const problems = [];
16723
+ const command = `dispatch ${commandName(tool)}`;
16724
+ const issue2 = optionalString(args, "issue");
16725
+ const project = optionalString(args, "project");
16726
+ const artifact = optionalString(args, "artifact");
16727
+ const version2 = optionalNumber(args, "version");
16728
+ const ref = tool === "dispatch_ask" && (issue2 !== undefined || project !== undefined) ? null : parsedRef;
16729
+ const refDocument = ref === null ? undefined : ref.owner.kind === "project" ? ref.artifact ?? ref.id : ref.kind === "spec" || ref.kind === "artifact" ? ref.id : undefined;
16730
+ if (ref !== null && refText !== undefined) {
16731
+ const refOwner = refOwnerName(ref);
16732
+ if (issue2 !== undefined && (ref.owner.kind !== "issue" || nativeIssueKeyPattern.test(issue2) && issue2 !== ref.owner.issue)) {
16733
+ problems.push(`issue ${issue2} and ref ${refText} name two owners; --ref ${refText} alone acts on ${refOwner}, and --issue ${issue2} alone acts on ${issue2}`);
16734
+ }
16735
+ if (project !== undefined && ref.owner.kind === "issue") {
16736
+ problems.push(`project ${project} and ref ${refText} name two owners; --ref ${refText} alone acts on ${refOwner}, and --project ${project}${artifact === undefined ? "" : ` --artifact ${artifact}`} alone acts on that project document`);
16737
+ }
16738
+ if (tool !== "dispatch_ask" && project === undefined && artifact !== undefined && refDocument !== undefined && artifact !== refDocument) {
16739
+ const owner = ref.owner.kind === "issue" ? `--issue ${ref.owner.issue}` : `--project ${ref.owner.project}`;
16740
+ problems.push(`artifact ${artifact} and ref ${refText} name two documents; --ref ${refText} alone names ${refDocument}, and ${owner} --artifact ${artifact} names ${artifact}`);
16741
+ }
16742
+ if (tool === "dispatch_read" && project === undefined && artifact !== undefined && ref.owner.kind === "issue" && ref.kind !== "issue" && refDocument === undefined) {
16743
+ problems.push(`ref ${refText} names no document, so dispatch read would read it and drop artifact ${artifact}: drop --artifact to read the ref, or read the document with --issue ${ref.owner.issue} --artifact ${artifact}`);
16744
+ }
16745
+ if (version2 !== undefined && ref.version !== undefined && version2 !== ref.version) {
16746
+ problems.push(`version ${version2} and ref ${refText} name two versions; name the version once`);
16747
+ }
16748
+ if (ref.version !== undefined && (tool === "dispatch_comment" || tool === "dispatch_suggest" || tool === "dispatch_doc_edit")) {
16749
+ problems.push(`${command} acts on the live document, so the version ref ${refText} names would be dropped: drop its @v${ref.version}`);
16750
+ }
16751
+ if (ref.version !== undefined && tool === "dispatch_read") {
16752
+ problems.push(`dispatch read prints the live document's summary, so the version ref ${refText} names would be dropped: read that version with dispatch doc-read --ref ${refText}`);
16753
+ }
16754
+ }
16755
+ const projectOwned = project !== undefined || ref?.owner.kind === "project";
16756
+ if (tool === "dispatch_comment" && !projectOwned && optionalString(args, "quote") === undefined) {
16757
+ const document = artifact ?? refDocument;
16758
+ if (document !== undefined) {
16759
+ 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`);
16760
+ }
16761
+ }
16762
+ if (tool === "dispatch_ask") {
16763
+ const anchor = asObject(args.anchor);
16764
+ const anchorArtifact = anchor === null ? undefined : optionalString(anchor, "artifact");
16765
+ if (!projectOwned && artifact !== undefined && anchor === null) {
16766
+ problems.push(`${command} opens the ask on the issue unless anchor quotes a document, so artifact ${artifact} would be dropped: add --anchor-json '${JSON.stringify({ artifact, quote: "<exact text>" })}' to ask about a passage of it, or drop --artifact`);
16767
+ }
16768
+ if (artifact !== undefined && anchorArtifact !== undefined && anchorArtifact !== artifact) {
16769
+ problems.push(`artifact ${artifact} and anchor.artifact ${anchorArtifact} name two documents; name the document once`);
16770
+ }
16771
+ }
16772
+ return problems;
16773
+ }
16672
16774
  var toolSchemas = new Map;
16673
16775
  function toolSchema(tool) {
16674
16776
  const cached2 = toolSchemas.get(tool);
@@ -16703,7 +16805,7 @@ async function resolveOwnerArguments(tool, input, cwd, env, exec, dashboardUrls,
16703
16805
  }
16704
16806
  ref = parseDispatchRef(refText);
16705
16807
  if (ref === null) {
16706
- problems.push(tool === "dispatch_ask" && !refText.startsWith("dispatch://") ? "ref must be a dispatch:// reference" : refGrammarProblem);
16808
+ problems.push(itemRefProblem(refText) ?? (tool === "dispatch_ask" && !refText.startsWith("dispatch://") ? "ref must be a dispatch:// reference" : refGrammarProblem));
16707
16809
  } else {
16708
16810
  const inner = innerRefProblem(tool, ref, refText);
16709
16811
  if (inner !== undefined)
@@ -16713,7 +16815,9 @@ async function resolveOwnerArguments(tool, input, cwd, env, exec, dashboardUrls,
16713
16815
  const issueArgument = args.issue;
16714
16816
  const projectArgument = args.project;
16715
16817
  const artifactArgument = args.artifact;
16716
- const versionArgument = args.version;
16818
+ const versionFromRef = tool === "dispatch_doc_read" && args.version === undefined ? ref?.version : undefined;
16819
+ const refVersion = versionFromRef === undefined ? {} : { version: versionFromRef };
16820
+ const ownerRef = tool === "dispatch_ask" && (issueArgument !== undefined || projectArgument !== undefined) ? null : ref;
16717
16821
  if (issueArgument !== undefined && projectArgument !== undefined) {
16718
16822
  problems.push("exactly one of issue and project is required");
16719
16823
  }
@@ -16721,45 +16825,47 @@ async function resolveOwnerArguments(tool, input, cwd, env, exec, dashboardUrls,
16721
16825
  if (!PROJECT_KEY_PATTERN.test(projectArgument)) {
16722
16826
  problems.push(`project is not a project key: ${PROJECT_KEY_RULE}`);
16723
16827
  }
16724
- const refDocument = ref?.owner.kind === "project" ? ref.artifact ?? ref.id : undefined;
16725
- if (ref?.owner.kind === "project" && (ref.owner.project !== projectArgument || artifactArgument !== undefined && artifactArgument !== refDocument)) {
16828
+ const refDocument = ownerRef?.owner.kind === "project" ? ownerRef.artifact ?? ownerRef.id : undefined;
16829
+ if (ownerRef?.owner.kind === "project" && (ownerRef.owner.project !== projectArgument || artifactArgument !== undefined && artifactArgument !== refDocument)) {
16726
16830
  problems.push("project and ref must name the same document");
16727
16831
  }
16728
16832
  return {
16729
16833
  args: {
16730
16834
  ...args,
16731
16835
  ...artifactArgument === undefined && refDocument !== undefined ? { artifact: refDocument } : {},
16732
- ...versionArgument === undefined && ref?.version !== undefined ? { version: ref.version } : {}
16836
+ ...refVersion
16733
16837
  },
16734
16838
  ref,
16735
16839
  owner: { kind: "project", project: projectArgument }
16736
16840
  };
16737
16841
  }
16738
- if (ref?.owner.kind === "project") {
16842
+ if (ownerRef?.owner.kind === "project") {
16739
16843
  return {
16740
16844
  args: {
16741
16845
  ...args,
16742
- project: ref.owner.project,
16743
- ...artifactArgument === undefined ? { artifact: ref.artifact ?? ref.id } : {},
16744
- ...versionArgument === undefined && ref.version !== undefined ? { version: ref.version } : {}
16846
+ project: ownerRef.owner.project,
16847
+ ...artifactArgument === undefined ? { artifact: ownerRef.artifact ?? ownerRef.id } : {},
16848
+ ...refVersion
16745
16849
  },
16746
16850
  ref,
16747
- owner: ref.owner
16851
+ owner: ownerRef.owner
16748
16852
  };
16749
16853
  }
16750
- if (issueArgument !== undefined || ref !== null) {
16854
+ if (issueArgument !== undefined || ownerRef !== null) {
16751
16855
  const issue3 = typeof issueArgument === "string" ? canonicalExternalIssueRef(issueArgument) : ref?.owner.kind === "issue" ? ref.owner.issue : undefined;
16752
16856
  return {
16753
16857
  args: {
16754
16858
  ...args,
16755
16859
  ...issue3 === undefined ? {} : { issue: issue3 },
16756
- ...artifactArgument === undefined && (ref?.kind === "spec" || ref?.kind === "artifact") ? { artifact: ref.id } : {},
16757
- ...versionArgument === undefined && ref?.version !== undefined ? { version: ref.version } : {}
16860
+ ...artifactArgument === undefined && tool !== "dispatch_ask" && (ref?.kind === "spec" || ref?.kind === "artifact") ? { artifact: ref.id } : {},
16861
+ ...refVersion
16758
16862
  },
16759
16863
  ref,
16760
16864
  owner: issue3 === undefined ? null : { kind: "issue", issue: issue3 }
16761
16865
  };
16762
16866
  }
16867
+ if (typeof args.ref === "string")
16868
+ return { args, ref, owner: null };
16763
16869
  const replyTarget = args.in_reply_to;
16764
16870
  if (tool === "dispatch_message" && typeof replyTarget === "string" && !replyTarget.startsWith("dispatch://")) {
16765
16871
  return { args, ref, owner: null };
@@ -17480,7 +17586,9 @@ async function executeDispatchTool(input) {
17480
17586
  const exec = input.exec ?? defaultExec;
17481
17587
  const problems = [];
17482
17588
  const ownerArguments = await resolveOwnerArguments(input.tool, input.args, input.cwd, env, exec, input.config.publicUrl === undefined ? [configUrl] : [configUrl, input.config.publicUrl], problems);
17483
- const ownerMissing = issueFreeTools[input.tool] !== true && ownerArguments.owner === null;
17589
+ const conflicts = issueFreeTools[input.tool] === true ? [] : ownerConflictProblems(input.tool, input.args, ownerArguments.ref, optionalString(ownerArguments.args, "ref"));
17590
+ problems.push(...conflicts);
17591
+ const ownerMissing = issueFreeTools[input.tool] !== true && (ownerArguments.owner === null || conflicts.length > 0);
17484
17592
  const schema = toolSchema(input.tool);
17485
17593
  const parsed = schema.safeParse(ownerArguments.args, { reportInput: true });
17486
17594
  if (!parsed.success) {
@@ -17530,6 +17638,13 @@ async function executeDispatchTool(input) {
17530
17638
  kind: "issue",
17531
17639
  issue: await resolveExistingIssue(client, ownerArguments.owner.issue)
17532
17640
  } : ownerArguments.owner;
17641
+ const issueArgument = optionalString(input.args, "issue");
17642
+ const refText = optionalString(ownerArguments.args, "ref");
17643
+ if (input.tool !== "dispatch_ask" && owner?.kind === "issue" && issueArgument !== undefined && refText !== undefined && ownerArguments.ref?.owner.kind === "issue" && owner.issue !== ownerArguments.ref.owner.issue) {
17644
+ throw new ToolInputError(input.tool, [
17645
+ `issue ${issueArgument} and ref ${refText} name two owners; --ref ${refText} alone acts on ${ownerArguments.ref.owner.issue}, and --issue ${issueArgument} alone acts on ${owner.issue}`
17646
+ ]);
17647
+ }
17533
17648
  const issue2 = () => {
17534
17649
  if (owner?.kind !== "issue")
17535
17650
  throw new Error("issue is required");
@@ -17857,7 +17972,7 @@ async function executeDispatchTool(input) {
17857
17972
  }
17858
17973
  case "dispatch_resolve_ask": {
17859
17974
  const kind = stringArg(args, "kind");
17860
- const id = await resolveAskArgument(input.tool, args, client, input.sessionId);
17975
+ const { id } = await resolveAskArgument(input.tool, args, client, input.sessionId);
17861
17976
  const ask = await client.resolveAsk(id, {
17862
17977
  kind,
17863
17978
  reason: stringArg(args, "reason"),
@@ -17883,6 +17998,10 @@ async function executeDispatchTool(input) {
17883
17998
  document = artifact;
17884
17999
  id = await resolveIdPrefix(input.tool, "comment", ref.id, refOwnerName(ref), () => client.getArtifactComments(artifact.id));
17885
18000
  }
18001
+ if (ref !== null) {
18002
+ const read = await client.getComment(id);
18003
+ await refuseItemOffRef(input.tool, "comment", ref, reference, read.comment, async () => document === undefined ? "" : document.id);
18004
+ }
17886
18005
  const comment = await client.resolveComment(id, actor);
17887
18006
  const { label, details } = await commentOwnerResult(client, comment, document);
17888
18007
  return { text: `Resolved comment ${comment.id} on ${label}.`, details };
@@ -17935,7 +18054,7 @@ ${await followsAsk(askOwner, input.subscriptions)}`,
17935
18054
  const options = args.options;
17936
18055
  const multiple = optionalBoolean(args, "multiple");
17937
18056
  const urgency = askUrgency(args);
17938
- const id = await resolveAskArgument(input.tool, args, client, input.sessionId);
18057
+ const { id } = await resolveAskArgument(input.tool, args, client, input.sessionId);
17939
18058
  const ask = await client.editAsk(id, {
17940
18059
  ...question === undefined ? {} : { question },
17941
18060
  ...Array.isArray(options) ? { options } : {},
@@ -18264,13 +18383,13 @@ ${trailer.join(`
18264
18383
  const sessionId = input.sessionId?.trim();
18265
18384
  if (!sessionId)
18266
18385
  throw new Error("host session id is required for dispatch follow");
18267
- const ask = await resolveAskArgument(input.tool, args, client, sessionId);
18386
+ const { id: ask, read: refRead } = await resolveAskArgument(input.tool, args, client, sessionId);
18268
18387
  const action = stringArg(args, "action");
18269
18388
  if (action === "unfollow") {
18270
18389
  await client.unfollowAsk(ask, sessionId, actor);
18271
18390
  return { text: `Unfollowed ask ${ask}.`, details: { ask } };
18272
18391
  }
18273
- const read = await client.getAsk(ask);
18392
+ const read = refRead ?? await client.getAsk(ask);
18274
18393
  await client.followAsk(ask, sessionId, actor);
18275
18394
  return {
18276
18395
  text: `Following ask ${ask}: its answer and replies reach this session directly.`,
@@ -18304,9 +18423,15 @@ ${trailer.join(`
18304
18423
  ...showImages(pictures3)
18305
18424
  };
18306
18425
  }
18426
+ let refDocumentId;
18427
+ const documentIdOf = (ref) => {
18428
+ refDocumentId ??= resolveDocument(ref.owner, ref.artifact).then((resolved) => resolved.artifact.id);
18429
+ return refDocumentId;
18430
+ };
18307
18431
  if (ownerArguments.ref?.kind === "ask") {
18308
18432
  const ref = ownerArguments.ref;
18309
- const askRead = await readAskRef(input.tool, ref.id, refOwnerName(ref), client, async () => ref.owner.kind === "issue" ? client.listIssueAsks(ref.owner.issue) : client.getArtifactAsks((await resolveDocument(ref.owner, ref.artifact)).artifact.id, "all"));
18433
+ const askRead = await readAskRef(input.tool, ref.id, refOwnerName(ref), client, async () => ref.owner.kind === "issue" ? client.listIssueAsks(ref.owner.issue) : client.getArtifactAsks(await documentIdOf(ref), "all"));
18434
+ await refuseItemOffRef(input.tool, "ask", ref, stringArg(args, "ref"), askRead.ask, () => documentIdOf(ref));
18310
18435
  const askRef = refTarget(ref, "ask", askRead.ask.id);
18311
18436
  const [pictures3, graph2] = await Promise.all([
18312
18437
  picturesOf(askTexts(askRead)),
@@ -18320,8 +18445,9 @@ ${trailer.join(`
18320
18445
  }
18321
18446
  if (ownerArguments.ref?.kind === "comment") {
18322
18447
  const ref = ownerArguments.ref;
18323
- const id = await resolveIdPrefix(input.tool, "comment", ref.id, refOwnerName(ref), async () => ref.owner.kind === "issue" ? client.getComments(ref.owner.issue) : client.getArtifactComments((await resolveDocument(ref.owner, ref.artifact)).artifact.id));
18448
+ const id = await resolveIdPrefix(input.tool, "comment", ref.id, refOwnerName(ref), async () => ref.owner.kind === "issue" ? client.getComments(ref.owner.issue) : client.getArtifactComments(await documentIdOf(ref)));
18324
18449
  const comment = await client.getComment(id);
18450
+ await refuseItemOffRef(input.tool, "comment", ref, stringArg(args, "ref"), comment.comment, () => documentIdOf(ref));
18325
18451
  const commentRef = refTarget(ref, "comment", id);
18326
18452
  const [pictures3, graph2] = await Promise.all([
18327
18453
  picturesOf(datedBodies([comment.comment, ...comment.replies])),
@@ -18349,21 +18475,28 @@ ${trailer.join(`
18349
18475
  ...showImages(pictures3)
18350
18476
  };
18351
18477
  }
18352
- if (documentOwner().kind === "project") {
18353
- const resolved = await resolveDocument(documentOwner(), stringArg(args, "artifact"));
18354
- const documentRef = dispatchDocumentRef(resolved.artifact.project, resolved.artifact.slug);
18478
+ const documentReference = optionalString(args, "artifact");
18479
+ if (documentOwner().kind === "project" || documentReference !== undefined) {
18480
+ const resolved = await resolveDocument(documentOwner(), documentReference);
18481
+ const { artifact } = resolved;
18482
+ const ownerKey = resolved.issue?.key ?? artifact.project;
18483
+ const documentRef = dispatchDocumentRef(ownerKey, artifact.slug);
18484
+ const approval = approvalLine(artifact);
18355
18485
  return {
18356
18486
  text: [
18357
- `Document: ${resolved.artifact.project} / ${resolved.artifact.name}`,
18487
+ `Document: ${ownerKey} / ${artifact.name}`,
18488
+ ...resolved.issue === undefined ? [] : [
18489
+ `Issue: ${resolved.issue.key} [${resolved.issue.status}] ${resolved.issue.title}`
18490
+ ],
18358
18491
  `Reference: ${documentRef}`,
18359
- `Versions: ${resolved.artifact.versions.length}`,
18360
- ...approvalLine(resolved.artifact) === undefined ? [] : [approvalLine(resolved.artifact)],
18492
+ `Versions: ${artifact.versions.length}`,
18493
+ ...approval === undefined ? [] : [approval],
18361
18494
  ...await graphSections(client, documentRef)
18362
18495
  ].join(`
18363
18496
  `),
18364
18497
  details: {
18365
- project: resolved.artifact.project,
18366
- document: documentLabel(resolved.artifact.project, resolved.artifact.slug)
18498
+ ...resolved.issue === undefined ? { project: artifact.project } : { issue: resolved.issue.key },
18499
+ document: documentLabel(ownerKey, artifact.slug)
18367
18500
  }
18368
18501
  };
18369
18502
  }
package/dist/envoy.js CHANGED
@@ -29987,7 +29987,7 @@ function imagesArgument(z2, owner, cap) {
29987
29987
  }
29988
29988
  var ASK_URGENCIES = ["low", "med", "high", "blocking"];
29989
29989
  var ASK_QUESTION_MAX = 800;
29990
- var DISPATCH_BODY_MAX = 2000;
29990
+ var DISPATCH_BODY_MAX = 32000;
29991
29991
  var SEARCH_QUERY_MAX = 1000;
29992
29992
  var SEARCH_QUERY_HINT = "search with a short phrase of a few words, not a passage";
29993
29993
  var SEARCH_KIND_DEPTH = 100;
@@ -30045,7 +30045,7 @@ var dispatchToolSpecs = [
30045
30045
  arguments: (z2) => ({
30046
30046
  issue: z2.string().describe(ISSUE_REFERENCE),
30047
30047
  status: z2.enum(ISSUE_STATUSES).describe("New lifecycle status.").optional(),
30048
- reason: z2.string({ max: 2000 }).describe("Required with status done, and only with it: why the issue is closing, at most 2,000 characters. Posted on the issue as a message before it closes.").optional(),
30048
+ reason: z2.string({ max: DISPATCH_BODY_MAX }).describe(`Required with status done, and only with it: why the issue is closing, at most ${DISPATCH_BODY_MAX} characters. Posted on the issue as a message before it closes.`).optional(),
30049
30049
  title: z2.string({ min: 1, max: ISSUE_TITLE_MAX }).describe("Replacement title, at most 1,000 characters.").optional(),
30050
30050
  labels: z2.array(z2.string({ min: 1, max: 40 }), { max: 20 }).describe("Replacement label set, at most 20 labels of up to 40 characters; replaces every existing label.").optional(),
30051
30051
  priority: z2.number({ int: true, min: 0, max: 3 }).nullable().optional().describe("Coarse priority: 0 is P0 (highest) through 3 is P3 (lowest); null clears it."),
@@ -30185,7 +30185,7 @@ var dispatchToolSpecs = [
30185
30185
  {
30186
30186
  name: "dispatch_comment",
30187
30187
  example: { issue: "DSP-1", body: "Looks good." },
30188
- description: "Add review feedback to an issue or project document quote, or reply to a question asked with dispatch ask. " + "Do not use it for an exact replacement; use " + `dispatch suggest instead. A quote anchor is pinned to its block. Body is at most 2,000 characters, the lines images appends included. ${OWNER_REFERENCE}`,
30188
+ description: "Add review feedback to an issue or project document quote, or reply to a question asked with dispatch ask. " + "Do not use it for an exact replacement; use " + `dispatch suggest instead. A quote anchor is pinned to its block. Body is at most ${DISPATCH_BODY_MAX} characters, the lines images appends included. ${OWNER_REFERENCE}`,
30189
30189
  arguments: (z2) => ({
30190
30190
  issue: z2.string().describe(ISSUE_REFERENCE).optional(),
30191
30191
  project: z2.string().describe("Project key owning the document.").optional(),
@@ -30193,11 +30193,11 @@ var dispatchToolSpecs = [
30193
30193
  ref: z2.string().describe("Optional dispatch:// issue or document reference.").optional(),
30194
30194
  quote: z2.string().describe("Optional exact quoted document text.").optional(),
30195
30195
  occurrence: z2.number({ int: true, min: 0 }).describe("Optional zero-based occurrence of quote.").optional(),
30196
- body: z2.string({ max: DISPATCH_BODY_MAX }).describe("Review comment, at most 2,000 characters."),
30196
+ body: z2.string({ max: DISPATCH_BODY_MAX }).describe(`Review comment, at most ${DISPATCH_BODY_MAX} characters.`),
30197
30197
  reply_to: z2.string().describe("A comment id (uuid); replying to any comment in a thread continues that thread (an " + "ask's clarification thread included). To reply to an ask, use reply_to_ask with the " + "ask id instead.").optional(),
30198
30198
  reply_to_ask: z2.string().describe("Optional ask id to reply to, threading this comment under that question. Mutually " + "exclusive with reply_to.").optional(),
30199
30199
  turn: z2.enum(["agent", "human"]).describe("Only with reply_to_ask: who holds the turn after this reply. agent: a progress note - " + "you keep the turn and the ask stays 'Waiting on agents' for the human; human (default): " + "you need the human to act - the ask returns to 'Waiting on you'.").optional(),
30200
- images: imagesArgument(z2, "the issue (or the project document's project)", "the 2,000-character body limit")
30200
+ images: imagesArgument(z2, "the issue (or the project document's project)", `the ${DISPATCH_BODY_MAX}-character body limit`)
30201
30201
  }),
30202
30202
  validation: commentValidation
30203
30203
  },
@@ -30209,7 +30209,7 @@ var dispatchToolSpecs = [
30209
30209
  quote: "old wording",
30210
30210
  replace_with: "new wording"
30211
30211
  },
30212
- description: "Propose an exact replacement for quoted document text. Do not use it for general feedback; use " + `dispatch comment instead. Optional explanation is at most 2,000 characters. ${OWNER_REFERENCE}`,
30212
+ description: "Propose an exact replacement for quoted document text. Do not use it for general feedback; use " + `dispatch comment instead. Optional explanation is at most ${DISPATCH_BODY_MAX} characters. ${OWNER_REFERENCE}`,
30213
30213
  arguments: (z2) => ({
30214
30214
  issue: z2.string().describe(ISSUE_REFERENCE).optional(),
30215
30215
  project: z2.string().describe("Project key owning the document.").optional(),
@@ -30217,7 +30217,7 @@ var dispatchToolSpecs = [
30217
30217
  ref: z2.string().describe("Optional dispatch:// issue or document reference.").optional(),
30218
30218
  quote: z2.string().describe("Exact document text to replace."),
30219
30219
  replace_with: z2.string().describe("Replacement text; a CR LF or a lone carriage return in it is written as a line feed."),
30220
- body: z2.string({ max: 2000 }).describe("Optional rationale, at most 2,000 characters.").optional(),
30220
+ body: z2.string({ max: DISPATCH_BODY_MAX }).describe(`Optional rationale, at most ${DISPATCH_BODY_MAX} characters.`).optional(),
30221
30221
  occurrence: z2.number({ int: true, min: 0 }).describe("Optional zero-based occurrence of quote.").optional()
30222
30222
  }),
30223
30223
  validation: documentOwnerValidation(true, true)
@@ -30225,12 +30225,12 @@ var dispatchToolSpecs = [
30225
30225
  {
30226
30226
  name: "dispatch_message",
30227
30227
  example: { issue: "DSP-1", body: "Implementation started." },
30228
- description: "Post a note humans must read now: a reply to a human's message or a deliverable that landed. A to-do only a human can " + "complete is an ask (dispatch ask), so it reaches their inbox. Never progress or status updates - Dispatch is a high-signal " + "record, not a log. Not a design decision (write it as a decision block in the document) or document feedback (dispatch comment). " + "To answer a human's direct message to this session - one sent from the Agents page, which names no issue - pass that message's bare id as " + "in_reply_to and no issue; the reply lands in that conversation. Another call with the same in_reply_to and new text posts a follow-up, " + "threaded under this session's first reply; the same text again posts nothing. dispatch read --message <id> reads " + "that conversation back. Every other message names its issue. " + `Body is at most 2,000 characters, the lines images appends included. ${ISSUE_REFERENCE}`,
30228
+ description: "Post a note humans must read now: a reply to a human's message or a deliverable that landed. A to-do only a human can " + "complete is an ask (dispatch ask), so it reaches their inbox. Never progress or status updates - Dispatch is a high-signal " + "record, not a log. Not a design decision (write it as a decision block in the document) or document feedback (dispatch comment). " + "To answer a human's direct message to this session - one sent from the Agents page, which names no issue - pass that message's bare id as " + "in_reply_to and no issue; the reply lands in that conversation. Another call with the same in_reply_to and new text posts a follow-up, " + "threaded under this session's first reply; the same text again posts nothing. dispatch read --message <id> reads " + "that conversation back. Every other message names its issue. " + `Body is at most ${DISPATCH_BODY_MAX} characters, the lines images appends included. ${ISSUE_REFERENCE}`,
30229
30229
  arguments: (z2) => ({
30230
30230
  issue: z2.string().describe(`${ISSUE_REFERENCE} Omit it only when in_reply_to answers a human's direct message to this session.`).optional(),
30231
- body: z2.string({ max: DISPATCH_BODY_MAX }).describe("Update text, at most 2,000 characters."),
30231
+ body: z2.string({ max: DISPATCH_BODY_MAX }).describe(`Update text, at most ${DISPATCH_BODY_MAX} characters.`),
30232
30232
  in_reply_to: z2.string().describe("Optional message id or dispatch://KEY/message/<id> reference to reply to, threading " + "this message under it so the reply stays with the original in the Conversation. " + "A bare id with no issue answers a human's direct message to this session; a " + "dispatch://KEY/message/<id> names the issue its message lives on, so that form is a " + "reply on that issue.").optional(),
30233
- images: imagesArgument(z2, "the issue (a reply to a direct message: your own conversation, dispatch://agent/<your session id>)", "the 2,000-character body limit")
30233
+ images: imagesArgument(z2, "the issue (a reply to a direct message: your own conversation, dispatch://agent/<your session id>)", `the ${DISPATCH_BODY_MAX}-character body limit`)
30234
30234
  }),
30235
30235
  validation: messageValidation
30236
30236
  },
@@ -30322,11 +30322,11 @@ var dispatchToolSpecs = [
30322
30322
  {
30323
30323
  name: "dispatch_read",
30324
30324
  example: { issue: "DSP-1" },
30325
- description: "Read an issue or project-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, " + "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). " + "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). " + "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,
30325
+ 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). " + "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,
30326
30326
  arguments: (z2) => ({
30327
30327
  issue: z2.string().describe(ISSUE_REFERENCE).optional(),
30328
30328
  project: z2.string().describe("Project key owning the document.").optional(),
30329
- artifact: z2.string().describe("Project document artifact id, slug, or filename.").optional(),
30329
+ artifact: z2.string().describe("Document artifact id, slug, or filename: one of the issue's, or a project document.").optional(),
30330
30330
  ref: z2.string().describe("Optional dispatch:// issue or document reference.").optional(),
30331
30331
  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()
30332
30332
  }),
@@ -31335,6 +31335,8 @@ function parseDispatchRef(ref) {
31335
31335
  ...version3 === undefined ? {} : { version: Number(version3) }
31336
31336
  };
31337
31337
  }
31338
+ if (version3 !== undefined)
31339
+ return null;
31338
31340
  if (targetID === undefined || targetKind !== "ask" && targetKind !== "comment")
31339
31341
  return null;
31340
31342
  return {
@@ -4,7 +4,7 @@ Read this to catch up after a restart, read a node, trace what cites it, or writ
4
4
 
5
5
  ## What comes back
6
6
 
7
- - After a restart, catch up with `dispatch read --issue <KEY>` (or `--project` with `--artifact`, or `--ref`). It prints the issue's summary, open asks, references and recent events, or an ask's, comment's or message's thread. Reads do not subscribe. Use `dispatch doc-read` for document text.
7
+ - After a restart, catch up with `dispatch read --issue <KEY>` (or `--artifact` with `--issue` or `--project`, or `--ref`). It prints the issue's summary, open asks, references and recent events, a document's summary (its issue, reference, versions and approval), or an ask's, comment's or message's thread. Reads do not subscribe. Use `dispatch doc-read` for document text.
8
8
  - An anchored comment or ask prints `Position:`, where its quote's block stands. `unavailable (DOC_SERVICE_UNAVAILABLE)` means try again shortly. `unavailable (DOC_SCHEMA)` means the document needs repair.
9
9
  - The API gives the same facts as `anchor_block` (or `anchor_block_error`) on `GET /api/v1/comments/{id}` and `GET /api/v1/asks/{id}`. `GET /api/v1/artifacts/{id}/blocks/{block_id}` gives them for any block.
10
10
  - Each read ends with `Referenced by:` (what cites the node) and `Links:` (what it cites), across projects. "What led to this decision" is one read on the ask.
@@ -28,3 +28,4 @@ dispatch://PROJECT/artifact/<document-ref>/comment/<id>
28
28
  - A reference renders as a link titled by its target. A body that is only a reference gets an unfurl card.
29
29
  - A bare UUID or `KEY#seq` is not a reference. `dispatch read` also takes a dashboard URL, also on the public dashboard when `DISPATCH_PUBLIC_URL` names one. An issue read prints its `Link:`: hand that to a person.
30
30
  - An ask or comment id can be a unique 8+ hex prefix. A message id is always the full UUID.
31
+ - An ask or comment on an issue's document is `dispatch://KEY/ask/<id>` or `dispatch://KEY/comment/<id>`, never written through its document.
@@ -18,7 +18,7 @@ dispatch search --query 'reviewer threads OR review comments'
18
18
  ```
19
19
 
20
20
  - No hits: drop a word before you add one. Try two or three wordings (the component's name, the symptom, the fix) before you conclude that nothing exists.
21
- - Open each hit that could be yours with `dispatch read`, then its parent (the `child_of` row under `Links:`). The parent's children and the `Components:` line show where the rest of the work is.
21
+ - Open each hit that could be yours with `dispatch read`, then its parent (the `child_of` row under `Links:`; a document hit names its issue on `Issue:`). The parent's children and the `Components:` line show where the rest of the work is.
22
22
 
23
23
  ## What to do with what you find
24
24
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/pi-envoy",
3
- "version": "10.16.0",
3
+ "version": "10.17.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "omp": {