@sjawhar/opencode-legion-envoy 1.20.0 → 1.20.2
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 +31 -4
- package/package.json +1 -1
- package/skills/dispatch/SKILL.md +21 -0
package/dist/src/server.js
CHANGED
|
@@ -13786,7 +13786,7 @@ var dispatchToolSpecs = [
|
|
|
13786
13786
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
13787
13787
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
13788
13788
|
artifact: z.string().describe("Project document artifact id, slug, or filename.").optional(),
|
|
13789
|
-
ref: z.string().describe("Optional dispatch:// issue or
|
|
13789
|
+
ref: z.string().describe("Optional dispatch:// reference (issue, document, message, or ask); appended to the question and rendered as a link.").optional(),
|
|
13790
13790
|
question: z.string({ max: 800 }).describe("Decision question, at most 800 characters."),
|
|
13791
13791
|
kind: z.enum(["action"]).describe("Optional human to-do ask kind.").optional(),
|
|
13792
13792
|
options: z.array(z.object({
|
|
@@ -15035,6 +15035,19 @@ function askKind(args) {
|
|
|
15035
15035
|
return value;
|
|
15036
15036
|
throw new Error("kind must be action");
|
|
15037
15037
|
}
|
|
15038
|
+
function askQuestionWithRef(args) {
|
|
15039
|
+
const question = stringArg(args, "question");
|
|
15040
|
+
const ref = optionalString(args, "ref");
|
|
15041
|
+
if (ref === undefined || question.includes(ref))
|
|
15042
|
+
return question;
|
|
15043
|
+
const withRef = `${question}
|
|
15044
|
+
|
|
15045
|
+
Ref: ${ref}`;
|
|
15046
|
+
if (withRef.length > 800) {
|
|
15047
|
+
throw new Error("question plus ref must be at most 800 characters");
|
|
15048
|
+
}
|
|
15049
|
+
return withRef;
|
|
15050
|
+
}
|
|
15038
15051
|
function parseDispatchRef(ref) {
|
|
15039
15052
|
const projectDocument = ref.match(/^dispatch:\/\/([A-Z][A-Z0-9]{1,9})\/artifact\/([^/@]+)(?:@v(\d+))?(?:\/(ask|comment)\/([^/]+))?$/);
|
|
15040
15053
|
if (projectDocument) {
|
|
@@ -15117,6 +15130,9 @@ async function resolveOwnerArguments(tool, args, cwd, env, exec) {
|
|
|
15117
15130
|
if (issueFreeTools[tool] === true)
|
|
15118
15131
|
return { args, ref: null, owner: null };
|
|
15119
15132
|
const refArgument = args.ref;
|
|
15133
|
+
if (tool === "dispatch_ask" && typeof refArgument === "string" && !refArgument.startsWith("dispatch://")) {
|
|
15134
|
+
throw new Error("ref must be a dispatch:// reference");
|
|
15135
|
+
}
|
|
15120
15136
|
const ref = typeof refArgument === "string" ? parseDispatchRef(refArgument) ?? (() => {
|
|
15121
15137
|
throw new Error("ref must be a valid dispatch:// reference such as dispatch://KEY-1, " + "dispatch://KEY-1/ask/<uuid>, dispatch://KEY-1/comment/<uuid>, " + "dispatch://KEY-1/message/<uuid>, dispatch://KEY-1/artifact/<slug>, or " + "dispatch://PROJECT/artifact/<document-ref> (an artifact id, slug, or filename)");
|
|
15122
15138
|
})() : null;
|
|
@@ -15406,12 +15422,23 @@ async function executeDispatchTool(input) {
|
|
|
15406
15422
|
if (!input.config.enabled || !configUrl || !configToken) {
|
|
15407
15423
|
throw new Error("Dispatch is disabled; resolve both DISPATCH_URL and DISPATCH_TOKEN");
|
|
15408
15424
|
}
|
|
15425
|
+
const baseFetch = input.fetchImpl ?? fetch;
|
|
15426
|
+
const fetchImpl = Object.assign(async (...args) => {
|
|
15427
|
+
try {
|
|
15428
|
+
return await baseFetch(...args);
|
|
15429
|
+
} catch (error) {
|
|
15430
|
+
if (error instanceof TypeError) {
|
|
15431
|
+
throw new Error(`Dispatch at ${configUrl} is unreachable: ${error.message}. If the Dispatch URL changed, restart this agent process so it picks up the new configuration.`);
|
|
15432
|
+
}
|
|
15433
|
+
throw error;
|
|
15434
|
+
}
|
|
15435
|
+
}, { preconnect: baseFetch.preconnect });
|
|
15409
15436
|
if (input.tool === "dispatch_open_asks") {
|
|
15410
15437
|
const sessionId = input.sessionId?.trim();
|
|
15411
15438
|
if (!sessionId)
|
|
15412
15439
|
throw new Error("host session id is required for dispatch_open_asks");
|
|
15413
15440
|
toolSchema(input.tool).parse(input.args);
|
|
15414
|
-
const client = new DispatchClient(configUrl, configToken,
|
|
15441
|
+
const client = new DispatchClient(configUrl, configToken, fetchImpl, input.signal);
|
|
15415
15442
|
const response = await client.openAsks(sessionId);
|
|
15416
15443
|
return { text: formatOpenAsksSummary(response, configUrl), details: { ...response } };
|
|
15417
15444
|
}
|
|
@@ -15420,7 +15447,7 @@ async function executeDispatchTool(input) {
|
|
|
15420
15447
|
const ownerArguments = await resolveOwnerArguments(input.tool, input.args, input.cwd, env, exec);
|
|
15421
15448
|
const args = toolSchema(input.tool).parse(ownerArguments.args);
|
|
15422
15449
|
const actor = toolActor(await resolveOrigin(env, exec, input.cwd), input);
|
|
15423
|
-
const client = new DispatchClient(configUrl, configToken,
|
|
15450
|
+
const client = new DispatchClient(configUrl, configToken, fetchImpl, input.signal);
|
|
15424
15451
|
const owner = ownerArguments.owner?.kind === "issue" ? {
|
|
15425
15452
|
kind: "issue",
|
|
15426
15453
|
issue: await ensureIssue(client, ownerArguments.owner.issue, actor)
|
|
@@ -15526,7 +15553,7 @@ async function executeDispatchTool(input) {
|
|
|
15526
15553
|
const kind = askKind(args);
|
|
15527
15554
|
const anchored = anchorArgs && resolved ? anchor(resolved.artifact, anchorArgs) : undefined;
|
|
15528
15555
|
const askInput = {
|
|
15529
|
-
question:
|
|
15556
|
+
question: askQuestionWithRef(args),
|
|
15530
15557
|
...kind === undefined ? {} : { kind },
|
|
15531
15558
|
...Array.isArray(options) ? { options } : {},
|
|
15532
15559
|
...multiple === undefined ? {} : { multiple },
|
package/package.json
CHANGED
package/skills/dispatch/SKILL.md
CHANGED
|
@@ -111,6 +111,7 @@ dispatch_ask({
|
|
|
111
111
|
issue?,
|
|
112
112
|
project?,
|
|
113
113
|
artifact?,
|
|
114
|
+
ref?,
|
|
114
115
|
question,
|
|
115
116
|
options?: { label, description? }[],
|
|
116
117
|
multiple?,
|
|
@@ -120,6 +121,8 @@ dispatch_ask({
|
|
|
120
121
|
```
|
|
121
122
|
It returns `details` `{ issue, topic, ask }` for an issue or `{ project, artifact, document, topic, ask }` for a project document.
|
|
122
123
|
|
|
124
|
+
References belong in the question text; `ref` is sugar that appends its `dispatch://` value to the question as a rendered link.
|
|
125
|
+
|
|
123
126
|
An ask is read on a phone by someone who has not read the code. Open with one or two plain
|
|
124
127
|
sentences: what needs deciding and why it matters now. Each option is a button with a label and
|
|
125
128
|
one sentence saying what happens if it is chosen; never enumerate choices in prose. Put the
|
|
@@ -341,6 +344,24 @@ with your text. Never do that: the spec is edited in place with `dispatch_doc_ed
|
|
|
341
344
|
artifact by the slug shown in the upload result or by its filename, and a project document by its artifact id, slug, or filename; the
|
|
342
345
|
slug also arrives on `artifact.created` events.
|
|
343
346
|
|
|
347
|
+
## Structure over stream
|
|
348
|
+
|
|
349
|
+
Dispatch is a structured workspace, never a message stream (Sami, 2026-09-13, verbatim: "strange
|
|
350
|
+
to me that agents keep trying to use dispatch as a giant stream of messages instead of
|
|
351
|
+
high-signal, structured conversation"). The structure IS the product:
|
|
352
|
+
|
|
353
|
+
- **One issue per piece of work.** A new deliverable — an email to send, a document to review, a
|
|
354
|
+
decision with its own lifecycle — gets its own issue with the content as the issue's document
|
|
355
|
+
(spec artifact), not a message pile on an existing issue. If you are about to post a message
|
|
356
|
+
carrying a draft, a spec, or anything over a couple of paragraphs, stop: that is an issue with a
|
|
357
|
+
document, or an artifact on the issue it belongs to.
|
|
358
|
+
- **Content lives in documents; decisions live in asks; messages only announce.** A draft the
|
|
359
|
+
human must read goes in a document artifact the dashboard renders with versions and margins; the
|
|
360
|
+
ask that needs their word references it (`ref`, or the `dispatch://` link inline) instead of
|
|
361
|
+
restating it. A message never carries a body a human has to scroll.
|
|
362
|
+
- **Never split one deliverable across a message + an ask that points at it.** Ask the question
|
|
363
|
+
with the document reference in the question text; the reader lands on the content in one click.
|
|
364
|
+
|
|
344
365
|
## Messages
|
|
345
366
|
|
|
346
367
|
Dispatch is a high-signal record for humans, not a log of what you are doing. A message is a reply to a human's message, or a
|