@sjawhar/pi-legion-envoy 5.16.8 → 5.16.10

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/envoy.js CHANGED
@@ -30028,6 +30028,16 @@ var messageValidation = {
30028
30028
  },
30029
30029
  message: "issue is required unless in_reply_to names a message delivered to this session, which is the one message with no issue."
30030
30030
  };
30031
+ var readOwner = documentOwnerValidation(true);
30032
+ var readValidation = {
30033
+ check: (value) => {
30034
+ const input = value;
30035
+ if (typeof input.message !== "string")
30036
+ return readOwner.check(value);
30037
+ return input.issue === undefined && input.project === undefined && input.artifact === undefined && input.ref === undefined;
30038
+ },
30039
+ message: `${readOwner.message} message stands alone: it names the conversation, so name no issue, project, artifact, or ref with it.`
30040
+ };
30031
30041
  var ISSUE_COMPONENTS_MODES = ["inherit", "explicit", "none"];
30032
30042
  function componentsArgument(z2) {
30033
30043
  return z2.object({
@@ -30255,7 +30265,7 @@ var dispatchToolSpecs = [
30255
30265
  {
30256
30266
  name: "dispatch_message",
30257
30267
  example: { issue: "DSP-1", body: "Implementation started." },
30258
- description: "Post a note humans must read now: a reply to a human's message or a deliverable that landed. A blocker only a human can " + "clear is an ask (dispatch_ask), so it lands in their inbox. Never progress or status updates - Dispatch is a high-signal " + "record, not a log. Not a decision (dispatch_ask) 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, and a second call with the same in_reply_to posts nothing because " + "Dispatch keeps the one reply per message. Every other message names its issue. " + `Body is at most 2,000 characters. ${ISSUE_REFERENCE}`,
30268
+ description: "Post a note humans must read now: a reply to a human's message or a deliverable that landed. A blocker only a human can " + "clear is an ask (dispatch_ask), so it lands in their inbox. Never progress or status updates - Dispatch is a high-signal " + "record, not a log. Not a decision (dispatch_ask) 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}) reads " + "that conversation back. Every other message names its issue. " + `Body is at most 2,000 characters. ${ISSUE_REFERENCE}`,
30259
30269
  arguments: (z2) => ({
30260
30270
  issue: z2.string().describe(`${ISSUE_REFERENCE} Omit it only when in_reply_to answers a human's direct message to this session.`).optional(),
30261
30271
  body: z2.string({ max: 2000 }).describe("Update text, at most 2,000 characters."),
@@ -30349,14 +30359,15 @@ var dispatchToolSpecs = [
30349
30359
  {
30350
30360
  name: "dispatch_read",
30351
30361
  example: { issue: "DSP-1" },
30352
- 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,
30362
+ 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). " + "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,
30353
30363
  arguments: (z2) => ({
30354
30364
  issue: z2.string().describe(ISSUE_REFERENCE).optional(),
30355
30365
  project: z2.string().describe("Project key owning the document.").optional(),
30356
30366
  artifact: z2.string().describe("Project document artifact id, slug, or filename.").optional(),
30357
- ref: z2.string().describe("Optional dispatch:// issue or document reference.").optional()
30367
+ ref: z2.string().describe("Optional dispatch:// issue or document reference.").optional(),
30368
+ 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()
30358
30369
  }),
30359
- validation: documentOwnerValidation(true)
30370
+ validation: readValidation
30360
30371
  },
30361
30372
  {
30362
30373
  name: "dispatch_search",
@@ -32443,8 +32454,8 @@ class DispatchClient {
32443
32454
  async message(issue2, input) {
32444
32455
  return this.#json("POST", ["api", "v1", "issues", await this.#resolveIssue(issue2), "messages"], input);
32445
32456
  }
32446
- async messageReply(id, input) {
32447
- return this.#json("POST", ["api", "v1", "messages", id, "reply"], input);
32457
+ async messageReply(id, input, options = {}) {
32458
+ return this.#json("POST", ["api", "v1", "messages", id, "reply"], input, options.followUp === true ? { follow_up: "true" } : undefined);
32448
32459
  }
32449
32460
  async getMessage(issue2, id) {
32450
32461
  return this.#json("GET", [
@@ -32456,6 +32467,9 @@ class DispatchClient {
32456
32467
  id
32457
32468
  ]);
32458
32469
  }
32470
+ async getMessageThread(id, session) {
32471
+ return this.#json("GET", ["api", "v1", "messages", id], undefined, { session });
32472
+ }
32459
32473
  async artifact(issue2, input) {
32460
32474
  const artifactPath = ["api", "v1", "issues", await this.#resolveIssue(issue2), "artifacts"];
32461
32475
  if ("content" in input)
@@ -33107,6 +33121,13 @@ function argumentProblems(tool, args) {
33107
33121
  }
33108
33122
  break;
33109
33123
  }
33124
+ case "dispatch_read": {
33125
+ const message = optionalString(args, "message");
33126
+ if (message !== undefined && messageIdOf(message) === undefined) {
33127
+ problems.push("message must be a full message id (uuid) or a dispatch://KEY/message/<id> reference");
33128
+ }
33129
+ break;
33130
+ }
33110
33131
  }
33111
33132
  return problems;
33112
33133
  }
@@ -33244,6 +33265,9 @@ async function resolveOwnerArguments(tool, input, cwd, env, exec, serverUrl, pro
33244
33265
  if (tool === "dispatch_message" && typeof replyTarget === "string" && !replyTarget.startsWith("dispatch://")) {
33245
33266
  return { args, ref, owner: null };
33246
33267
  }
33268
+ if (tool === "dispatch_read" && typeof args.message === "string") {
33269
+ return { args, ref, owner: null };
33270
+ }
33247
33271
  const legionIssue = env.LEGION_ISSUE;
33248
33272
  if (!legionIssue) {
33249
33273
  problems.push(ownerRequiredProblem);
@@ -34166,16 +34190,30 @@ ${followsAsk(askOwner)}`,
34166
34190
  const inReplyTo = messageInReplyTo(args);
34167
34191
  const body = stringArg(args, "body");
34168
34192
  if (owner === null && inReplyTo !== undefined) {
34169
- const reply = await client.messageReply(inReplyTo, { body, attempt: 1, actor });
34193
+ const reply = await client.messageReply(inReplyTo, { body, attempt: 1, actor }, { followUp: true });
34194
+ if (reply.duplicate === true && reply.body === body) {
34195
+ return {
34196
+ text: `Dispatch already has this exact text in the conversation (message ${reply.id}); ` + "nothing new was posted. Send different text if you have more to say.",
34197
+ details: { message: reply.id, in_reply_to: inReplyTo, posted: false, duplicate: true }
34198
+ };
34199
+ }
34170
34200
  if (reply.body !== body) {
34171
34201
  return {
34172
34202
  text: `Message ${inReplyTo} was already answered by message ${reply.id}; Dispatch kept ` + "that reply and posted nothing. Wait for their next message rather than answering " + "this one again.",
34173
34203
  details: { message: reply.id, in_reply_to: inReplyTo, posted: false }
34174
34204
  };
34175
34205
  }
34206
+ const readBack = `dispatch_read({message: "${inReplyTo}"}) reads the conversation back.`;
34207
+ const parent = reply.in_reply_to ?? undefined;
34208
+ const follows = parent === inReplyTo ? undefined : parent;
34176
34209
  return {
34177
- text: `Replied to message ${inReplyTo} with message ${reply.id}`,
34178
- details: { message: reply.id, in_reply_to: inReplyTo, posted: true }
34210
+ text: follows === undefined ? `Replied to message ${inReplyTo} with message ${reply.id}. ${readBack}` : `Replied to message ${inReplyTo} with message ${reply.id}, a follow-up threaded ` + `under your reply ${follows}. ${readBack}`,
34211
+ details: {
34212
+ message: reply.id,
34213
+ in_reply_to: inReplyTo,
34214
+ posted: true,
34215
+ ...follows === undefined ? {} : { follows }
34216
+ }
34179
34217
  };
34180
34218
  }
34181
34219
  const issueKey = issue2();
@@ -34347,6 +34385,21 @@ ${trailer.join(`
34347
34385
  };
34348
34386
  }
34349
34387
  case "dispatch_read": {
34388
+ const message = optionalString(args, "message");
34389
+ if (message !== undefined) {
34390
+ const sessionId = input.sessionId?.trim();
34391
+ if (!sessionId)
34392
+ throw new Error("host session id is required for dispatch_read({message})");
34393
+ const thread = await client.getMessageThread(messageIdOf(message), sessionId);
34394
+ const issueKey2 = thread.message.issue_key;
34395
+ return {
34396
+ text: messageSummary(thread, issueKey2 === null ? [] : await graphSections(client, dispatchChildRef(dispatchIssueRef(issueKey2), "message", thread.message.id))),
34397
+ details: {
34398
+ message: thread.message.id,
34399
+ ...issueKey2 === null ? {} : { issue: issueKey2 }
34400
+ }
34401
+ };
34402
+ }
34350
34403
  if (ownerArguments.ref?.kind === "ask") {
34351
34404
  const ref = ownerArguments.ref;
34352
34405
  const id = await resolveIdPrefix(input.tool, "ask", ref.id, refOwnerName(ref), async () => ref.owner.kind === "issue" ? client.listIssueAsks(ref.owner.issue) : client.getArtifactAsks((await resolveDocument(ref.owner, ref.artifact)).artifact.id, "all"));