@sjawhar/opencode-legion-envoy 1.46.0 → 1.47.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 +72 -29
- package/package.json +1 -1
- package/skills/dispatch/SKILL.md +4 -2
package/dist/src/server.js
CHANGED
|
@@ -13552,6 +13552,13 @@ function date4(params) {
|
|
|
13552
13552
|
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
|
|
13553
13553
|
config(en_default());
|
|
13554
13554
|
// ../contracts/src/dispatch-api.ts
|
|
13555
|
+
function serviceSubjectLabel(subject) {
|
|
13556
|
+
const parts = subject.split(":");
|
|
13557
|
+
if (parts.length === 4 && parts[0] === "system" && parts[1] === "serviceaccount" && parts[2] !== "" && parts[3] !== "") {
|
|
13558
|
+
return `${parts[2]}/${parts[3]}`;
|
|
13559
|
+
}
|
|
13560
|
+
return subject;
|
|
13561
|
+
}
|
|
13555
13562
|
var ASK_TURNS = ["human", "agent"];
|
|
13556
13563
|
var DELIVERY_CAPABILITIES = ["aside", "btw", "steer"];
|
|
13557
13564
|
var DispatchEventSchema = object({
|
|
@@ -13952,7 +13959,7 @@ var dispatchToolSpecs = [
|
|
|
13952
13959
|
},
|
|
13953
13960
|
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.",
|
|
13954
13961
|
arguments: (z) => ({
|
|
13955
|
-
ask: z.string().describe("Ask id
|
|
13962
|
+
ask: z.string().describe("Ask id (uuid); an 8+ hex prefix unique among this session's own open asks works too."),
|
|
13956
13963
|
question: z.string({ max: ASK_QUESTION_MAX }).describe(`Replacement decision question, at most ${ASK_QUESTION_MAX} characters.`).optional(),
|
|
13957
13964
|
options: z.array(z.object({
|
|
13958
13965
|
label: z.string().describe("Selectable option label."),
|
|
@@ -13978,7 +13985,7 @@ var dispatchToolSpecs = [
|
|
|
13978
13985
|
},
|
|
13979
13986
|
description: "Retract an open question that is moot or resolve one after finding the answer. This closes the question without answering it.",
|
|
13980
13987
|
arguments: (z) => ({
|
|
13981
|
-
ask: z.string().describe("Ask id to close."),
|
|
13988
|
+
ask: z.string().describe("Ask id (uuid) to close; an 8+ hex prefix unique among this session's own open asks " + "works too."),
|
|
13982
13989
|
kind: z.enum(["retracted", "resolved"]).describe("Whether the ask is retracted or self-resolved."),
|
|
13983
13990
|
reason: z.string({ min: 1 }).describe("Why the open ask no longer needs a human answer.")
|
|
13984
13991
|
})
|
|
@@ -13997,7 +14004,7 @@ var dispatchToolSpecs = [
|
|
|
13997
14004
|
example: { ask: "01234567-0000-4000-8000-000000000001", action: "follow" },
|
|
13998
14005
|
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>.>",
|
|
13999
14006
|
arguments: (z) => ({
|
|
14000
|
-
ask: z.string().describe("
|
|
14007
|
+
ask: z.string().describe("Ask id (uuid); an 8+ hex prefix unique among this session's own open asks works too."),
|
|
14001
14008
|
action: z.enum(["follow", "unfollow"]).describe("follow | unfollow")
|
|
14002
14009
|
}),
|
|
14003
14010
|
strict: true
|
|
@@ -15688,24 +15695,51 @@ function messageIdOf(value) {
|
|
|
15688
15695
|
return id === undefined ? undefined : normalizeUUID(id);
|
|
15689
15696
|
}
|
|
15690
15697
|
var idPrefixPattern = /^[0-9a-f][0-9a-f-]{7,}$/i;
|
|
15691
|
-
|
|
15692
|
-
|
|
15698
|
+
function refOwnerName(ref) {
|
|
15699
|
+
return ref.owner.kind === "issue" ? ref.owner.issue : `${ref.owner.project}/${ref.artifact}`;
|
|
15700
|
+
}
|
|
15701
|
+
async function resolveIdPrefix(tool, kind, id, ownerName, list) {
|
|
15702
|
+
const fullID = normalizeUUID(id);
|
|
15693
15703
|
if (fullID !== undefined)
|
|
15694
15704
|
return fullID;
|
|
15695
|
-
|
|
15696
|
-
if (!idPrefixPattern.test(ref.id)) {
|
|
15705
|
+
if (!idPrefixPattern.test(id)) {
|
|
15697
15706
|
throw new ToolInputError(tool, [
|
|
15698
|
-
`${kind} id ${
|
|
15707
|
+
`${kind} id ${id} must be a full uuid or a prefix of at least 8 hex characters`
|
|
15699
15708
|
]);
|
|
15700
15709
|
}
|
|
15701
|
-
const prefix =
|
|
15710
|
+
const prefix = id.toLowerCase();
|
|
15702
15711
|
const matches = (await list()).filter((item) => item.id.toLowerCase().startsWith(prefix));
|
|
15703
15712
|
if (matches.length === 1 && matches[0] !== undefined)
|
|
15704
15713
|
return matches[0].id;
|
|
15705
15714
|
throw new ToolInputError(tool, [
|
|
15706
|
-
matches.length === 0 ? `${kind} id ${
|
|
15715
|
+
matches.length === 0 ? `${kind} id ${id} matches none of the ${kind}s on ${ownerName}; use the full id` : `${kind} id ${id} matches ${matches.length} ${kind}s on ${ownerName}; use the full id`
|
|
15707
15716
|
]);
|
|
15708
15717
|
}
|
|
15718
|
+
var askIdShapeProblem = "ask ids are uuids (a prefix of at least 8 hex characters works)";
|
|
15719
|
+
async function sessionOpenAsks(client, sessionId) {
|
|
15720
|
+
const session = sessionId?.trim();
|
|
15721
|
+
if (!session)
|
|
15722
|
+
throw new Error("host session id is required to read this session's open asks");
|
|
15723
|
+
return (await client.openAsks(session)).asks;
|
|
15724
|
+
}
|
|
15725
|
+
function openAskHints(asks) {
|
|
15726
|
+
if (asks.length === 0)
|
|
15727
|
+
return "you have no open asks";
|
|
15728
|
+
const hints = asks.slice(0, askHintLimit).map((ask) => `${ask.id.slice(0, 8)} \u2014 ${textHead(ask.question)}`);
|
|
15729
|
+
return `your open asks: ${hints.join("; ")}`;
|
|
15730
|
+
}
|
|
15731
|
+
async function resolveAskArgument(tool, args, client, sessionId) {
|
|
15732
|
+
const id = askId(args);
|
|
15733
|
+
if (normalizeUUID(id) === undefined && !idPrefixPattern.test(id)) {
|
|
15734
|
+
const asks = await sessionOpenAsks(client, sessionId).catch(() => {
|
|
15735
|
+
return;
|
|
15736
|
+
});
|
|
15737
|
+
throw new ToolInputError(tool, [
|
|
15738
|
+
asks === undefined ? askIdShapeProblem : `${askIdShapeProblem}; ${openAskHints(asks)}`
|
|
15739
|
+
]);
|
|
15740
|
+
}
|
|
15741
|
+
return resolveIdPrefix(tool, "ask", id, "this session", () => sessionOpenAsks(client, sessionId));
|
|
15742
|
+
}
|
|
15709
15743
|
function messageInReplyTo(args) {
|
|
15710
15744
|
const inReplyTo = optionalString(args, "in_reply_to");
|
|
15711
15745
|
return inReplyTo === undefined ? undefined : messageIdOf(inReplyTo);
|
|
@@ -15724,7 +15758,8 @@ function argumentProblems(tool, args) {
|
|
|
15724
15758
|
break;
|
|
15725
15759
|
}
|
|
15726
15760
|
case "dispatch_edit_ask":
|
|
15727
|
-
case "dispatch_resolve_ask":
|
|
15761
|
+
case "dispatch_resolve_ask":
|
|
15762
|
+
case "dispatch_follow": {
|
|
15728
15763
|
const ask = optionalString(args, "ask");
|
|
15729
15764
|
if (ask?.startsWith("dispatch://") && parseDispatchRef(ask)?.kind !== "ask") {
|
|
15730
15765
|
problems.push(askIdProblem);
|
|
@@ -16117,9 +16152,16 @@ function eventHead(event) {
|
|
|
16117
16152
|
return;
|
|
16118
16153
|
}
|
|
16119
16154
|
}
|
|
16155
|
+
function actorText(actor) {
|
|
16156
|
+
const service = actor.kind === "session" ? actor.service : undefined;
|
|
16157
|
+
if (service === undefined) {
|
|
16158
|
+
return `${actor.kind} ${actor.id}`;
|
|
16159
|
+
}
|
|
16160
|
+
return `${actor.kind} ${actor.id} (as ${serviceSubjectLabel(service)})`;
|
|
16161
|
+
}
|
|
16120
16162
|
function eventLine(event) {
|
|
16121
16163
|
const head = eventHead(event);
|
|
16122
|
-
return `- #${event.seq} ${event.type} \xB7 ${event.actor
|
|
16164
|
+
return `- #${event.seq} ${event.type} \xB7 ${actorText(event.actor)} \xB7 ${event.created_at}${head === undefined || head === "" ? "" : ` \xB7 ${head}`}`;
|
|
16123
16165
|
}
|
|
16124
16166
|
function childrenSummary(issue) {
|
|
16125
16167
|
return [
|
|
@@ -16132,7 +16174,7 @@ function childrenSummary(issue) {
|
|
|
16132
16174
|
function askSummary({ ask, replies }, graph) {
|
|
16133
16175
|
const answer = ask.answer;
|
|
16134
16176
|
const chain = replies.flatMap((reply) => [
|
|
16135
|
-
`${reply.id} \xB7 ${reply.author
|
|
16177
|
+
`${reply.id} \xB7 ${actorText(reply.author)}`,
|
|
16136
16178
|
`Body: ${reply.body}`
|
|
16137
16179
|
]);
|
|
16138
16180
|
return [
|
|
@@ -16199,12 +16241,12 @@ function formatOpenAsksSummary(response, baseUrl) {
|
|
|
16199
16241
|
}
|
|
16200
16242
|
function commentSummary({ comment, replies }, graph) {
|
|
16201
16243
|
const root = [
|
|
16202
|
-
`${comment.id} \xB7 ${comment.author
|
|
16244
|
+
`${comment.id} \xB7 ${actorText(comment.author)}`,
|
|
16203
16245
|
...comment.anchor?.quote === undefined ? [] : [`> ${comment.anchor.quote}`],
|
|
16204
16246
|
`Body: ${comment.body}`
|
|
16205
16247
|
];
|
|
16206
16248
|
const chain = replies.flatMap((reply) => [
|
|
16207
|
-
`${reply.id} \xB7 ${reply.author
|
|
16249
|
+
`${reply.id} \xB7 ${actorText(reply.author)}`,
|
|
16208
16250
|
...reply.anchor?.quote === undefined ? [] : [`> ${reply.anchor.quote}`],
|
|
16209
16251
|
`Body: ${reply.body}`
|
|
16210
16252
|
]);
|
|
@@ -16218,12 +16260,9 @@ function commentSummary({ comment, replies }, graph) {
|
|
|
16218
16260
|
`);
|
|
16219
16261
|
}
|
|
16220
16262
|
function messageSummary({ message, replies }, graph) {
|
|
16221
|
-
const root = [
|
|
16222
|
-
`${message.id} \xB7 ${message.author.kind} ${message.author.id}`,
|
|
16223
|
-
`Body: ${message.body}`
|
|
16224
|
-
];
|
|
16263
|
+
const root = [`${message.id} \xB7 ${actorText(message.author)}`, `Body: ${message.body}`];
|
|
16225
16264
|
const chain = replies.flatMap((reply) => [
|
|
16226
|
-
`${reply.id} \xB7 ${reply.author
|
|
16265
|
+
`${reply.id} \xB7 ${actorText(reply.author)}`,
|
|
16227
16266
|
`Body: ${reply.body}`
|
|
16228
16267
|
]);
|
|
16229
16268
|
return [
|
|
@@ -16305,9 +16344,11 @@ async function executeDispatchTool(input) {
|
|
|
16305
16344
|
const client = dispatchClient();
|
|
16306
16345
|
const identity = await client.whoami();
|
|
16307
16346
|
const owner = identity.kind === "agent" ? identity.owner : identity.login.toLowerCase();
|
|
16347
|
+
const service = identity.kind === "agent" ? identity.service ?? null : null;
|
|
16348
|
+
const unowned = "no owner, so issues you create without an assignee are unassigned (or inherit their parent's).";
|
|
16308
16349
|
return {
|
|
16309
|
-
text: owner
|
|
16310
|
-
details: { session: sessionId, owner }
|
|
16350
|
+
text: owner !== null ? `Session ${sessionId} acts for ${owner}: issues you create without an assignee are assigned to ${owner}.` : service !== null ? `Session ${sessionId} runs as service ${serviceSubjectLabel(service)}: ${unowned}` : `Session ${sessionId} runs under the shared token: ${unowned}`,
|
|
16351
|
+
details: { session: sessionId, owner, service }
|
|
16311
16352
|
};
|
|
16312
16353
|
}
|
|
16313
16354
|
const args = parsed.success ? parsed.data : ownerArguments.args;
|
|
@@ -16518,7 +16559,8 @@ async function executeDispatchTool(input) {
|
|
|
16518
16559
|
}
|
|
16519
16560
|
case "dispatch_resolve_ask": {
|
|
16520
16561
|
const kind = stringArg(args, "kind");
|
|
16521
|
-
const
|
|
16562
|
+
const id = await resolveAskArgument(input.tool, args, client, input.sessionId);
|
|
16563
|
+
const ask = await client.resolveAsk(id, {
|
|
16522
16564
|
kind,
|
|
16523
16565
|
reason: stringArg(args, "reason"),
|
|
16524
16566
|
actor
|
|
@@ -16537,11 +16579,11 @@ async function executeDispatchTool(input) {
|
|
|
16537
16579
|
let document;
|
|
16538
16580
|
if (ref?.owner.kind === "issue") {
|
|
16539
16581
|
const issueKey = ref.owner.issue;
|
|
16540
|
-
id = await resolveIdPrefix(input.tool, "comment", ref, () => client.getComments(issueKey));
|
|
16582
|
+
id = await resolveIdPrefix(input.tool, "comment", ref.id, refOwnerName(ref), () => client.getComments(issueKey));
|
|
16541
16583
|
} else if (ref !== null) {
|
|
16542
16584
|
const artifact = (await resolveArtifact(client, ref.owner, ref.artifact)).artifact;
|
|
16543
16585
|
document = artifact;
|
|
16544
|
-
id = await resolveIdPrefix(input.tool, "comment", ref, () => client.getArtifactComments(artifact.id));
|
|
16586
|
+
id = await resolveIdPrefix(input.tool, "comment", ref.id, refOwnerName(ref), () => client.getArtifactComments(artifact.id));
|
|
16545
16587
|
}
|
|
16546
16588
|
const comment = await client.resolveComment(id, actor);
|
|
16547
16589
|
const { label, details } = await commentOwnerResult(client, comment, document);
|
|
@@ -16585,7 +16627,8 @@ ${followsAsk(askOwner)}`,
|
|
|
16585
16627
|
const options = args.options;
|
|
16586
16628
|
const multiple = optionalBoolean(args, "multiple");
|
|
16587
16629
|
const urgency = askUrgency(args);
|
|
16588
|
-
const
|
|
16630
|
+
const id = await resolveAskArgument(input.tool, args, client, input.sessionId);
|
|
16631
|
+
const ask = await client.editAsk(id, {
|
|
16589
16632
|
...question === undefined ? {} : { question },
|
|
16590
16633
|
...Array.isArray(options) ? { options } : {},
|
|
16591
16634
|
...multiple === undefined ? {} : { multiple },
|
|
@@ -16812,7 +16855,7 @@ ${trailer.join(`
|
|
|
16812
16855
|
const sessionId = input.sessionId?.trim();
|
|
16813
16856
|
if (!sessionId)
|
|
16814
16857
|
throw new Error("host session id is required for dispatch_follow");
|
|
16815
|
-
const ask =
|
|
16858
|
+
const ask = await resolveAskArgument(input.tool, args, client, sessionId);
|
|
16816
16859
|
const action = stringArg(args, "action");
|
|
16817
16860
|
if (action === "unfollow") {
|
|
16818
16861
|
await client.unfollowAsk(ask, sessionId, actor);
|
|
@@ -16828,7 +16871,7 @@ ${trailer.join(`
|
|
|
16828
16871
|
case "dispatch_read": {
|
|
16829
16872
|
if (ownerArguments.ref?.kind === "ask") {
|
|
16830
16873
|
const ref = ownerArguments.ref;
|
|
16831
|
-
const id = await resolveIdPrefix(input.tool, "ask", ref, async () => ref.owner.kind === "issue" ? client.listIssueAsks(ref.owner.issue) : client.getArtifactAsks((await resolveArtifact(client, ref.owner, ref.artifact)).artifact.id, "all"));
|
|
16874
|
+
const id = await resolveIdPrefix(input.tool, "ask", ref.id, refOwnerName(ref), async () => ref.owner.kind === "issue" ? client.listIssueAsks(ref.owner.issue) : client.getArtifactAsks((await resolveArtifact(client, ref.owner, ref.artifact)).artifact.id, "all"));
|
|
16832
16875
|
const askRead = await client.getAsk(id);
|
|
16833
16876
|
const askRef = refTarget(ref, "ask", id);
|
|
16834
16877
|
return {
|
|
@@ -16838,7 +16881,7 @@ ${trailer.join(`
|
|
|
16838
16881
|
}
|
|
16839
16882
|
if (ownerArguments.ref?.kind === "comment") {
|
|
16840
16883
|
const ref = ownerArguments.ref;
|
|
16841
|
-
const id = await resolveIdPrefix(input.tool, "comment", ref, async () => ref.owner.kind === "issue" ? client.getComments(ref.owner.issue) : client.getArtifactComments((await resolveArtifact(client, ref.owner, ref.artifact)).artifact.id));
|
|
16884
|
+
const id = await resolveIdPrefix(input.tool, "comment", ref.id, refOwnerName(ref), async () => ref.owner.kind === "issue" ? client.getComments(ref.owner.issue) : client.getArtifactComments((await resolveArtifact(client, ref.owner, ref.artifact)).artifact.id));
|
|
16842
16885
|
const comment = await client.getComment(id);
|
|
16843
16886
|
const commentRef = refTarget(ref, "comment", id);
|
|
16844
16887
|
return {
|
package/package.json
CHANGED
package/skills/dispatch/SKILL.md
CHANGED
|
@@ -148,7 +148,7 @@ An ask goes to the issue's assignee: their Inbox opens on **Mine**, which lists
|
|
|
148
148
|
```ts
|
|
149
149
|
dispatch_whoami({})
|
|
150
150
|
```
|
|
151
|
-
It returns `details` `{ session, owner }`: `owner` is the lowercase login of the human whose personal token you run under, or `null` under the shared token. An issue you create without `assignee` goes to your owner;
|
|
151
|
+
It returns `details` `{ session, owner, service }`: `owner` is the lowercase login of the human whose personal token you run under, or `null` under the shared token or a verified service token, in which case `service` is that token's subject (`system:serviceaccount:<namespace>:<name>`, `null` otherwise) and every write you make is rendered `(as <namespace>/<name>)` — the namespace is kept because every namespace has a `default` service account, and a subject that is not a Kubernetes one is shown whole. An issue you create without `assignee` goes to your owner; with no owner it inherits its parent's assignee, or stays unassigned without a parent. If an issue you are asking on is unassigned and the answer matters, assign it to your owner (`PATCH /api/v1/issues/{key}` with `{"assignee": "<login>"}`; any authenticated caller may reassign, and an unlisted login is refused with `ASSIGNEE_NOT_ALLOWED`) or name in the question who should answer it. Never reassign an issue a human holds to get an answer faster: that is the human's call.
|
|
152
152
|
|
|
153
153
|
Architects create newly tracked child work with:
|
|
154
154
|
```ts
|
|
@@ -708,7 +708,9 @@ the host tells you once per ask. Leave a thread you no longer need, or rejoin on
|
|
|
708
708
|
dispatch_follow({ ask, action: "follow" | "unfollow" })
|
|
709
709
|
```
|
|
710
710
|
|
|
711
|
-
`ask` is the full ask id or a `dispatch://KEY/ask/<id>` reference
|
|
711
|
+
`ask` is the full ask id or a `dispatch://KEY/ask/<id>` reference; `dispatch_follow`, `dispatch_edit_ask`, and
|
|
712
|
+
`dispatch_resolve_ask` also take an 8+ hex prefix that is unique among your own open asks, and refuse anything shorter by naming
|
|
713
|
+
those asks. A human may also remove you from the ask card; either way you are
|
|
712
714
|
told with an `ask.follower_removed` notice, and a human adding you arrives as `ask.follower_added`.
|
|
713
715
|
|
|
714
716
|
No write subscribes you to an issue or document. Following covers your own asks and the threads you joined; everything else on the
|