@sjawhar/pi-envoy 10.16.0 → 10.16.1

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
@@ -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
@@ -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.16.1",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "omp": {