@sjawhar/opencode-legion-envoy 1.40.1 → 1.41.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.
Files changed (2) hide show
  1. package/dist/src/server.js +160 -14
  2. package/package.json +1 -1
@@ -13629,6 +13629,24 @@ var AskEditedEventPayloadSchema = object({
13629
13629
  }),
13630
13630
  edited_by: object({ kind: string2(), id: string2() }).passthrough()
13631
13631
  });
13632
+ var CommentMentionSchema = object({
13633
+ target: string2(),
13634
+ delivery: _enum2(DELIVERY_CAPABILITIES),
13635
+ session_id: string2().nullable()
13636
+ });
13637
+ var CommentDeliverySchema = object({
13638
+ comment_id: string2(),
13639
+ target: string2(),
13640
+ attempt: number2().int().positive(),
13641
+ delivery: _enum2(DELIVERY_CAPABILITIES),
13642
+ session_id: string2().nullable(),
13643
+ envelope_id: string2().nullable(),
13644
+ state: _enum2(["pending", "sent", "failed"]),
13645
+ error: string2().nullable(),
13646
+ resolve_error: string2().nullable(),
13647
+ reply_id: string2().nullable(),
13648
+ created_at: string2()
13649
+ });
13632
13650
  var CommentEventPayloadSchema = object({
13633
13651
  id: string2().optional(),
13634
13652
  artifact_name: string2().optional(),
@@ -13644,7 +13662,9 @@ var CommentEventPayloadSchema = object({
13644
13662
  anchor: object({ block_id: string2().nullable().optional(), quote: string2().optional() }).nullish(),
13645
13663
  suggestion: object({ replace_with: string2().optional() }).nullish(),
13646
13664
  author: object({ kind: string2(), id: string2() }).optional(),
13647
- created_at: string2().optional()
13665
+ created_at: string2().optional(),
13666
+ mentions: array(CommentMentionSchema).optional(),
13667
+ deliveries: array(CommentDeliverySchema).optional()
13648
13668
  });
13649
13669
  var MessageEventPayloadSchema = object({
13650
13670
  id: string2().optional(),
@@ -13654,8 +13674,9 @@ var MessageEventPayloadSchema = object({
13654
13674
  reply_body: string2().optional(),
13655
13675
  author: object({ kind: string2(), id: string2() }).optional()
13656
13676
  });
13677
+ var DispatchTargetedResourceIDSchema = uuid2();
13657
13678
  var DispatchTargetedMessagePayloadSchema = MessageEventPayloadSchema.extend({
13658
- id: string2(),
13679
+ id: DispatchTargetedResourceIDSchema,
13659
13680
  issue_key: string2().nullable(),
13660
13681
  author: object({ kind: string2(), id: string2() }),
13661
13682
  body: string2(),
@@ -13664,6 +13685,31 @@ var DispatchTargetedMessagePayloadSchema = MessageEventPayloadSchema.extend({
13664
13685
  deliveries: array(unknown()),
13665
13686
  created_at: string2()
13666
13687
  });
13688
+ var DispatchTargetedCommentPayloadSchema = CommentEventPayloadSchema.extend({
13689
+ id: DispatchTargetedResourceIDSchema,
13690
+ issue_key: string2().nullable(),
13691
+ artifact_id: string2().nullable(),
13692
+ author: object({ kind: string2(), id: string2() }),
13693
+ body: string2(),
13694
+ reply_to: string2().nullable(),
13695
+ ask_id: string2().nullable(),
13696
+ mentions: array(CommentMentionSchema),
13697
+ deliveries: array(CommentDeliverySchema),
13698
+ created_at: string2(),
13699
+ artifact_name: string2()
13700
+ });
13701
+ var DispatchTargetedMessageDeliverySchema = object({
13702
+ attempt: number2().int().positive(),
13703
+ mode: _enum2(DELIVERY_CAPABILITIES)
13704
+ });
13705
+ var DispatchTargetedCommentDeliverySchema = DispatchTargetedMessageDeliverySchema.extend({
13706
+ comment_id: DispatchTargetedResourceIDSchema,
13707
+ target: string2()
13708
+ });
13709
+ var DispatchTargetedDeliverySchema = union([
13710
+ DispatchTargetedCommentDeliverySchema,
13711
+ DispatchTargetedMessageDeliverySchema
13712
+ ]);
13667
13713
  var MessageDeliveryEventPayloadSchema = object({
13668
13714
  message_id: string2().optional(),
13669
13715
  attempt: number2().int().positive().optional(),
@@ -13808,6 +13854,7 @@ var DOC_EDIT_OPS = ["replace", "delete", "insert", "retype", "move"];
13808
13854
  var dispatchToolSpecs = [
13809
13855
  {
13810
13856
  name: "dispatch_issue",
13857
+ example: { project: "DSP", title: "Native workspace" },
13811
13858
  description: "Create a native Dispatch issue for newly tracked work. Search first with dispatch_search; if potentially duplicate issues exist, this returns 409 POSSIBLE_DUPLICATE unless force is true after reading them. " + `Do not use it when an existing issue already covers the work; read or update that issue instead. ${ISSUE_REFERENCE}`,
13812
13859
  arguments: (z) => ({
13813
13860
  project: z.string().describe("Project key for the new issue."),
@@ -13824,6 +13871,7 @@ var dispatchToolSpecs = [
13824
13871
  },
13825
13872
  {
13826
13873
  name: "dispatch_issue_update",
13874
+ example: { issue: "DSP-1", status: "in_progress" },
13827
13875
  description: "Update an existing issue: move its lifecycle status, retitle it, replace its labels, link a URL " + "(the pull request that delivers it, a run, a document), set its route, set or clear its parent, " + "or attach it to architecture components. Status is one of " + `${ISSUE_STATUSES.join(", ")}; outside Legion, move it yourself as the work advances; inside ` + "Legion the daemon moves it. external_links are " + "merged into the issue's existing links by URL, so linking the pull request you just opened " + "keeps every earlier link. components replaces the issue's own attachment and is allowed on a " + "closed issue. Priority is the human's and is not settable here. At least one " + `field besides issue is required. ${ISSUE_REFERENCE}`,
13828
13876
  arguments: (z) => ({
13829
13877
  issue: z.string().describe(ISSUE_REFERENCE),
@@ -13846,6 +13894,7 @@ var dispatchToolSpecs = [
13846
13894
  },
13847
13895
  {
13848
13896
  name: "dispatch_ask",
13897
+ example: { issue: "DSP-1", question: "Ship this?" },
13849
13898
  description: "Open a durable, answerable decision on an issue or project document. Do not use it for a status update or discussion; " + "use dispatch_message instead. A to-do a human must complete is a question phrased as that to-do, with the options you want (for example Done / Can't). " + "Anchor a document question, thread reply_to/reply_to_ask, or cite a dispatch:// " + `reference \u2014 it must be answerable from its own text and anchor alone, never "see above". A quote anchor is pinned to its block. Question is at most ${ASK_QUESTION_MAX} ` + `characters and has at most 8 options. ${OWNER_REFERENCE}`,
13850
13899
  arguments: (z) => ({
13851
13900
  issue: z.string().describe(ISSUE_REFERENCE).optional(),
@@ -13869,6 +13918,10 @@ var dispatchToolSpecs = [
13869
13918
  },
13870
13919
  {
13871
13920
  name: "dispatch_edit_ask",
13921
+ example: {
13922
+ ask: "01234567-0000-4000-8000-000000000001",
13923
+ question: "Ship the revised plan?"
13924
+ },
13872
13925
  description: "Edit an open question in place. Use it to correct or refine the same decision; retract the " + "old ask and open a new one when the decision itself changes. Previous text remains in the " + "event log. Only the asking session can edit it; answered or resolved asks cannot be edited.",
13873
13926
  arguments: (z) => ({
13874
13927
  ask: z.string().describe("Ask id to edit."),
@@ -13890,6 +13943,11 @@ var dispatchToolSpecs = [
13890
13943
  },
13891
13944
  {
13892
13945
  name: "dispatch_resolve_ask",
13946
+ example: {
13947
+ ask: "01234567-0000-4000-8000-000000000001",
13948
+ kind: "retracted",
13949
+ reason: "A newer question supersedes this one."
13950
+ },
13893
13951
  description: "Retract an open question that is moot or resolve one after finding the answer. This closes the question without answering it.",
13894
13952
  arguments: (z) => ({
13895
13953
  ask: z.string().describe("Ask id to close."),
@@ -13899,6 +13957,7 @@ var dispatchToolSpecs = [
13899
13957
  },
13900
13958
  {
13901
13959
  name: "dispatch_resolve_comment",
13960
+ example: { comment: "01234567-0000-4000-8000-000000000001" },
13902
13961
  description: "Resolve a review comment thread once it has been addressed - typically your own comment " + "after the document was fixed. Any session or human may resolve any open comment on an " + "open issue or project document; reopening a resolved comment is human-only (the dashboard). " + "Not for asks: use dispatch_resolve_ask.",
13903
13962
  arguments: (z) => ({
13904
13963
  comment: z.string().describe("Comment id (uuid), or a dispatch://KEY/comment/<id> or " + "dispatch://PROJECT/artifact/<slug>/comment/<id> reference; a reference accepts an " + "8+ character id prefix that is unique on its owner.")
@@ -13907,6 +13966,7 @@ var dispatchToolSpecs = [
13907
13966
  },
13908
13967
  {
13909
13968
  name: "dispatch_follow",
13969
+ example: { ask: "01234567-0000-4000-8000-000000000001", action: "follow" },
13910
13970
  description: "Follow or unfollow an ask. Every session that opens or replies to an ask follows it: its answer, " + "edits, resolution, and replies reach that session directly. Unfollow to stop; follow to rejoin or " + "to hear an ask you never wrote to. Whole-issue subscription is separate: envoy_subscribe " + "notifications.dispatch.issue.<KEY>.>",
13911
13971
  arguments: (z) => ({
13912
13972
  ask: z.string().describe("Full ask id (uuid)."),
@@ -13916,6 +13976,7 @@ var dispatchToolSpecs = [
13916
13976
  },
13917
13977
  {
13918
13978
  name: "dispatch_comment",
13979
+ example: { issue: "DSP-1", body: "Looks good." },
13919
13980
  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. ${OWNER_REFERENCE}`,
13920
13981
  arguments: (z) => ({
13921
13982
  issue: z.string().describe(ISSUE_REFERENCE).optional(),
@@ -13933,6 +13994,12 @@ var dispatchToolSpecs = [
13933
13994
  },
13934
13995
  {
13935
13996
  name: "dispatch_suggest",
13997
+ example: {
13998
+ issue: "DSP-1",
13999
+ artifact: "spec",
14000
+ quote: "old wording",
14001
+ replace_with: "new wording"
14002
+ },
13936
14003
  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}`,
13937
14004
  arguments: (z) => ({
13938
14005
  issue: z.string().describe(ISSUE_REFERENCE).optional(),
@@ -13948,6 +14015,7 @@ var dispatchToolSpecs = [
13948
14015
  },
13949
14016
  {
13950
14017
  name: "dispatch_message",
14018
+ example: { issue: "DSP-1", body: "Implementation started." },
13951
14019
  description: "Post a note humans must read now: a reply to a human's message, a deliverable that landed, or a blocker only " + "they can clear. Never progress or status updates - Dispatch is a high-signal record, not a log. Not a decision " + `(dispatch_ask) or document feedback (dispatch_comment). Body is at most 2,000 characters. ${ISSUE_REFERENCE}`,
13952
14020
  arguments: (z) => ({
13953
14021
  issue: z.string().describe(ISSUE_REFERENCE),
@@ -13957,6 +14025,11 @@ var dispatchToolSpecs = [
13957
14025
  },
13958
14026
  {
13959
14027
  name: "dispatch_doc_edit",
14028
+ example: {
14029
+ issue: "DSP-1",
14030
+ artifact: "spec",
14031
+ ops: [{ op: "replace", find: "old", with: "new" }]
14032
+ },
13960
14033
  description: "Apply deterministic document edits: replace or delete quoted text, insert markdown at an anchor, retype an identified paragraph or typed block into a schema-declared typed block, and delete or move a whole block by its id. " + "Do not use it for review feedback or for reading; use dispatch_comment, dispatch_suggest, or dispatch_doc_read instead. " + "For replace, delete, and quote anchors, find text as rendered: inline Markdown (**bold**, `code`) is tolerated; a leading '# ' matches a heading. replace is inline: with is the new text of the matched span, so a leading list or heading marker stays literal text. " + "A delete whose find is a block's entire text removes the block (a list emptied of its items goes too); delete with block removes any block by id, and move with block relocates one. " + 'Insert and move anchors also accept "start", "end", "heading:<exact heading text>", and "block:<id>"; block ids are the #id of a typed block or a row of GET /api/v1/artifacts/{id}/blocks. ' + `The spec (or any document) holds requirements, design, and decisions - never progress, status, or timestamps. ${OWNER_REFERENCE} ${SPEC_WRITING_GUIDANCE}`,
13961
14034
  arguments: (z) => ({
13962
14035
  issue: z.string().describe(ISSUE_REFERENCE).optional(),
@@ -13981,6 +14054,7 @@ var dispatchToolSpecs = [
13981
14054
  },
13982
14055
  {
13983
14056
  name: "dispatch_doc_read",
14057
+ example: { issue: "DSP-1" },
13984
14058
  description: "Read a live document or a named document version. Do not use it for issue status, asks, or events; " + "use dispatch_read instead. Supply ref, issue, or project plus artifact; issue plus an omitted artifact reads the primary document. " + OWNER_REFERENCE,
13985
14059
  arguments: (z) => ({
13986
14060
  issue: z.string().describe(ISSUE_REFERENCE).optional(),
@@ -13993,6 +14067,7 @@ var dispatchToolSpecs = [
13993
14067
  },
13994
14068
  {
13995
14069
  name: "dispatch_request_approval",
14070
+ example: { issue: "DSP-1" },
13996
14071
  description: "Ask a human to approve a document at its current version - the exception path for a spec " + "that departs from what was settled or proposes children, not a step for every issue. Opens an " + "approval ask (Approve / Request changes) in the human's Inbox; the answer pins a review to the " + "document version and arrives as artifact.approved or artifact.changes_requested. A later edit " + "makes an approval stale; request again for the new version. Idempotent while a request is open. " + OWNER_REFERENCE,
13997
14072
  arguments: (z) => ({
13998
14073
  issue: z.string().describe(ISSUE_REFERENCE).optional(),
@@ -14003,6 +14078,8 @@ var dispatchToolSpecs = [
14003
14078
  },
14004
14079
  {
14005
14080
  name: "dispatch_artifact",
14081
+ example: { issue: "DSP-1", name: "design.md", content: `# Design
14082
+ ` },
14006
14083
  description: "Attach a local file or inline text as an issue artifact or project document. Do not use it to edit a live document; use " + `dispatch_doc_edit instead. Exactly one of path or content is required; artifacts are limited to 25 MiB. ${OWNER_REFERENCE}`,
14007
14084
  arguments: (z) => ({
14008
14085
  issue: z.string().describe(ISSUE_REFERENCE).optional(),
@@ -14022,6 +14099,7 @@ var dispatchToolSpecs = [
14022
14099
  },
14023
14100
  {
14024
14101
  name: "dispatch_read",
14102
+ example: { issue: "DSP-1" },
14025
14103
  description: "Read an issue or project-document summary, targeted ask, or targeted comment reply chain. Do not use it for document " + "contents; use dispatch_doc_read instead. Supply ref, issue, or project plus artifact. " + "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. " + OWNER_REFERENCE,
14026
14104
  arguments: (z) => ({
14027
14105
  issue: z.string().describe(ISSUE_REFERENCE).optional(),
@@ -14033,6 +14111,7 @@ var dispatchToolSpecs = [
14033
14111
  },
14034
14112
  {
14035
14113
  name: "dispatch_search",
14114
+ example: { query: "astrolabe" },
14036
14115
  description: "Search every issue, document, comment, ask, and message for a keyword or phrase and get deep links. " + "Use it before creating an issue or a design document, and to find where a word was written. " + 'Websearch syntax: "quoted phrase", -excluded, OR.',
14037
14116
  arguments: (z) => ({
14038
14117
  query: z.string({ min: 2 }).describe("Keyword, phrase, or websearch expression; at least 2 characters."),
@@ -14042,6 +14121,7 @@ var dispatchToolSpecs = [
14042
14121
  },
14043
14122
  {
14044
14123
  name: "dispatch_issues",
14124
+ example: { project: "AGENTC" },
14045
14125
  description: "List a project's issues for a roadmap or backlog pass: every issue in one project, each carrying " + "its status, priority, parent, labels, and open-ask count, so you can see backlog shape without " + "opening every issue. Optionally filter by status, parent, label, or how recently it changed. Do " + "not use it to search by keyword or phrase; dispatch_search remains the keyword surface. Rows are " + "capped at limit (default 50, max 250), applied to the response here, not by the server.",
14046
14126
  arguments: (z) => ({
14047
14127
  project: z.string().describe("Project key to list issues from."),
@@ -14054,6 +14134,7 @@ var dispatchToolSpecs = [
14054
14134
  },
14055
14135
  {
14056
14136
  name: "dispatch_architecture_sync",
14137
+ example: { project: "CORE" },
14057
14138
  description: "Import a project's architecture model from its configured source repository now, instead of " + "waiting for the server's five-minute schedule. Returns the imported commit and component " + "count, or the recorded error when the model was rejected (the previous model stays up). " + "The source itself is configured by a human in Settings; 404 SOURCE_NOT_FOUND without one.",
14058
14139
  arguments: (z) => ({
14059
14140
  project: z.string().describe("Project key whose architecture source to sync, such as CORE.")
@@ -14062,6 +14143,7 @@ var dispatchToolSpecs = [
14062
14143
  },
14063
14144
  {
14064
14145
  name: "dispatch_open_asks",
14146
+ example: {},
14065
14147
  description: "List active unanswered asks, oldest first, with age and whose reply is needed. Omit project to " + "see only this session's own authored asks (call before saying you are waiting for human input); " + "supply project to see every open ask across that project's issues and documents, whoever authored " + "them.",
14066
14148
  arguments: (z) => ({
14067
14149
  project: z.string().describe("Project key; when supplied, lists every open ask in the project instead of only this session's own.").optional()
@@ -14070,6 +14152,7 @@ var dispatchToolSpecs = [
14070
14152
  },
14071
14153
  {
14072
14154
  name: "dispatch_whoami",
14155
+ example: {},
14073
14156
  description: "Who Dispatch takes this session for: {session, owner}. owner is the lowercase GitHub login of the human whose personal token you run under (the default assignee of issues you create), or null under the shared token.",
14074
14157
  arguments: () => ({}),
14075
14158
  strict: true
@@ -15172,9 +15255,11 @@ class ToolInputError extends Error {
15172
15255
  problems;
15173
15256
  constructor(tool, problems) {
15174
15257
  const count = problems.length;
15258
+ const help = dispatchInputHelp(tool);
15175
15259
  super([
15176
15260
  `${tool} was not called: ${count} problem${count === 1 ? "" : "s"}`,
15177
- ...problems.map((problem) => `- ${problem}`)
15261
+ ...problems.map((problem) => `- ${problem}`),
15262
+ ...help === undefined ? [] : help.map((line) => `- ${line}`)
15178
15263
  ].join(`
15179
15264
  `));
15180
15265
  this.name = "ToolInputError";
@@ -15182,6 +15267,14 @@ class ToolInputError extends Error {
15182
15267
  this.problems = problems;
15183
15268
  }
15184
15269
  }
15270
+ function dispatchInputHelp(tool) {
15271
+ const spec = dispatchToolSpecs.find((candidate) => candidate.name === tool);
15272
+ if (spec === undefined)
15273
+ return;
15274
+ const schema = dispatchToolSchema(spec, zodSchemaApi(exports_external), { strict: true });
15275
+ const allowed = Object.keys(shapeOf(schema) ?? {}).join(", ") || "none";
15276
+ return [`Allowed keys: ${allowed}`, `Example: ${tool}(${JSON.stringify(spec.example)})`];
15277
+ }
15185
15278
  function unwrap(schema) {
15186
15279
  let current = schema;
15187
15280
  while (current !== undefined) {
@@ -15486,7 +15579,21 @@ function refTarget(ref, kind, id) {
15486
15579
  const ownerRef = ref.owner.kind === "issue" ? dispatchIssueRef(ref.owner.issue) : dispatchDocumentRef(ref.owner.project, `${ref.artifact}`);
15487
15580
  return dispatchChildRef(ownerRef, kind, id);
15488
15581
  }
15489
- var uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
15582
+ var canonicalUUIDPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
15583
+ var compactUUIDPattern = /^[0-9a-f]{32}$/i;
15584
+ function normalizeUUID(value) {
15585
+ let compact = value;
15586
+ if (value.slice(0, 9).toLowerCase() === "urn:uuid:") {
15587
+ compact = value.slice(9);
15588
+ } else if (value.length === 38 && value.startsWith("{") && value.endsWith("}")) {
15589
+ compact = value.slice(1, -1);
15590
+ }
15591
+ if (canonicalUUIDPattern.test(compact))
15592
+ compact = compact.replaceAll("-", "");
15593
+ if (!compactUUIDPattern.test(compact))
15594
+ return;
15595
+ return `${compact.slice(0, 8)}-${compact.slice(8, 12)}-${compact.slice(12, 16)}-${compact.slice(16, 20)}-${compact.slice(20)}`.toLowerCase();
15596
+ }
15490
15597
  var askIdProblem = "ask must be a bare ask id or a dispatch://.../ask/<id> reference";
15491
15598
  var commentIdProblem = "comment must be a bare comment id or a dispatch://.../comment/<id> reference";
15492
15599
  var messageIdProblem = "in_reply_to must be a full message id (uuid) or a dispatch://KEY/message/<id> reference";
@@ -15499,12 +15606,13 @@ function messageIdOf(value) {
15499
15606
  const reference = parseDispatchRef(value);
15500
15607
  return reference?.kind === "message" ? reference.id : undefined;
15501
15608
  })() : value;
15502
- return id !== undefined && uuidPattern.test(id) ? id : undefined;
15609
+ return id === undefined ? undefined : normalizeUUID(id);
15503
15610
  }
15504
15611
  var idPrefixPattern = /^[0-9a-f][0-9a-f-]{7,}$/i;
15505
15612
  async function resolveIdPrefix(tool, kind, ref, list) {
15506
- if (uuidPattern.test(ref.id))
15507
- return ref.id;
15613
+ const fullID = normalizeUUID(ref.id);
15614
+ if (fullID !== undefined)
15615
+ return fullID;
15508
15616
  const ownerName = ref.owner.kind === "issue" ? ref.owner.issue : `${ref.owner.project}/${ref.artifact}`;
15509
15617
  if (!idPrefixPattern.test(ref.id)) {
15510
15618
  throw new ToolInputError(tool, [
@@ -15736,21 +15844,53 @@ async function resolveArtifact(client, owner, artifactReference) {
15736
15844
  return { owner, artifact };
15737
15845
  const names = artifacts.filter((candidate) => candidate.name === artifactReference);
15738
15846
  if (names.length > 1) {
15739
- throw new Error(`artifact name ${artifactReference} is ambiguous in project ${owner.project}; ` + `${names.length} documents share it \u2014 use its slug instead`);
15847
+ throw new Error(documentReferenceProblem(artifactReference, names, "project", true));
15848
+ }
15849
+ if (names[0] === undefined) {
15850
+ throw new Error(documentReferenceProblem(artifactReference, artifacts, "project"));
15740
15851
  }
15741
- if (names[0] === undefined)
15742
- throw error;
15743
15852
  return { owner, artifact: names[0] };
15744
15853
  }
15745
15854
  }
15746
15855
  const issue = await client.getIssue(owner.issue);
15747
- const artifact = artifactReference === undefined || artifactReference === "spec" ? issue.artifacts.find((candidate) => candidate.primary || candidate.id === issue.primary_artifact_id) : issue.artifacts.find((candidate) => candidate.id === artifactReference || candidate.slug === artifactReference || candidate.name === artifactReference);
15856
+ let artifact;
15857
+ if (artifactReference === undefined || artifactReference === "spec") {
15858
+ artifact = issue.artifacts.find((candidate) => candidate.primary || candidate.id === issue.primary_artifact_id);
15859
+ } else {
15860
+ artifact = issue.artifacts.find((candidate) => candidate.id === artifactReference || candidate.slug === artifactReference);
15861
+ if (artifact === undefined) {
15862
+ const names = issue.artifacts.filter((candidate) => candidate.name === artifactReference);
15863
+ if (names.length > 1) {
15864
+ throw new Error(documentReferenceProblem(artifactReference, names, "issue", true));
15865
+ }
15866
+ artifact = names[0];
15867
+ }
15868
+ }
15748
15869
  if (!artifact) {
15749
- const slugs = issue.artifacts.map((candidate) => `${candidate.slug}${candidate.primary || candidate.id === issue.primary_artifact_id ? " (primary)" : ""}`);
15750
- throw new Error(`artifact "${artifactReference ?? "spec"}" was not found on issue ${issue.key}; artifacts: ${slugs.length === 0 ? "none" : slugs.join(", ")}`);
15870
+ throw new Error(documentReferenceProblem(artifactReference ?? "spec", issue.artifacts, "issue"));
15751
15871
  }
15752
15872
  return { owner, issue, artifact };
15753
15873
  }
15874
+ var documentHintLimit = 8;
15875
+ function documentReferenceProblem(reference, documents, owner, ambiguous = false) {
15876
+ const hints = documents.slice(0, documentHintLimit).map((document) => `${document.slug} (${document.name})`);
15877
+ const list = hints.length === 0 ? "none" : hints.join(", ");
15878
+ return ambiguous ? `"${reference}" names ${documents.length} documents on this ${owner}; use a slug: ${list}` : `document "${reference}" not found by slug; this ${owner}'s documents: ${list}`;
15879
+ }
15880
+ var askHintLimit = 8;
15881
+ function askIDInputProblem(asks, scope) {
15882
+ const hints = asks.slice(0, askHintLimit).map((ask) => `${ask.id.slice(0, 8)}\u2026 ${textHead(ask.question)}`);
15883
+ return `ask IDs are UUIDs; use the full ask ID; ${scope} open asks: ${hints.length === 0 ? "none" : hints.join(", ")}`;
15884
+ }
15885
+ async function invalidReplyToAskProblem(client, owner, resolved) {
15886
+ if (owner.kind === "issue") {
15887
+ const issue = resolved?.issue ?? await client.getIssue(owner.issue);
15888
+ return askIDInputProblem(issue.open_asks, "this issue's");
15889
+ }
15890
+ if (resolved === undefined)
15891
+ throw new Error("project document is missing its resolved artifact");
15892
+ return askIDInputProblem(await client.getArtifactAsks(resolved.artifact.id, "open"), "this document's");
15893
+ }
15754
15894
  function anchor(artifact, args) {
15755
15895
  const quote = optionalString(args, "quote");
15756
15896
  if (quote === undefined)
@@ -16354,7 +16494,13 @@ ${followsAsk(askOwner)}`,
16354
16494
  const resolved = owner.kind === "project" || artifactReference === undefined ? owner.kind === "project" ? await resolveArtifact(client, owner, artifactReference) : undefined : await resolveArtifact(client, owner, artifactReference);
16355
16495
  const anchored = resolved ? anchor(resolved.artifact, args) : undefined;
16356
16496
  const replyTo = optionalString(args, "reply_to");
16357
- const replyToAsk = optionalString(args, "reply_to_ask");
16497
+ const replyToAskReference = optionalString(args, "reply_to_ask");
16498
+ const replyToAsk = replyToAskReference === undefined ? undefined : normalizeUUID(replyToAskReference);
16499
+ if (replyToAskReference !== undefined && replyToAsk === undefined) {
16500
+ throw new ToolInputError(input.tool, [
16501
+ await invalidReplyToAskProblem(client, owner, resolved)
16502
+ ]);
16503
+ }
16358
16504
  const requestedTurn = optionalString(args, "turn");
16359
16505
  const commentInput = {
16360
16506
  body: stringArg(args, "body"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/opencode-legion-envoy",
3
- "version": "1.40.1",
3
+ "version": "1.41.0",
4
4
  "type": "module",
5
5
  "main": "dist/src/server.js",
6
6
  "exports": {