@sjawhar/pi-legion-envoy 3.1.0 → 4.0.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/envoy.js +43 -12
- package/dist/legion.js +13 -5
- package/dist/skills/dispatch/SKILL.md +25 -0
- package/package.json +1 -1
package/dist/envoy.js
CHANGED
|
@@ -29997,6 +29997,13 @@ var commentValidation = {
|
|
|
29997
29997
|
},
|
|
29998
29998
|
message: `${commentOwner.message} turn requires reply_to_ask.`
|
|
29999
29999
|
};
|
|
30000
|
+
var messageValidation = {
|
|
30001
|
+
check: (value) => {
|
|
30002
|
+
const input = value;
|
|
30003
|
+
return typeof input.issue === "string" || typeof input.in_reply_to === "string";
|
|
30004
|
+
},
|
|
30005
|
+
message: "issue is required unless in_reply_to names a message delivered to this session, which is the one message with no issue."
|
|
30006
|
+
};
|
|
30000
30007
|
var ISSUE_COMPONENTS_MODES = ["inherit", "explicit", "none"];
|
|
30001
30008
|
function componentsArgument(z2) {
|
|
30002
30009
|
return z2.object({
|
|
@@ -30217,12 +30224,13 @@ var dispatchToolSpecs = [
|
|
|
30217
30224
|
{
|
|
30218
30225
|
name: "dispatch_message",
|
|
30219
30226
|
example: { issue: "DSP-1", body: "Implementation started." },
|
|
30220
|
-
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 " +
|
|
30227
|
+
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}`,
|
|
30221
30228
|
arguments: (z2) => ({
|
|
30222
|
-
issue: z2.string().describe(ISSUE_REFERENCE),
|
|
30229
|
+
issue: z2.string().describe(`${ISSUE_REFERENCE} Omit it only when in_reply_to answers a human's direct message to this session.`).optional(),
|
|
30223
30230
|
body: z2.string({ max: 2000 }).describe("Update text, at most 2,000 characters."),
|
|
30224
|
-
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()
|
|
30225
|
-
})
|
|
30231
|
+
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()
|
|
30232
|
+
}),
|
|
30233
|
+
validation: messageValidation
|
|
30226
30234
|
},
|
|
30227
30235
|
{
|
|
30228
30236
|
name: "dispatch_doc_edit",
|
|
@@ -31879,12 +31887,14 @@ function renderInbound(raw, sessionID, subject2) {
|
|
|
31879
31887
|
issueKey: frame.event.issue_key,
|
|
31880
31888
|
body: message2.data.body
|
|
31881
31889
|
};
|
|
31882
|
-
|
|
31883
|
-
|
|
31884
|
-
|
|
31885
|
-
|
|
31886
|
-
|
|
31887
|
-
|
|
31890
|
+
dispatchReply = {
|
|
31891
|
+
tool: "dispatch_message",
|
|
31892
|
+
args: {
|
|
31893
|
+
...frame.event.issue_key === null ? {} : { issue: frame.event.issue_key },
|
|
31894
|
+
in_reply_to: message2.data.id,
|
|
31895
|
+
body: "..."
|
|
31896
|
+
}
|
|
31897
|
+
};
|
|
31888
31898
|
}
|
|
31889
31899
|
} else if (frame.event.type === "comment.created") {
|
|
31890
31900
|
const comment2 = DispatchTargetedCommentPayloadSchema.safeParse(frame.event.payload);
|
|
@@ -32371,6 +32381,9 @@ class DispatchClient {
|
|
|
32371
32381
|
async message(issue2, input) {
|
|
32372
32382
|
return this.#json("POST", ["api", "v1", "issues", await this.#resolveIssue(issue2), "messages"], input);
|
|
32373
32383
|
}
|
|
32384
|
+
async messageReply(id, input) {
|
|
32385
|
+
return this.#json("POST", ["api", "v1", "messages", id, "reply"], input);
|
|
32386
|
+
}
|
|
32374
32387
|
async getMessage(issue2, id) {
|
|
32375
32388
|
return this.#json("GET", [
|
|
32376
32389
|
"api",
|
|
@@ -33196,6 +33209,10 @@ async function resolveOwnerArguments(tool, input, cwd, env, exec, serverUrl, pro
|
|
|
33196
33209
|
owner: issue3 === undefined ? null : { kind: "issue", issue: issue3 }
|
|
33197
33210
|
};
|
|
33198
33211
|
}
|
|
33212
|
+
const replyTarget = args.in_reply_to;
|
|
33213
|
+
if (tool === "dispatch_message" && typeof replyTarget === "string" && !replyTarget.startsWith("dispatch://")) {
|
|
33214
|
+
return { args, ref, owner: null };
|
|
33215
|
+
}
|
|
33199
33216
|
const legionIssue = env.LEGION_ISSUE;
|
|
33200
33217
|
if (!legionIssue) {
|
|
33201
33218
|
problems.push(ownerRequiredProblem);
|
|
@@ -33620,7 +33637,7 @@ async function executeDispatchTool(input) {
|
|
|
33620
33637
|
const schema = toolSchema(input.tool);
|
|
33621
33638
|
const parsed = schema.safeParse(ownerArguments.args, { reportInput: true });
|
|
33622
33639
|
if (!parsed.success) {
|
|
33623
|
-
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")));
|
|
33640
|
+
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"))));
|
|
33624
33641
|
problems.push(...formatZodIssues(issues, schema));
|
|
33625
33642
|
}
|
|
33626
33643
|
problems.push(...argumentProblems(input.tool, ownerArguments.args));
|
|
@@ -34050,9 +34067,23 @@ ${followsAsk(askOwner)}`,
|
|
|
34050
34067
|
}
|
|
34051
34068
|
case "dispatch_message": {
|
|
34052
34069
|
const inReplyTo = messageInReplyTo(args);
|
|
34070
|
+
const body = stringArg(args, "body");
|
|
34071
|
+
if (owner === null && inReplyTo !== undefined) {
|
|
34072
|
+
const reply = await client.messageReply(inReplyTo, { body, attempt: 1, actor });
|
|
34073
|
+
if (reply.body !== body) {
|
|
34074
|
+
return {
|
|
34075
|
+
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.",
|
|
34076
|
+
details: { message: reply.id, in_reply_to: inReplyTo, posted: false }
|
|
34077
|
+
};
|
|
34078
|
+
}
|
|
34079
|
+
return {
|
|
34080
|
+
text: `Replied to message ${inReplyTo} with message ${reply.id}`,
|
|
34081
|
+
details: { message: reply.id, in_reply_to: inReplyTo, posted: true }
|
|
34082
|
+
};
|
|
34083
|
+
}
|
|
34053
34084
|
const issueKey = issue2();
|
|
34054
34085
|
const message = await client.message(issueKey, {
|
|
34055
|
-
body
|
|
34086
|
+
body,
|
|
34056
34087
|
...inReplyTo === undefined ? {} : { in_reply_to: inReplyTo },
|
|
34057
34088
|
actor
|
|
34058
34089
|
});
|
package/dist/legion.js
CHANGED
|
@@ -16142,7 +16142,7 @@ import { logger } from "@oh-my-pi/pi-utils";
|
|
|
16142
16142
|
// package.json
|
|
16143
16143
|
var package_default = {
|
|
16144
16144
|
name: "@sjawhar/pi-legion-envoy",
|
|
16145
|
-
version: "
|
|
16145
|
+
version: "4.0.0",
|
|
16146
16146
|
type: "module",
|
|
16147
16147
|
omp: {
|
|
16148
16148
|
extensions: [
|
|
@@ -30070,6 +30070,13 @@ var commentValidation = {
|
|
|
30070
30070
|
},
|
|
30071
30071
|
message: `${commentOwner.message} turn requires reply_to_ask.`
|
|
30072
30072
|
};
|
|
30073
|
+
var messageValidation = {
|
|
30074
|
+
check: (value) => {
|
|
30075
|
+
const input = value;
|
|
30076
|
+
return typeof input.issue === "string" || typeof input.in_reply_to === "string";
|
|
30077
|
+
},
|
|
30078
|
+
message: "issue is required unless in_reply_to names a message delivered to this session, which is the one message with no issue."
|
|
30079
|
+
};
|
|
30073
30080
|
var ISSUE_COMPONENTS_MODES = ["inherit", "explicit", "none"];
|
|
30074
30081
|
function componentsArgument(z2) {
|
|
30075
30082
|
return z2.object({
|
|
@@ -30290,12 +30297,13 @@ var dispatchToolSpecs = [
|
|
|
30290
30297
|
{
|
|
30291
30298
|
name: "dispatch_message",
|
|
30292
30299
|
example: { issue: "DSP-1", body: "Implementation started." },
|
|
30293
|
-
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 " +
|
|
30300
|
+
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}`,
|
|
30294
30301
|
arguments: (z2) => ({
|
|
30295
|
-
issue: z2.string().describe(ISSUE_REFERENCE),
|
|
30302
|
+
issue: z2.string().describe(`${ISSUE_REFERENCE} Omit it only when in_reply_to answers a human's direct message to this session.`).optional(),
|
|
30296
30303
|
body: z2.string({ max: 2000 }).describe("Update text, at most 2,000 characters."),
|
|
30297
|
-
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()
|
|
30298
|
-
})
|
|
30304
|
+
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()
|
|
30305
|
+
}),
|
|
30306
|
+
validation: messageValidation
|
|
30299
30307
|
},
|
|
30300
30308
|
{
|
|
30301
30309
|
name: "dispatch_doc_edit",
|
|
@@ -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
|