@sjawhar/opencode-legion-envoy 2.0.5 → 2.2.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/src/server.js +35 -6
- package/package.json +1 -1
- package/skills/dispatch/SKILL.md +25 -0
package/dist/src/server.js
CHANGED
|
@@ -13902,6 +13902,13 @@ var commentValidation = {
|
|
|
13902
13902
|
},
|
|
13903
13903
|
message: `${commentOwner.message} turn requires reply_to_ask.`
|
|
13904
13904
|
};
|
|
13905
|
+
var messageValidation = {
|
|
13906
|
+
check: (value) => {
|
|
13907
|
+
const input = value;
|
|
13908
|
+
return typeof input.issue === "string" || typeof input.in_reply_to === "string";
|
|
13909
|
+
},
|
|
13910
|
+
message: "issue is required unless in_reply_to names a message delivered to this session, which is the one message with no issue."
|
|
13911
|
+
};
|
|
13905
13912
|
var ISSUE_COMPONENTS_MODES = ["inherit", "explicit", "none"];
|
|
13906
13913
|
function componentsArgument(z2) {
|
|
13907
13914
|
return z2.object({
|
|
@@ -14119,12 +14126,13 @@ var dispatchToolSpecs = [
|
|
|
14119
14126
|
{
|
|
14120
14127
|
name: "dispatch_message",
|
|
14121
14128
|
example: { issue: "DSP-1", body: "Implementation started." },
|
|
14122
|
-
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 " +
|
|
14129
|
+
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). 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}`,
|
|
14123
14130
|
arguments: (z2) => ({
|
|
14124
|
-
issue: z2.string().describe(ISSUE_REFERENCE),
|
|
14131
|
+
issue: z2.string().describe(`${ISSUE_REFERENCE} Omit it only when in_reply_to answers a human's direct message to this session.`).optional(),
|
|
14125
14132
|
body: z2.string({ max: 2000 }).describe("Update text, at most 2,000 characters."),
|
|
14126
|
-
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.").optional()
|
|
14127
|
-
})
|
|
14133
|
+
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()
|
|
14134
|
+
}),
|
|
14135
|
+
validation: messageValidation
|
|
14128
14136
|
},
|
|
14129
14137
|
{
|
|
14130
14138
|
name: "dispatch_doc_edit",
|
|
@@ -15171,6 +15179,9 @@ class DispatchClient {
|
|
|
15171
15179
|
async message(issue2, input) {
|
|
15172
15180
|
return this.#json("POST", ["api", "v1", "issues", await this.#resolveIssue(issue2), "messages"], input);
|
|
15173
15181
|
}
|
|
15182
|
+
async messageReply(id, input) {
|
|
15183
|
+
return this.#json("POST", ["api", "v1", "messages", id, "reply"], input);
|
|
15184
|
+
}
|
|
15174
15185
|
async getMessage(issue2, id) {
|
|
15175
15186
|
return this.#json("GET", [
|
|
15176
15187
|
"api",
|
|
@@ -16019,6 +16030,10 @@ async function resolveOwnerArguments(tool, input, cwd, env, exec, serverUrl, pro
|
|
|
16019
16030
|
owner: issue3 === undefined ? null : { kind: "issue", issue: issue3 }
|
|
16020
16031
|
};
|
|
16021
16032
|
}
|
|
16033
|
+
const replyTarget = args.in_reply_to;
|
|
16034
|
+
if (tool === "dispatch_message" && typeof replyTarget === "string" && !replyTarget.startsWith("dispatch://")) {
|
|
16035
|
+
return { args, ref, owner: null };
|
|
16036
|
+
}
|
|
16022
16037
|
const legionIssue = env.LEGION_ISSUE;
|
|
16023
16038
|
if (!legionIssue) {
|
|
16024
16039
|
problems.push(ownerRequiredProblem);
|
|
@@ -16443,7 +16458,7 @@ async function executeDispatchTool(input) {
|
|
|
16443
16458
|
const schema = toolSchema(input.tool);
|
|
16444
16459
|
const parsed = schema.safeParse(ownerArguments.args, { reportInput: true });
|
|
16445
16460
|
if (!parsed.success) {
|
|
16446
|
-
const issues = parsed.error.issues.filter((issue3) => !ownerMissing || !(issue3.code === "invalid_type" && issue3.path.length === 1 && issue3.path[0] === "issue" && issue3.input === undefined || issue3.code === "custom" && issue3.path.length === 0 && issue3.message.startsWith("Exactly one of issue and project is required")));
|
|
16461
|
+
const issues = parsed.error.issues.filter((issue3) => !ownerMissing || !(issue3.code === "invalid_type" && issue3.path.length === 1 && issue3.path[0] === "issue" && issue3.input === undefined || issue3.code === "custom" && issue3.path.length === 0 && (issue3.message.startsWith("Exactly one of issue and project is required") || issue3.message.startsWith("issue is required unless in_reply_to"))));
|
|
16447
16462
|
problems.push(...formatZodIssues(issues, schema));
|
|
16448
16463
|
}
|
|
16449
16464
|
problems.push(...argumentProblems(input.tool, ownerArguments.args));
|
|
@@ -16873,9 +16888,23 @@ ${followsAsk(askOwner)}`,
|
|
|
16873
16888
|
}
|
|
16874
16889
|
case "dispatch_message": {
|
|
16875
16890
|
const inReplyTo = messageInReplyTo(args);
|
|
16891
|
+
const body = stringArg(args, "body");
|
|
16892
|
+
if (owner === null && inReplyTo !== undefined) {
|
|
16893
|
+
const reply = await client.messageReply(inReplyTo, { body, attempt: 1, actor });
|
|
16894
|
+
if (reply.body !== body) {
|
|
16895
|
+
return {
|
|
16896
|
+
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.",
|
|
16897
|
+
details: { message: reply.id, in_reply_to: inReplyTo, posted: false }
|
|
16898
|
+
};
|
|
16899
|
+
}
|
|
16900
|
+
return {
|
|
16901
|
+
text: `Replied to message ${inReplyTo} with message ${reply.id}`,
|
|
16902
|
+
details: { message: reply.id, in_reply_to: inReplyTo, posted: true }
|
|
16903
|
+
};
|
|
16904
|
+
}
|
|
16876
16905
|
const issueKey = issue2();
|
|
16877
16906
|
const message = await client.message(issueKey, {
|
|
16878
|
-
body
|
|
16907
|
+
body,
|
|
16879
16908
|
...inReplyTo === undefined ? {} : { in_reply_to: inReplyTo },
|
|
16880
16909
|
actor
|
|
16881
16910
|
});
|
package/package.json
CHANGED
package/skills/dispatch/SKILL.md
CHANGED
|
@@ -807,6 +807,31 @@ through Envoy or the hub. A bearer that targets over HTTP names its own session
|
|
|
807
807
|
target only a session that advertises the mode you want. Sending to a session with no issue
|
|
808
808
|
(`POST /api/v1/agents/{session_id}/messages`) stays human-only.
|
|
809
809
|
|
|
810
|
+
### Answering a direct message
|
|
811
|
+
|
|
812
|
+
A human can also message you directly from the **Agents** page, with no issue at all. That frame
|
|
813
|
+
names no issue and its `reply_with` hint carries none either; answer it with the message's bare
|
|
814
|
+
id in `in_reply_to`, alone:
|
|
815
|
+
|
|
816
|
+
```ts
|
|
817
|
+
dispatch_message({
|
|
818
|
+
in_reply_to: "<the direct message's id>",
|
|
819
|
+
body: "The requested answer.",
|
|
820
|
+
})
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
Leave `issue` out — there is no issue to post into, and naming one would file your answer on
|
|
824
|
+
unrelated work. Dispatch threads the reply under their message in the same conversation, and the
|
|
825
|
+
human sees it on your agent card. Every other message still names its issue, so keep the `issue`
|
|
826
|
+
the frame gave you whenever it gave you one; a `dispatch://KEY/message/<id>` reference names the
|
|
827
|
+
issue its message lives on, so that form is a reply on that issue, not a direct message.
|
|
828
|
+
|
|
829
|
+
One reply per message they send. A second call with the same `in_reply_to` posts nothing:
|
|
830
|
+
Dispatch answers it with the reply already stored, and the tool result says the message was
|
|
831
|
+
already answered rather than reporting a send. That happens most often when your host answered
|
|
832
|
+
the **BTW** automatically before you got here — read the result before writing again, and wait
|
|
833
|
+
for their next message instead.
|
|
834
|
+
|
|
810
835
|
## Following
|
|
811
836
|
|
|
812
837
|
An ask has followers: every session that wrote to it — the session that opened it and every session that replied with
|