@sjawhar/opencode-legion-envoy 12.0.1 → 12.1.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/dispatch.js +201 -79
- package/dist/src/server.js +48 -24
- package/package.json +1 -1
- package/skills/dispatch/SKILL.md +3 -3
- package/skills/dispatch-writing-specs/SKILL.md +1 -1
package/dist/dispatch.js
CHANGED
|
@@ -14688,7 +14688,7 @@ var dispatchToolSpecs = [
|
|
|
14688
14688
|
{
|
|
14689
14689
|
name: "dispatch_doc_read",
|
|
14690
14690
|
example: { issue: "DSP-1" },
|
|
14691
|
-
description: "Read a live document or a named document version, or the text of an uploaded file at its latest or named version. " + "Do not use it for issue status, asks, or events; " + "use dispatch read instead. Supply ref, issue, or project plus artifact; issue plus an omitted artifact reads the primary document. " + "A live read returns its document token for an optional dispatch doc-edit precondition. With blocks it lists the live document's blocks instead of its markdown, in document order: each block's id, type, token, anchored comments and asks, and opening words, and under a table each row by the index delete_row takes and each cell by its row and the column delete_column takes; block ids, block tokens and those indexes are what dispatch doc-edit takes. " + "After the markdown it prints every open thread anchored on the document in full: an open ask with its id and question and each reply's author, time and text, and a comment thread with its root's id, author, quote and text and each reply's author, time and text, in order; a resolved thread, and an ask that is answered or resolved, is not printed. " + "A picture (an uploaded PNG, JPEG, GIF or WebP of at most 3,750,000 bytes, 5 MB once base64-encoded) comes back as an image you see, with its name, type, size and version, every time you ask, including one a read already showed this session; " + "any other file that is not UTF-8 text is described, with the route that serves its bytes. " + OWNER_REFERENCE,
|
|
14691
|
+
description: "Read a live document or a named document version, or the text of an uploaded file at its latest or named version. " + "Do not use it for issue status, asks, or events; " + "use dispatch read instead. Supply ref, issue, or project plus artifact; issue plus an omitted artifact reads the primary document. " + "A live read returns its document token for an optional dispatch doc-edit precondition. With blocks it lists the live document's blocks instead of its markdown, in document order: each block's id, type, token, anchored comments and asks, and opening words, and under a table each row by the index delete_row takes and each cell by its row and the column delete_column takes; block ids, block tokens and those indexes are what dispatch doc-edit takes. " + "After the markdown it prints every open thread anchored on the document in full: an open ask with its id and question and each reply's author, time and text, and a comment thread with its root's id, author, time, quote and text and each reply's author, time and text, in order; a resolved thread, and an ask that is answered or resolved, is not printed. " + "A picture (an uploaded PNG, JPEG, GIF or WebP of at most 3,750,000 bytes, 5 MB once base64-encoded) comes back as an image you see, with its name, type, size and version, every time you ask, including one a read already showed this session; " + "any other file that is not UTF-8 text is described, with the route that serves its bytes. " + OWNER_REFERENCE,
|
|
14692
14692
|
arguments: (z) => ({
|
|
14693
14693
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
14694
14694
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
@@ -14848,6 +14848,13 @@ var dispatchToolSpecs = [
|
|
|
14848
14848
|
}),
|
|
14849
14849
|
strict: true
|
|
14850
14850
|
},
|
|
14851
|
+
{
|
|
14852
|
+
name: "dispatch_answers",
|
|
14853
|
+
example: {},
|
|
14854
|
+
description: "List every answer and comment a person wrote to this session in the last 72 hours, newest first, each in full: " + "answers to its asks, replies on its asks and in its comment threads, comments on its documents and on issues routed to it, " + "mentions of it, messages to it and replies to its messages. Each item names its time, its writer, what it is, its " + "dispatch:// reference and its issue or document, an answer's question and choices, and the words, never cut. " + "Run it when the list of people's answers on a model request says it left older items out. " + "It reads this session's id from CLAUDE_CODE_SESSION_ID under Claude Code and DISPATCH_SESSION_ID elsewhere, so a subagent reads its parent's.",
|
|
14855
|
+
arguments: () => ({}),
|
|
14856
|
+
strict: true
|
|
14857
|
+
},
|
|
14851
14858
|
{
|
|
14852
14859
|
name: "dispatch_whoami",
|
|
14853
14860
|
example: {},
|
|
@@ -15961,6 +15968,12 @@ class DispatchClient {
|
|
|
15961
15968
|
async openAsksForProject(project) {
|
|
15962
15969
|
return this.#json("GET", ["api", "v1", "asks", "open"], undefined, { project });
|
|
15963
15970
|
}
|
|
15971
|
+
async sessionAnswers(sessionID, options = {}) {
|
|
15972
|
+
return this.#json("GET", ["api", "v1", "agents", sessionID, "answers"], undefined, {
|
|
15973
|
+
...options.since === undefined ? {} : { since: options.since },
|
|
15974
|
+
...options.maxChars === undefined ? {} : { max_chars: options.maxChars }
|
|
15975
|
+
}, options.signal);
|
|
15976
|
+
}
|
|
15964
15977
|
async listAgents() {
|
|
15965
15978
|
return this.#json("GET", ["api", "v1", "agents"]);
|
|
15966
15979
|
}
|
|
@@ -16189,12 +16202,12 @@ class DispatchClient {
|
|
|
16189
16202
|
throw error;
|
|
16190
16203
|
}
|
|
16191
16204
|
}
|
|
16192
|
-
async#json(method, path, body, query) {
|
|
16193
|
-
return (await this.#jsonAnswer(method, path, body, query)).payload;
|
|
16205
|
+
async#json(method, path, body, query, signal) {
|
|
16206
|
+
return (await this.#jsonAnswer(method, path, body, query, signal)).payload;
|
|
16194
16207
|
}
|
|
16195
|
-
async#jsonAnswer(method, path, body, query) {
|
|
16208
|
+
async#jsonAnswer(method, path, body, query, signal) {
|
|
16196
16209
|
const url = this.#url(path, query);
|
|
16197
|
-
const response = await this.#send(method, url, body === undefined ? undefined : { content: JSON.stringify(body), type: "application/json" });
|
|
16210
|
+
const response = await this.#send(method, url, body === undefined ? undefined : { content: JSON.stringify(body), type: "application/json" }, undefined, signal);
|
|
16198
16211
|
return { status: response.status, payload: await this.#response(method, url, response) };
|
|
16199
16212
|
}
|
|
16200
16213
|
async#upload(path, input) {
|
|
@@ -16217,7 +16230,7 @@ class DispatchClient {
|
|
|
16217
16230
|
const response = await this.#send(method, url, { content, type });
|
|
16218
16231
|
return this.#response(method, url, response);
|
|
16219
16232
|
}
|
|
16220
|
-
async#send(method, url, body, accept = "application/json") {
|
|
16233
|
+
async#send(method, url, body, accept = "application/json", signal) {
|
|
16221
16234
|
const headers = {
|
|
16222
16235
|
Accept: accept,
|
|
16223
16236
|
Authorization: `Bearer ${this.token}`
|
|
@@ -16228,7 +16241,7 @@ class DispatchClient {
|
|
|
16228
16241
|
return this.fetchImpl(url, {
|
|
16229
16242
|
method,
|
|
16230
16243
|
headers,
|
|
16231
|
-
signal: this.#signal,
|
|
16244
|
+
signal: signal === undefined ? this.#signal : AbortSignal.any([this.#signal, signal]),
|
|
16232
16245
|
...body === undefined ? {} : { body: body.content }
|
|
16233
16246
|
});
|
|
16234
16247
|
}
|
|
@@ -16414,6 +16427,9 @@ class DispatchClient {
|
|
|
16414
16427
|
function documentLabel(project, slug) {
|
|
16415
16428
|
return `${project}/${slug}`;
|
|
16416
16429
|
}
|
|
16430
|
+
function ownerTitle(owner) {
|
|
16431
|
+
return "issue" in owner ? `${owner.issue.key}: ${owner.issue.title}` : `${owner.document.project} / ${owner.document.name}`;
|
|
16432
|
+
}
|
|
16417
16433
|
function issueTopic(key) {
|
|
16418
16434
|
return { label: key, topic: dispatchIssueSubject(key, ">") };
|
|
16419
16435
|
}
|
|
@@ -17227,6 +17243,99 @@ function notMadeLine(reason, context, searchQuery) {
|
|
|
17227
17243
|
return `No related-item check was made: ${reason}. Search before you rely on this being new: dispatch search --project ${context.project}${query}`;
|
|
17228
17244
|
}
|
|
17229
17245
|
|
|
17246
|
+
// ../envoy-client/src/dispatch-session-directory.ts
|
|
17247
|
+
import { randomUUID } from "crypto";
|
|
17248
|
+
import { renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
17249
|
+
import { homedir } from "os";
|
|
17250
|
+
import { join as join2 } from "path";
|
|
17251
|
+
var SESSION_ID = /^[A-Za-z0-9._-]{1,128}$/;
|
|
17252
|
+
var PRIVATE_FILE = 384;
|
|
17253
|
+
function sessionStateRoot(env) {
|
|
17254
|
+
const stateDir = env["DISPATCH_STATE_DIR"];
|
|
17255
|
+
if (stateDir)
|
|
17256
|
+
return stateDir;
|
|
17257
|
+
return join2(env["XDG_STATE_HOME"] || join2(homedir(), ".local", "state"), "dispatch");
|
|
17258
|
+
}
|
|
17259
|
+
function sessionDirectory(env, sessionId) {
|
|
17260
|
+
if (!SESSION_ID.test(sessionId) || sessionId === "." || sessionId === "..") {
|
|
17261
|
+
throw new Error(`dispatch: the session id ${JSON.stringify(sessionId)} is not 1-128 letters, digits, ".", "_" or "-"`);
|
|
17262
|
+
}
|
|
17263
|
+
return join2(sessionStateRoot(env), "sessions", sessionId);
|
|
17264
|
+
}
|
|
17265
|
+
function writeAtomically(path, text) {
|
|
17266
|
+
const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
17267
|
+
try {
|
|
17268
|
+
writeFileSync2(temporary, text, { mode: PRIVATE_FILE });
|
|
17269
|
+
renameSync2(temporary, path);
|
|
17270
|
+
} catch (error) {
|
|
17271
|
+
rmSync2(temporary, { force: true });
|
|
17272
|
+
throw error;
|
|
17273
|
+
}
|
|
17274
|
+
}
|
|
17275
|
+
|
|
17276
|
+
// ../envoy-client/src/session-answers.ts
|
|
17277
|
+
var SESSION_ANSWERS_WINDOW_MS = 72 * 3600000;
|
|
17278
|
+
var SESSION_ANSWERS_MAX_CHARS = 8000;
|
|
17279
|
+
var NO_ROUTE_BACKOFF_MS = 5 * 60000;
|
|
17280
|
+
var QUESTION_MAX = 200;
|
|
17281
|
+
var RELATION_PHRASES = {
|
|
17282
|
+
answered_your_ask: "answered your ask",
|
|
17283
|
+
replied_on_your_ask: "replied on your ask",
|
|
17284
|
+
replied_in_a_comment_thread: "replied in a comment thread",
|
|
17285
|
+
commented_on_a_document: "commented on a document",
|
|
17286
|
+
commented_on_an_issue: "commented on an issue",
|
|
17287
|
+
mentioned_you: "mentioned you",
|
|
17288
|
+
sent_you_a_message: "sent you a message",
|
|
17289
|
+
replied_to_your_message: "replied to your message"
|
|
17290
|
+
};
|
|
17291
|
+
function isNoAnswersRoute(error) {
|
|
17292
|
+
return error instanceof DispatchServiceError && error.status === 404;
|
|
17293
|
+
}
|
|
17294
|
+
function renderSessionAnswerItem(item) {
|
|
17295
|
+
const relation = `${RELATION_PHRASES[item.relation]}${item.replaced === true ? " (replaces an earlier answer)" : ""}`;
|
|
17296
|
+
const head = [item.at, `user ${item.author.id}`, relation];
|
|
17297
|
+
if (item.edited_at !== undefined)
|
|
17298
|
+
head.push(`edited ${item.edited_at}`);
|
|
17299
|
+
const owner = item.owner === null ? undefined : ownerTitle(item.owner);
|
|
17300
|
+
const where = item.ref === null ? `Direct message ${item.id}, on no issue` : owner === undefined ? item.ref : `${item.ref} \xB7 ${owner}`;
|
|
17301
|
+
const lines = [head.join(" \xB7 "), where];
|
|
17302
|
+
if (item.ask !== undefined) {
|
|
17303
|
+
const question = item.ask.question.replace(/\s+/g, " ").trim();
|
|
17304
|
+
lines.push(`Question: ${question.length > QUESTION_MAX ? `${question.slice(0, QUESTION_MAX)}\u2026` : question}`);
|
|
17305
|
+
}
|
|
17306
|
+
if (item.kind === "answer") {
|
|
17307
|
+
const selected = item.selected ?? [];
|
|
17308
|
+
lines.push(`Selected: ${selected.length === 0 ? "none" : selected.join(", ")}`);
|
|
17309
|
+
}
|
|
17310
|
+
if (item.text !== "")
|
|
17311
|
+
lines.push(item.text);
|
|
17312
|
+
return lines.join(`
|
|
17313
|
+
`);
|
|
17314
|
+
}
|
|
17315
|
+
function withinWindow(items, now) {
|
|
17316
|
+
const start = now - SESSION_ANSWERS_WINDOW_MS;
|
|
17317
|
+
return items.filter((item) => Date.parse(item.at) >= start);
|
|
17318
|
+
}
|
|
17319
|
+
var MAX_CHARS_TEXT = SESSION_ANSWERS_MAX_CHARS.toLocaleString("en-US");
|
|
17320
|
+
function renderSessionAnswersList(items, options) {
|
|
17321
|
+
const shown = withinWindow(items, Number(options.now));
|
|
17322
|
+
if (shown.length === 0)
|
|
17323
|
+
return "No person wrote to this session in the last 72 hours.";
|
|
17324
|
+
return [
|
|
17325
|
+
`${shown.length} ${shown.length === 1 ? "item" : "items"} people wrote to this session in the last 72 hours, newest first, word for word:`,
|
|
17326
|
+
...shown.map(renderSessionAnswerItem)
|
|
17327
|
+
].join(`
|
|
17328
|
+
|
|
17329
|
+
`);
|
|
17330
|
+
}
|
|
17331
|
+
var STATE_KEY = Symbol.for("legion.pi-envoy.session-answers.v2");
|
|
17332
|
+
var processGlobal = globalThis;
|
|
17333
|
+
var state = processGlobal[STATE_KEY] ?? {
|
|
17334
|
+
sessions: new Map,
|
|
17335
|
+
noRouteUntil: new Map
|
|
17336
|
+
};
|
|
17337
|
+
processGlobal[STATE_KEY] = state;
|
|
17338
|
+
|
|
17230
17339
|
// ../envoy-client/src/dispatch-execute.ts
|
|
17231
17340
|
function documentTopic(artifact) {
|
|
17232
17341
|
return documentTopicOf(artifact.project, artifact.slug);
|
|
@@ -17349,6 +17458,7 @@ var issueFreeTools = {
|
|
|
17349
17458
|
dispatch_project: true,
|
|
17350
17459
|
dispatch_projects: true,
|
|
17351
17460
|
dispatch_open_asks: true,
|
|
17461
|
+
dispatch_answers: true,
|
|
17352
17462
|
dispatch_whoami: true,
|
|
17353
17463
|
dispatch_architecture_sync: true
|
|
17354
17464
|
};
|
|
@@ -18461,12 +18571,9 @@ function openAskAge(ageSeconds) {
|
|
|
18461
18571
|
const days = Math.floor(hours / 24);
|
|
18462
18572
|
return `${days}d${hours % 24 === 0 ? "" : ` ${hours % 24}h`}`;
|
|
18463
18573
|
}
|
|
18464
|
-
function openAskOwner(ask) {
|
|
18465
|
-
return "issue" in ask.owner ? `${ask.owner.issue.key}: ${ask.owner.issue.title}` : `${ask.owner.document.project} / ${ask.owner.document.name}`;
|
|
18466
|
-
}
|
|
18467
18574
|
function openAskLine(ask, baseUrl) {
|
|
18468
18575
|
const priority = ask.priority === null ? "" : `P${ask.priority} \xB7 `;
|
|
18469
|
-
return `- ${openAskAge(ask.age_seconds)} \xB7 ${priority}${
|
|
18576
|
+
return `- ${openAskAge(ask.age_seconds)} \xB7 ${priority}${ownerTitle(ask.owner)} \xB7 ${ask.question} \xB7 ${new URL(ask.ref, baseUrl).toString()}`;
|
|
18470
18577
|
}
|
|
18471
18578
|
function formatOpenAsksSummary(response, baseUrl) {
|
|
18472
18579
|
const scope = "active open asks you authored on open issues and project documents";
|
|
@@ -18882,6 +18989,24 @@ async function executeDispatchTool(input) {
|
|
|
18882
18989
|
const response = await client.openAsks(sessionId);
|
|
18883
18990
|
return { text: formatOpenAsksSummary(response, linkBase), details: { ...response } };
|
|
18884
18991
|
}
|
|
18992
|
+
if (input.tool === "dispatch_answers") {
|
|
18993
|
+
const sessionId = input.sessionId?.trim();
|
|
18994
|
+
if (!sessionId)
|
|
18995
|
+
throw new Error("host session id is required for dispatch answers");
|
|
18996
|
+
let response;
|
|
18997
|
+
try {
|
|
18998
|
+
response = await dispatchClient().sessionAnswers(sessionId);
|
|
18999
|
+
} catch (error) {
|
|
19000
|
+
if (isNoAnswersRoute(error)) {
|
|
19001
|
+
throw new Error(`This Dispatch (${configUrl}) has no answers route yet: its build is older than the answers route.`);
|
|
19002
|
+
}
|
|
19003
|
+
throw error;
|
|
19004
|
+
}
|
|
19005
|
+
return {
|
|
19006
|
+
text: renderSessionAnswersList(response.items, { now: Date.now() }),
|
|
19007
|
+
details: { session: sessionId, since: response.since, total: response.total }
|
|
19008
|
+
};
|
|
19009
|
+
}
|
|
18885
19010
|
if (input.tool === "dispatch_whoami") {
|
|
18886
19011
|
const sessionId = input.sessionId?.trim();
|
|
18887
19012
|
if (!sessionId)
|
|
@@ -19939,16 +20064,13 @@ import {
|
|
|
19939
20064
|
readdirSync as readdirSync2,
|
|
19940
20065
|
readFileSync as readFileSync2,
|
|
19941
20066
|
readSync,
|
|
19942
|
-
|
|
19943
|
-
rmSync as rmSync2,
|
|
20067
|
+
rmSync as rmSync3,
|
|
19944
20068
|
statSync as statSync2,
|
|
19945
|
-
writeFileSync as
|
|
20069
|
+
writeFileSync as writeFileSync3
|
|
19946
20070
|
} from "fs";
|
|
19947
|
-
import {
|
|
19948
|
-
import { join as join2 } from "path";
|
|
19949
|
-
var SESSION_ID = /^[A-Za-z0-9._-]{1,128}$/;
|
|
20071
|
+
import { join as join3 } from "path";
|
|
19950
20072
|
var PRIVATE_DIR = 448;
|
|
19951
|
-
var
|
|
20073
|
+
var PRIVATE_FILE2 = 384;
|
|
19952
20074
|
var IDLE_SESSION_MS = 14 * 86400000;
|
|
19953
20075
|
var PRUNE_INTERVAL_MS = 86400000;
|
|
19954
20076
|
var PRUNED_STAMP = ".pruned";
|
|
@@ -19958,18 +20080,6 @@ var PICTURE_EXTENSIONS = {
|
|
|
19958
20080
|
"image/gif": "gif",
|
|
19959
20081
|
"image/webp": "webp"
|
|
19960
20082
|
};
|
|
19961
|
-
function sessionStateRoot(env) {
|
|
19962
|
-
const stateDir = env["DISPATCH_STATE_DIR"];
|
|
19963
|
-
if (stateDir)
|
|
19964
|
-
return stateDir;
|
|
19965
|
-
return join2(env["XDG_STATE_HOME"] || join2(homedir(), ".local", "state"), "dispatch");
|
|
19966
|
-
}
|
|
19967
|
-
function sessionDirectory(env, sessionId) {
|
|
19968
|
-
if (!SESSION_ID.test(sessionId) || sessionId === "." || sessionId === "..") {
|
|
19969
|
-
throw new Error(`dispatch: the session id ${JSON.stringify(sessionId)} is not 1-128 letters, digits, ".", "_" or "-"`);
|
|
19970
|
-
}
|
|
19971
|
-
return join2(sessionStateRoot(env), "sessions", sessionId);
|
|
19972
|
-
}
|
|
19973
20083
|
function ensureDirectory(dir) {
|
|
19974
20084
|
mkdirSync2(dir, { recursive: true, mode: PRIVATE_DIR });
|
|
19975
20085
|
}
|
|
@@ -19982,16 +20092,11 @@ function readIfPresent(path) {
|
|
|
19982
20092
|
throw error;
|
|
19983
20093
|
}
|
|
19984
20094
|
}
|
|
19985
|
-
function writeAtomically(path, text) {
|
|
19986
|
-
const temporary = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
19987
|
-
writeFileSync2(temporary, text, { mode: PRIVATE_FILE });
|
|
19988
|
-
renameSync2(temporary, path);
|
|
19989
|
-
}
|
|
19990
20095
|
function stringList(value) {
|
|
19991
20096
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
19992
20097
|
}
|
|
19993
20098
|
function loadSessionMemory(dir, sessionId) {
|
|
19994
|
-
const path =
|
|
20099
|
+
const path = join3(dir, "state.json");
|
|
19995
20100
|
const text = readIfPresent(path);
|
|
19996
20101
|
if (text === undefined)
|
|
19997
20102
|
return new Set;
|
|
@@ -20015,7 +20120,7 @@ function writeMemory(dir, sessionId, follows) {
|
|
|
20015
20120
|
pictures: [...shownPictures(sessionId)],
|
|
20016
20121
|
follows: [...follows]
|
|
20017
20122
|
};
|
|
20018
|
-
writeAtomically(
|
|
20123
|
+
writeAtomically(join3(dir, "state.json"), `${JSON.stringify(state)}
|
|
20019
20124
|
`);
|
|
20020
20125
|
}
|
|
20021
20126
|
function appendResult(dir, entry) {
|
|
@@ -20024,8 +20129,8 @@ function appendResult(dir, entry) {
|
|
|
20024
20129
|
}
|
|
20025
20130
|
function writeResult(dir, entry) {
|
|
20026
20131
|
const line = { at: new Date().toISOString(), ...entry };
|
|
20027
|
-
appendFileSync(
|
|
20028
|
-
`, { mode:
|
|
20132
|
+
appendFileSync(join3(dir, "results.jsonl"), `${JSON.stringify(line)}
|
|
20133
|
+
`, { mode: PRIVATE_FILE2 });
|
|
20029
20134
|
}
|
|
20030
20135
|
function recordOutcome(dir, sessionId, follows, entry) {
|
|
20031
20136
|
ensureDirectory(dir);
|
|
@@ -20035,11 +20140,11 @@ function recordOutcome(dir, sessionId, follows, entry) {
|
|
|
20035
20140
|
function writePicture(dir, image) {
|
|
20036
20141
|
const bytes = Buffer.from(image.data, "base64");
|
|
20037
20142
|
const name = createHash2("sha256").update(bytes).digest("hex").slice(0, 16);
|
|
20038
|
-
const pictures =
|
|
20039
|
-
const path =
|
|
20143
|
+
const pictures = join3(dir, "pictures");
|
|
20144
|
+
const path = join3(pictures, `${name}.${PICTURE_EXTENSIONS[image.mimeType]}`);
|
|
20040
20145
|
ensureDirectory(pictures);
|
|
20041
20146
|
try {
|
|
20042
|
-
|
|
20147
|
+
writeFileSync3(path, bytes, { mode: PRIVATE_FILE2, flag: "wx" });
|
|
20043
20148
|
} catch (error) {
|
|
20044
20149
|
if (!hasErrnoCode(error, "EEXIST"))
|
|
20045
20150
|
throw error;
|
|
@@ -20047,17 +20152,17 @@ function writePicture(dir, image) {
|
|
|
20047
20152
|
return { path, bytes: bytes.length };
|
|
20048
20153
|
}
|
|
20049
20154
|
function writeLongOutput(dir, text) {
|
|
20050
|
-
const out =
|
|
20155
|
+
const out = join3(dir, "out");
|
|
20051
20156
|
ensureDirectory(out);
|
|
20052
|
-
const path =
|
|
20053
|
-
|
|
20157
|
+
const path = join3(out, `${new Date().toISOString().replaceAll(":", "-")}-${process.pid}.md`);
|
|
20158
|
+
writeFileSync3(path, text, { mode: PRIVATE_FILE2 });
|
|
20054
20159
|
return path;
|
|
20055
20160
|
}
|
|
20056
20161
|
function readSessionTitle(dir) {
|
|
20057
|
-
return readIfPresent(
|
|
20162
|
+
return readIfPresent(join3(dir, "title")) || undefined;
|
|
20058
20163
|
}
|
|
20059
20164
|
function pruneSessions(root, now) {
|
|
20060
|
-
const stamp =
|
|
20165
|
+
const stamp = join3(root, PRUNED_STAMP);
|
|
20061
20166
|
try {
|
|
20062
20167
|
if (now - statSync2(stamp).mtimeMs < PRUNE_INTERVAL_MS)
|
|
20063
20168
|
return;
|
|
@@ -20065,7 +20170,7 @@ function pruneSessions(root, now) {
|
|
|
20065
20170
|
if (!hasErrnoCode(error, "ENOENT"))
|
|
20066
20171
|
throw error;
|
|
20067
20172
|
}
|
|
20068
|
-
const sessions =
|
|
20173
|
+
const sessions = join3(root, "sessions");
|
|
20069
20174
|
let entries = [];
|
|
20070
20175
|
try {
|
|
20071
20176
|
entries = readdirSync2(sessions, { withFileTypes: true });
|
|
@@ -20076,10 +20181,10 @@ function pruneSessions(root, now) {
|
|
|
20076
20181
|
for (const entry of entries) {
|
|
20077
20182
|
if (!entry.isDirectory())
|
|
20078
20183
|
continue;
|
|
20079
|
-
const dir =
|
|
20184
|
+
const dir = join3(sessions, entry.name);
|
|
20080
20185
|
try {
|
|
20081
20186
|
if (now - statSync2(dir).mtimeMs > IDLE_SESSION_MS && askgateState(dir).kind === "off") {
|
|
20082
|
-
|
|
20187
|
+
rmSync3(dir, { recursive: true, force: true });
|
|
20083
20188
|
}
|
|
20084
20189
|
} catch (error) {
|
|
20085
20190
|
if (!hasErrnoCode(error, "ENOENT"))
|
|
@@ -20088,7 +20193,7 @@ function pruneSessions(root, now) {
|
|
|
20088
20193
|
}
|
|
20089
20194
|
sweepStores(root, now);
|
|
20090
20195
|
ensureDirectory(root);
|
|
20091
|
-
|
|
20196
|
+
writeFileSync3(stamp, "", { mode: PRIVATE_FILE2 });
|
|
20092
20197
|
}
|
|
20093
20198
|
|
|
20094
20199
|
// ../envoy-client/src/envoy-config.ts
|
|
@@ -20101,7 +20206,7 @@ import {
|
|
|
20101
20206
|
openSync as openSync3,
|
|
20102
20207
|
readFileSync as readFileSync4,
|
|
20103
20208
|
readlinkSync,
|
|
20104
|
-
rmSync as
|
|
20209
|
+
rmSync as rmSync4,
|
|
20105
20210
|
statSync as statSync3
|
|
20106
20211
|
} from "fs";
|
|
20107
20212
|
import { homedir as homedir2, tmpdir } from "os";
|
|
@@ -20367,9 +20472,9 @@ function parseFile(filePath, schema, checkout) {
|
|
|
20367
20472
|
}
|
|
20368
20473
|
var FAILURE_KEPT_MS = 60000;
|
|
20369
20474
|
var COMMAND_OUTCOMES = Symbol.for("legion.envoy-config.commands.v1");
|
|
20370
|
-
var
|
|
20371
|
-
var commandOutcomes =
|
|
20372
|
-
|
|
20475
|
+
var processGlobal2 = globalThis;
|
|
20476
|
+
var commandOutcomes = processGlobal2[COMMAND_OUTCOMES] ?? new Map;
|
|
20477
|
+
processGlobal2[COMMAND_OUTCOMES] = commandOutcomes;
|
|
20373
20478
|
function runCommand(command, env, cwd) {
|
|
20374
20479
|
const childEnv = {};
|
|
20375
20480
|
for (const [name, variable] of Object.entries(env)) {
|
|
@@ -20423,10 +20528,10 @@ function runCommand(command, env, cwd) {
|
|
|
20423
20528
|
}
|
|
20424
20529
|
return { value };
|
|
20425
20530
|
} finally {
|
|
20426
|
-
|
|
20531
|
+
rmSync4(directory, { recursive: true, force: true });
|
|
20427
20532
|
}
|
|
20428
20533
|
}
|
|
20429
|
-
function fileValue(value, field, filePath, env) {
|
|
20534
|
+
function fileValue(value, field, filePath, env, commands = "run") {
|
|
20430
20535
|
if (!value.startsWith("!")) {
|
|
20431
20536
|
if (!sendable(value)) {
|
|
20432
20537
|
throw new Error(`${filePath}: ${field} holds whitespace or a control character`);
|
|
@@ -20437,7 +20542,11 @@ function fileValue(value, field, filePath, env) {
|
|
|
20437
20542
|
const cwd = path.dirname(path.resolve(filePath));
|
|
20438
20543
|
const key = JSON.stringify([command, filePath, cwd]);
|
|
20439
20544
|
let outcome = commandOutcomes.get(key);
|
|
20440
|
-
if (
|
|
20545
|
+
if (commands === "kept") {
|
|
20546
|
+
if (outcome === undefined) {
|
|
20547
|
+
throw new Error(`${filePath}: ${field}'s command has not run in this process yet, and this read does not run it`);
|
|
20548
|
+
}
|
|
20549
|
+
} else if (outcome === undefined || "failure" in outcome && !outcome.timedOut && Date.now() - outcome.at >= FAILURE_KEPT_MS) {
|
|
20441
20550
|
outcome = { ...runCommand(command, env, cwd), at: Date.now() };
|
|
20442
20551
|
commandOutcomes.set(key, outcome);
|
|
20443
20552
|
}
|
|
@@ -20547,7 +20656,7 @@ function bearerSource(env, section, written) {
|
|
|
20547
20656
|
const bearer = written();
|
|
20548
20657
|
return bearer === undefined ? { kind: "none" } : { kind: "file", ...bearer };
|
|
20549
20658
|
}
|
|
20550
|
-
function fileBearer(bearer, url, section, env) {
|
|
20659
|
+
function fileBearer(bearer, url, section, env, commands = "run") {
|
|
20551
20660
|
const named = httpUrl2(url)?.origin;
|
|
20552
20661
|
const paired = httpUrl2(bearer.server)?.origin;
|
|
20553
20662
|
if (named !== paired) {
|
|
@@ -20559,7 +20668,7 @@ function fileBearer(bearer, url, section, env) {
|
|
|
20559
20668
|
if (bearer.token === "") {
|
|
20560
20669
|
throw new Error(`${bearer.field} in ${bearer.path} must be a non-empty bearer token`);
|
|
20561
20670
|
}
|
|
20562
|
-
return fileValue(bearer.token, bearer.field, bearer.path, env);
|
|
20671
|
+
return fileValue(bearer.token, bearer.field, bearer.path, env, commands);
|
|
20563
20672
|
}
|
|
20564
20673
|
|
|
20565
20674
|
// ../envoy-client/src/dispatch-config.ts
|
|
@@ -20594,7 +20703,30 @@ function writtenToken(config, checkout) {
|
|
|
20594
20703
|
checkout: false
|
|
20595
20704
|
};
|
|
20596
20705
|
}
|
|
20597
|
-
function dispatchSettings(env, config) {
|
|
20706
|
+
function dispatchSettings(env, config, options = {}) {
|
|
20707
|
+
const target = dispatchTarget(env, config);
|
|
20708
|
+
if (!target.enabled)
|
|
20709
|
+
return target;
|
|
20710
|
+
const { url, source } = target;
|
|
20711
|
+
let token;
|
|
20712
|
+
try {
|
|
20713
|
+
token = source.kind === "variable" ? source.token : fileBearer(source, url, "DISPATCH", env, options.commands);
|
|
20714
|
+
} catch (error) {
|
|
20715
|
+
return offWithError(messageFor(error));
|
|
20716
|
+
}
|
|
20717
|
+
const publicUrl = nonEmpty(env.DISPATCH_PUBLIC_URL);
|
|
20718
|
+
const publicParsed = publicUrl === undefined ? undefined : httpUrl2(publicUrl);
|
|
20719
|
+
if (publicUrl !== undefined && (publicParsed === undefined || publicParsed.port === "0")) {
|
|
20720
|
+
return offWithError("DISPATCH_PUBLIC_URL must be an http or https URL");
|
|
20721
|
+
}
|
|
20722
|
+
return {
|
|
20723
|
+
enabled: true,
|
|
20724
|
+
url,
|
|
20725
|
+
token,
|
|
20726
|
+
...publicUrl === undefined ? {} : { publicUrl: publicUrl.replace(/\/+$/, "") }
|
|
20727
|
+
};
|
|
20728
|
+
}
|
|
20729
|
+
function dispatchTarget(env, config) {
|
|
20598
20730
|
if (config.kind === "failed")
|
|
20599
20731
|
return offWithError(config.error);
|
|
20600
20732
|
const explicitUrl = nonEmpty(env.DISPATCH_URL);
|
|
@@ -20617,27 +20749,17 @@ function dispatchSettings(env, config) {
|
|
|
20617
20749
|
if (explicitUrl !== undefined && httpUrl2(explicitUrl) === undefined) {
|
|
20618
20750
|
return offWithError("DISPATCH_URL must be an http or https URL");
|
|
20619
20751
|
}
|
|
20752
|
+
let source;
|
|
20620
20753
|
try {
|
|
20621
|
-
|
|
20622
|
-
if (source.kind === "none") {
|
|
20623
|
-
return offWithError(explicitUrl === undefined ? `${config.path}: dispatch is enabled and no token is: set dispatch.token, or DISPATCH_TOKEN or DISPATCH_TOKEN_FILE` : `DISPATCH_URL is set and no token is: set DISPATCH_TOKEN or DISPATCH_TOKEN_FILE, or dispatch.token in ${config.path}`);
|
|
20624
|
-
}
|
|
20625
|
-
const url = (explicitUrl ?? (source.kind === "file" ? source.server : own.serverUrl ?? DEFAULT_SERVER_URL)).replace(/\/+$/, "");
|
|
20626
|
-
const token = source.kind === "variable" ? source.token : fileBearer(source, url, "DISPATCH", env);
|
|
20627
|
-
const publicUrl = nonEmpty(env.DISPATCH_PUBLIC_URL);
|
|
20628
|
-
const publicParsed = publicUrl === undefined ? undefined : httpUrl2(publicUrl);
|
|
20629
|
-
if (publicUrl !== undefined && (publicParsed === undefined || publicParsed.port === "0")) {
|
|
20630
|
-
return offWithError("DISPATCH_PUBLIC_URL must be an http or https URL");
|
|
20631
|
-
}
|
|
20632
|
-
return {
|
|
20633
|
-
enabled: true,
|
|
20634
|
-
url,
|
|
20635
|
-
token,
|
|
20636
|
-
...publicUrl === undefined ? {} : { publicUrl: publicUrl.replace(/\/+$/, "") }
|
|
20637
|
-
};
|
|
20754
|
+
source = checkout !== null && checkoutServer !== undefined ? checkoutPair(checkout, checkoutServer) : bearerSource(env, "DISPATCH", () => writtenToken(config, checkout));
|
|
20638
20755
|
} catch (error) {
|
|
20639
20756
|
return offWithError(messageFor(error));
|
|
20640
20757
|
}
|
|
20758
|
+
if (source.kind === "none") {
|
|
20759
|
+
return offWithError(explicitUrl === undefined ? `${config.path}: dispatch is enabled and no token is: set dispatch.token, or DISPATCH_TOKEN or DISPATCH_TOKEN_FILE` : `DISPATCH_URL is set and no token is: set DISPATCH_TOKEN or DISPATCH_TOKEN_FILE, or dispatch.token in ${config.path}`);
|
|
20760
|
+
}
|
|
20761
|
+
const url = (explicitUrl ?? (source.kind === "file" ? source.server : own.serverUrl ?? DEFAULT_SERVER_URL)).replace(/\/+$/, "");
|
|
20762
|
+
return { enabled: true, url, source };
|
|
20641
20763
|
}
|
|
20642
20764
|
|
|
20643
20765
|
// ../envoy-client/src/envoy-settings.ts
|
|
@@ -20774,7 +20896,7 @@ function sessionDispatchSettings(file, env, options) {
|
|
|
20774
20896
|
return { dispatch: offWithError(file.error), warnings: [], missing: null };
|
|
20775
20897
|
const config = readEnvoyConfig(file.path, env, options);
|
|
20776
20898
|
return {
|
|
20777
|
-
dispatch: dispatchSettings(env, config),
|
|
20899
|
+
dispatch: dispatchSettings(env, config, { commands: options.commands }),
|
|
20778
20900
|
warnings: config.kind === "read" ? config.warnings : [],
|
|
20779
20901
|
missing: config.kind === "read" ? config.missing : null
|
|
20780
20902
|
};
|
package/dist/src/server.js
CHANGED
|
@@ -14206,7 +14206,7 @@ var dispatchToolSpecs = [
|
|
|
14206
14206
|
{
|
|
14207
14207
|
name: "dispatch_doc_read",
|
|
14208
14208
|
example: { issue: "DSP-1" },
|
|
14209
|
-
description: "Read a live document or a named document version, or the text of an uploaded file at its latest or named version. " + "Do not use it for issue status, asks, or events; " + "use dispatch read instead. Supply ref, issue, or project plus artifact; issue plus an omitted artifact reads the primary document. " + "A live read returns its document token for an optional dispatch doc-edit precondition. With blocks it lists the live document's blocks instead of its markdown, in document order: each block's id, type, token, anchored comments and asks, and opening words, and under a table each row by the index delete_row takes and each cell by its row and the column delete_column takes; block ids, block tokens and those indexes are what dispatch doc-edit takes. " + "After the markdown it prints every open thread anchored on the document in full: an open ask with its id and question and each reply's author, time and text, and a comment thread with its root's id, author, quote and text and each reply's author, time and text, in order; a resolved thread, and an ask that is answered or resolved, is not printed. " + "A picture (an uploaded PNG, JPEG, GIF or WebP of at most 3,750,000 bytes, 5 MB once base64-encoded) comes back as an image you see, with its name, type, size and version, every time you ask, including one a read already showed this session; " + "any other file that is not UTF-8 text is described, with the route that serves its bytes. " + OWNER_REFERENCE,
|
|
14209
|
+
description: "Read a live document or a named document version, or the text of an uploaded file at its latest or named version. " + "Do not use it for issue status, asks, or events; " + "use dispatch read instead. Supply ref, issue, or project plus artifact; issue plus an omitted artifact reads the primary document. " + "A live read returns its document token for an optional dispatch doc-edit precondition. With blocks it lists the live document's blocks instead of its markdown, in document order: each block's id, type, token, anchored comments and asks, and opening words, and under a table each row by the index delete_row takes and each cell by its row and the column delete_column takes; block ids, block tokens and those indexes are what dispatch doc-edit takes. " + "After the markdown it prints every open thread anchored on the document in full: an open ask with its id and question and each reply's author, time and text, and a comment thread with its root's id, author, time, quote and text and each reply's author, time and text, in order; a resolved thread, and an ask that is answered or resolved, is not printed. " + "A picture (an uploaded PNG, JPEG, GIF or WebP of at most 3,750,000 bytes, 5 MB once base64-encoded) comes back as an image you see, with its name, type, size and version, every time you ask, including one a read already showed this session; " + "any other file that is not UTF-8 text is described, with the route that serves its bytes. " + OWNER_REFERENCE,
|
|
14210
14210
|
arguments: (z) => ({
|
|
14211
14211
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
14212
14212
|
project: z.string().describe("Project key owning the document.").optional(),
|
|
@@ -14366,6 +14366,13 @@ var dispatchToolSpecs = [
|
|
|
14366
14366
|
}),
|
|
14367
14367
|
strict: true
|
|
14368
14368
|
},
|
|
14369
|
+
{
|
|
14370
|
+
name: "dispatch_answers",
|
|
14371
|
+
example: {},
|
|
14372
|
+
description: "List every answer and comment a person wrote to this session in the last 72 hours, newest first, each in full: " + "answers to its asks, replies on its asks and in its comment threads, comments on its documents and on issues routed to it, " + "mentions of it, messages to it and replies to its messages. Each item names its time, its writer, what it is, its " + "dispatch:// reference and its issue or document, an answer's question and choices, and the words, never cut. " + "Run it when the list of people's answers on a model request says it left older items out. " + "It reads this session's id from CLAUDE_CODE_SESSION_ID under Claude Code and DISPATCH_SESSION_ID elsewhere, so a subagent reads its parent's.",
|
|
14373
|
+
arguments: () => ({}),
|
|
14374
|
+
strict: true
|
|
14375
|
+
},
|
|
14369
14376
|
{
|
|
14370
14377
|
name: "dispatch_whoami",
|
|
14371
14378
|
example: {},
|
|
@@ -14834,7 +14841,7 @@ function runCommand(command, env, cwd) {
|
|
|
14834
14841
|
rmSync(directory, { recursive: true, force: true });
|
|
14835
14842
|
}
|
|
14836
14843
|
}
|
|
14837
|
-
function fileValue(value, field, filePath, env) {
|
|
14844
|
+
function fileValue(value, field, filePath, env, commands = "run") {
|
|
14838
14845
|
if (!value.startsWith("!")) {
|
|
14839
14846
|
if (!sendable(value)) {
|
|
14840
14847
|
throw new Error(`${filePath}: ${field} holds whitespace or a control character`);
|
|
@@ -14845,7 +14852,11 @@ function fileValue(value, field, filePath, env) {
|
|
|
14845
14852
|
const cwd = path.dirname(path.resolve(filePath));
|
|
14846
14853
|
const key = JSON.stringify([command, filePath, cwd]);
|
|
14847
14854
|
let outcome = commandOutcomes.get(key);
|
|
14848
|
-
if (
|
|
14855
|
+
if (commands === "kept") {
|
|
14856
|
+
if (outcome === undefined) {
|
|
14857
|
+
throw new Error(`${filePath}: ${field}'s command has not run in this process yet, and this read does not run it`);
|
|
14858
|
+
}
|
|
14859
|
+
} else if (outcome === undefined || "failure" in outcome && !outcome.timedOut && Date.now() - outcome.at >= FAILURE_KEPT_MS) {
|
|
14849
14860
|
outcome = { ...runCommand(command, env, cwd), at: Date.now() };
|
|
14850
14861
|
commandOutcomes.set(key, outcome);
|
|
14851
14862
|
}
|
|
@@ -14955,7 +14966,7 @@ function bearerSource(env, section, written) {
|
|
|
14955
14966
|
const bearer = written();
|
|
14956
14967
|
return bearer === undefined ? { kind: "none" } : { kind: "file", ...bearer };
|
|
14957
14968
|
}
|
|
14958
|
-
function fileBearer(bearer, url, section, env) {
|
|
14969
|
+
function fileBearer(bearer, url, section, env, commands = "run") {
|
|
14959
14970
|
const named = httpUrl2(url)?.origin;
|
|
14960
14971
|
const paired = httpUrl2(bearer.server)?.origin;
|
|
14961
14972
|
if (named !== paired) {
|
|
@@ -14967,7 +14978,7 @@ function fileBearer(bearer, url, section, env) {
|
|
|
14967
14978
|
if (bearer.token === "") {
|
|
14968
14979
|
throw new Error(`${bearer.field} in ${bearer.path} must be a non-empty bearer token`);
|
|
14969
14980
|
}
|
|
14970
|
-
return fileValue(bearer.token, bearer.field, bearer.path, env);
|
|
14981
|
+
return fileValue(bearer.token, bearer.field, bearer.path, env, commands);
|
|
14971
14982
|
}
|
|
14972
14983
|
|
|
14973
14984
|
// ../envoy-client/src/dispatch-config.ts
|
|
@@ -15002,7 +15013,30 @@ function writtenToken(config, checkout) {
|
|
|
15002
15013
|
checkout: false
|
|
15003
15014
|
};
|
|
15004
15015
|
}
|
|
15005
|
-
function dispatchSettings(env, config) {
|
|
15016
|
+
function dispatchSettings(env, config, options = {}) {
|
|
15017
|
+
const target = dispatchTarget(env, config);
|
|
15018
|
+
if (!target.enabled)
|
|
15019
|
+
return target;
|
|
15020
|
+
const { url, source } = target;
|
|
15021
|
+
let token;
|
|
15022
|
+
try {
|
|
15023
|
+
token = source.kind === "variable" ? source.token : fileBearer(source, url, "DISPATCH", env, options.commands);
|
|
15024
|
+
} catch (error) {
|
|
15025
|
+
return offWithError(messageFor(error));
|
|
15026
|
+
}
|
|
15027
|
+
const publicUrl = nonEmpty(env.DISPATCH_PUBLIC_URL);
|
|
15028
|
+
const publicParsed = publicUrl === undefined ? undefined : httpUrl2(publicUrl);
|
|
15029
|
+
if (publicUrl !== undefined && (publicParsed === undefined || publicParsed.port === "0")) {
|
|
15030
|
+
return offWithError("DISPATCH_PUBLIC_URL must be an http or https URL");
|
|
15031
|
+
}
|
|
15032
|
+
return {
|
|
15033
|
+
enabled: true,
|
|
15034
|
+
url,
|
|
15035
|
+
token,
|
|
15036
|
+
...publicUrl === undefined ? {} : { publicUrl: publicUrl.replace(/\/+$/, "") }
|
|
15037
|
+
};
|
|
15038
|
+
}
|
|
15039
|
+
function dispatchTarget(env, config) {
|
|
15006
15040
|
if (config.kind === "failed")
|
|
15007
15041
|
return offWithError(config.error);
|
|
15008
15042
|
const explicitUrl = nonEmpty(env.DISPATCH_URL);
|
|
@@ -15025,27 +15059,17 @@ function dispatchSettings(env, config) {
|
|
|
15025
15059
|
if (explicitUrl !== undefined && httpUrl2(explicitUrl) === undefined) {
|
|
15026
15060
|
return offWithError("DISPATCH_URL must be an http or https URL");
|
|
15027
15061
|
}
|
|
15062
|
+
let source;
|
|
15028
15063
|
try {
|
|
15029
|
-
|
|
15030
|
-
if (source.kind === "none") {
|
|
15031
|
-
return offWithError(explicitUrl === undefined ? `${config.path}: dispatch is enabled and no token is: set dispatch.token, or DISPATCH_TOKEN or DISPATCH_TOKEN_FILE` : `DISPATCH_URL is set and no token is: set DISPATCH_TOKEN or DISPATCH_TOKEN_FILE, or dispatch.token in ${config.path}`);
|
|
15032
|
-
}
|
|
15033
|
-
const url = (explicitUrl ?? (source.kind === "file" ? source.server : own.serverUrl ?? DEFAULT_SERVER_URL)).replace(/\/+$/, "");
|
|
15034
|
-
const token = source.kind === "variable" ? source.token : fileBearer(source, url, "DISPATCH", env);
|
|
15035
|
-
const publicUrl = nonEmpty(env.DISPATCH_PUBLIC_URL);
|
|
15036
|
-
const publicParsed = publicUrl === undefined ? undefined : httpUrl2(publicUrl);
|
|
15037
|
-
if (publicUrl !== undefined && (publicParsed === undefined || publicParsed.port === "0")) {
|
|
15038
|
-
return offWithError("DISPATCH_PUBLIC_URL must be an http or https URL");
|
|
15039
|
-
}
|
|
15040
|
-
return {
|
|
15041
|
-
enabled: true,
|
|
15042
|
-
url,
|
|
15043
|
-
token,
|
|
15044
|
-
...publicUrl === undefined ? {} : { publicUrl: publicUrl.replace(/\/+$/, "") }
|
|
15045
|
-
};
|
|
15064
|
+
source = checkout !== null && checkoutServer !== undefined ? checkoutPair(checkout, checkoutServer) : bearerSource(env, "DISPATCH", () => writtenToken(config, checkout));
|
|
15046
15065
|
} catch (error) {
|
|
15047
15066
|
return offWithError(messageFor(error));
|
|
15048
15067
|
}
|
|
15068
|
+
if (source.kind === "none") {
|
|
15069
|
+
return offWithError(explicitUrl === undefined ? `${config.path}: dispatch is enabled and no token is: set dispatch.token, or DISPATCH_TOKEN or DISPATCH_TOKEN_FILE` : `DISPATCH_URL is set and no token is: set DISPATCH_TOKEN or DISPATCH_TOKEN_FILE, or dispatch.token in ${config.path}`);
|
|
15070
|
+
}
|
|
15071
|
+
const url = (explicitUrl ?? (source.kind === "file" ? source.server : own.serverUrl ?? DEFAULT_SERVER_URL)).replace(/\/+$/, "");
|
|
15072
|
+
return { enabled: true, url, source };
|
|
15049
15073
|
}
|
|
15050
15074
|
|
|
15051
15075
|
// ../envoy-client/src/envoy-settings.ts
|
|
@@ -15619,7 +15643,7 @@ function sourceSession(sessionID) {
|
|
|
15619
15643
|
// src/server.ts
|
|
15620
15644
|
import { tool } from "@opencode-ai/plugin/tool";
|
|
15621
15645
|
// package.json
|
|
15622
|
-
var version2 = "12.0
|
|
15646
|
+
var version2 = "12.1.0";
|
|
15623
15647
|
|
|
15624
15648
|
// src/log.ts
|
|
15625
15649
|
import { appendFile, mkdir } from "fs/promises";
|
package/package.json
CHANGED
package/skills/dispatch/SKILL.md
CHANGED
|
@@ -45,12 +45,12 @@ Which machines, files or people, the numbers, then "A happens to X, or B to Y?"
|
|
|
45
45
|
|
|
46
46
|
Outside Legion, claim your issue (`dispatch claim`) and move it and your children: `in_progress` at implementation, `testing` on a production-like surface, `needs_review` while its PR waits, `done` with `--reason` once driven in production. An issue with the `legion` label, or under one: never write its status or route it, and claim it only as a Legion role of its tree, even when its claim reads `· not running`, as a suspended architect's does: taking it removes the tree from the workflow.
|
|
47
47
|
|
|
48
|
-
A claim, or `dispatch issue-update --route session:<your id>` (`--route ''` on release), brings a person's answers, comments and messages; for agents' activity, `envoy_subscribe notifications.dispatch.issue.<KEY
|
|
48
|
+
A claim, or `dispatch issue-update --route session:<your id>` (`--route ''` on release), brings a person's answers, comments and messages; for agents' activity, `envoy_subscribe notifications.dispatch.issue.<KEY>.>`, which shows no event Dispatch marks quiet, like an unnamed autosave.
|
|
49
49
|
|
|
50
50
|
## Close what you opened
|
|
51
51
|
|
|
52
|
-
- An ask you opened is yours until answered or resolved.
|
|
53
|
-
-
|
|
52
|
+
- An ask you opened is yours until answered or resolved. `dispatch resolve-ask` it that turn once anything makes it needless: another answer, a later write, work dropped.
|
|
53
|
+
- Retract an ask a new one replaces.
|
|
54
54
|
|
|
55
55
|
## Structure over stream
|
|
56
56
|
|
|
@@ -33,7 +33,7 @@ For each spec, ask, comment and message:
|
|
|
33
33
|
- Write about the current design, never the person or how people reached it. Never quote people or upload their words.
|
|
34
34
|
- Link each source where the person wrote it. With no link, state the design.
|
|
35
35
|
- In a spec, cite code by file and symbol, never a line number: it goes stale at the next edit.
|
|
36
|
-
- A sentence that cites a gate passing (a test, review, check or proof) also says
|
|
36
|
+
- A sentence that cites a gate passing (a test, review, check or proof) also says what the pass does not show: "The tests pass; they catch a wrong verdict, not a proof of a contradiction."
|
|
37
37
|
- Name your session only if a reader must reach it.
|
|
38
38
|
- Keep only text that helps a reader understand, decide, build, check.
|
|
39
39
|
- Explain each name or term a new reader may not know. Say in a clause what each issue you name does for this reader.
|