@sjawhar/opencode-legion-envoy 0.15.0 → 0.17.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/README.md +16 -9
- package/dist/src/server.js +876 -331
- package/package.json +1 -1
- package/skills/dispatch/SKILL.md +189 -220
- package/skills/legion-architect/SKILL.md +61 -38
- package/skills/legion-retro/SKILL.md +1 -1
- package/skills/legion-worker/SKILL.md +9 -8
- package/src/server.ts +74 -93
package/dist/src/server.js
CHANGED
|
@@ -19,6 +19,122 @@ import { existsSync } from "fs";
|
|
|
19
19
|
import path3 from "path";
|
|
20
20
|
import { fileURLToPath } from "url";
|
|
21
21
|
|
|
22
|
+
// ../contracts/src/dispatch-tools.ts
|
|
23
|
+
var ISSUE_REFERENCE = "An issue is a native KEY or external owner/repo#n reference; an external reference creates its native issue on first use when its repository is mapped in DISPATCH_REPO_PROJECTS.";
|
|
24
|
+
var ASK_URGENCIES = ["low", "med", "high", "blocking"];
|
|
25
|
+
var DOC_EDIT_OPS = ["replace", "delete", "insert"];
|
|
26
|
+
var dispatchToolSpecs = [
|
|
27
|
+
{
|
|
28
|
+
name: "dispatch_issue",
|
|
29
|
+
description: "Create a native Dispatch issue for newly tracked work. Do not use it when an existing issue " + `already covers the work; read or update that issue instead. ${ISSUE_REFERENCE}`,
|
|
30
|
+
arguments: (z) => ({
|
|
31
|
+
project: z.string().describe("Project key for the new issue."),
|
|
32
|
+
title: z.string().describe("Concise issue title."),
|
|
33
|
+
parent: z.string().describe("Optional parent issue.").optional(),
|
|
34
|
+
external: z.string().describe("Optional external issue reference.").optional(),
|
|
35
|
+
spec: z.string().describe("Optional initial primary-document markdown.").optional()
|
|
36
|
+
})
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "dispatch_ask",
|
|
40
|
+
description: "Open a durable, answerable decision on an issue. Do not use it for a status update or discussion; " + `use dispatch_message instead. Question is at most 800 characters and has at most 8 options. ${ISSUE_REFERENCE}`,
|
|
41
|
+
arguments: (z) => ({
|
|
42
|
+
issue: z.string().describe(ISSUE_REFERENCE),
|
|
43
|
+
question: z.string({ max: 800 }).describe("Decision question, at most 800 characters."),
|
|
44
|
+
options: z.array(z.object({
|
|
45
|
+
label: z.string().describe("Selectable option label."),
|
|
46
|
+
description: z.string().describe("Optional option context.").optional()
|
|
47
|
+
}), { max: 8 }).describe("Optional choices, at most 8.").optional(),
|
|
48
|
+
multiple: z.boolean().describe("Whether multiple choices may be selected.").optional(),
|
|
49
|
+
custom: z.boolean().describe("Whether a free-text answer is allowed.").optional(),
|
|
50
|
+
urgency: z.enum(ASK_URGENCIES).describe("Optional decision urgency.").optional(),
|
|
51
|
+
anchor: z.object({
|
|
52
|
+
artifact: z.string().describe("Artifact slug or id containing the quoted text."),
|
|
53
|
+
quote: z.string().describe("Exact text the decision concerns."),
|
|
54
|
+
occurrence: z.number({ int: true, min: 0 }).describe("Zero-based occurrence of the quote.").optional()
|
|
55
|
+
}).describe("Optional document location for the question.").optional()
|
|
56
|
+
})
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: "dispatch_comment",
|
|
60
|
+
description: "Add review feedback to an issue or document quote. Do not use it for an exact replacement; use " + `dispatch_suggest instead. Body is at most 2,000 characters. ${ISSUE_REFERENCE}`,
|
|
61
|
+
arguments: (z) => ({
|
|
62
|
+
issue: z.string().describe(ISSUE_REFERENCE),
|
|
63
|
+
artifact: z.string().describe("Artifact slug or id required when quote is given.").optional(),
|
|
64
|
+
quote: z.string().describe("Optional exact quoted document text.").optional(),
|
|
65
|
+
occurrence: z.number({ int: true, min: 0 }).describe("Optional zero-based occurrence of quote.").optional(),
|
|
66
|
+
body: z.string({ max: 2000 }).describe("Review comment, at most 2,000 characters."),
|
|
67
|
+
reply_to: z.string().describe("Optional comment id to reply to.").optional()
|
|
68
|
+
})
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: "dispatch_suggest",
|
|
72
|
+
description: "Propose an exact replacement for quoted document text. Do not use it for general feedback; use " + `dispatch_comment instead. Optional explanation is at most 2,000 characters. ${ISSUE_REFERENCE}`,
|
|
73
|
+
arguments: (z) => ({
|
|
74
|
+
issue: z.string().describe(ISSUE_REFERENCE),
|
|
75
|
+
artifact: z.string().describe("Artifact slug or id containing the quoted text."),
|
|
76
|
+
quote: z.string().describe("Exact document text to replace."),
|
|
77
|
+
replace_with: z.string().describe("Replacement text."),
|
|
78
|
+
body: z.string({ max: 2000 }).describe("Optional rationale, at most 2,000 characters.").optional(),
|
|
79
|
+
occurrence: z.number({ int: true, min: 0 }).describe("Optional zero-based occurrence of quote.").optional()
|
|
80
|
+
})
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: "dispatch_message",
|
|
84
|
+
description: "Post a plain issue update. Do not use it for a decision or line-specific review; use dispatch_ask " + `or dispatch_comment instead. Body is at most 2,000 characters. ${ISSUE_REFERENCE}`,
|
|
85
|
+
arguments: (z) => ({
|
|
86
|
+
issue: z.string().describe(ISSUE_REFERENCE),
|
|
87
|
+
body: z.string({ max: 2000 }).describe("Update text, at most 2,000 characters.")
|
|
88
|
+
})
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: "dispatch_doc_edit",
|
|
92
|
+
description: "Apply deterministic text edits to a document. Do not use it for review feedback or for reading; use " + `dispatch_comment, dispatch_suggest, or dispatch_doc_read instead. ${ISSUE_REFERENCE}`,
|
|
93
|
+
arguments: (z) => ({
|
|
94
|
+
issue: z.string().describe(ISSUE_REFERENCE),
|
|
95
|
+
artifact: z.string().describe("Artifact slug or id for the document."),
|
|
96
|
+
ops: z.array(z.object({
|
|
97
|
+
op: z.enum(DOC_EDIT_OPS).describe("Edit operation."),
|
|
98
|
+
find: z.string().describe("Text to find for replace or delete.").optional(),
|
|
99
|
+
with: z.string().describe("Replacement text for replace.").optional(),
|
|
100
|
+
occurrence: z.number({ int: true, min: 0 }).describe("Optional zero-based match occurrence.").optional(),
|
|
101
|
+
markdown: z.string().describe("Markdown to insert.").optional(),
|
|
102
|
+
after: z.string().describe("Anchor after which to insert.").optional(),
|
|
103
|
+
before: z.string().describe("Anchor before which to insert.").optional()
|
|
104
|
+
})).describe("Flat tagged edits; the server validates fields required for each operation."),
|
|
105
|
+
summary: z.string().describe("Optional named-version summary.").optional()
|
|
106
|
+
})
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: "dispatch_doc_read",
|
|
110
|
+
description: "Read a live document or a named document version. Do not use it for issue status, asks, or events; " + "use dispatch_read instead. Supply ref or issue; issue plus an omitted artifact reads the primary document. " + `${ISSUE_REFERENCE}`,
|
|
111
|
+
arguments: (z) => ({
|
|
112
|
+
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
113
|
+
artifact: z.string().describe("Optional artifact slug or id; primary document by default.").optional(),
|
|
114
|
+
version: z.number({ int: true, min: 1 }).describe("Optional version number.").optional(),
|
|
115
|
+
ref: z.string().describe("Optional dispatch:// document reference.").optional()
|
|
116
|
+
})
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: "dispatch_artifact",
|
|
120
|
+
description: "Upload a local file as an issue artifact. Do not use it to edit a live document; use dispatch_doc_edit " + `instead. Files are limited to 25 MiB. ${ISSUE_REFERENCE}`,
|
|
121
|
+
arguments: (z) => ({
|
|
122
|
+
issue: z.string().describe(ISSUE_REFERENCE),
|
|
123
|
+
name: z.string().describe("Artifact filename shown in Dispatch."),
|
|
124
|
+
path: z.string().describe("Local path to the file to upload."),
|
|
125
|
+
primary: z.boolean().describe("Make this document the issue primary artifact.").optional(),
|
|
126
|
+
summary: z.string().describe("Optional version summary.").optional()
|
|
127
|
+
})
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: "dispatch_read",
|
|
131
|
+
description: "Read an issue summary, targeted ask, or targeted comment reply chain. Do not use it for document " + "contents; use dispatch_doc_read instead. Supply issue or ref. " + ISSUE_REFERENCE,
|
|
132
|
+
arguments: (z) => ({
|
|
133
|
+
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
134
|
+
ref: z.string().describe("Optional dispatch:// issue reference.").optional()
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
];
|
|
22
138
|
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
|
|
23
139
|
var exports_external = {};
|
|
24
140
|
__export(exports_external, {
|
|
@@ -13551,25 +13667,20 @@ function date4(params) {
|
|
|
13551
13667
|
|
|
13552
13668
|
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
|
|
13553
13669
|
config(en_default());
|
|
13554
|
-
// ../contracts/src/dispatch-question.ts
|
|
13555
|
-
var DispatchQuestionOptionSchema = strictObject({
|
|
13556
|
-
label: string2().min(1),
|
|
13557
|
-
description: string2().optional()
|
|
13558
|
-
});
|
|
13559
|
-
var DispatchQuestionSchema = strictObject({
|
|
13560
|
-
askId: string2().optional(),
|
|
13561
|
-
question: string2().min(1),
|
|
13562
|
-
header: string2().optional(),
|
|
13563
|
-
options: array(DispatchQuestionOptionSchema).optional(),
|
|
13564
|
-
multiple: boolean2().optional(),
|
|
13565
|
-
custom: boolean2().optional()
|
|
13566
|
-
});
|
|
13567
|
-
var DispatchQuestionInputSchema = DispatchQuestionSchema.omit({ askId: true });
|
|
13568
13670
|
// ../contracts/src/envelope.ts
|
|
13569
13671
|
var isSubject = (value) => typeof value === "string" && value.length > 0;
|
|
13570
13672
|
var EnvelopeSchema = object({
|
|
13571
13673
|
event_id: string2().min(1),
|
|
13572
|
-
source: _enum2([
|
|
13674
|
+
source: _enum2([
|
|
13675
|
+
"agent",
|
|
13676
|
+
"human",
|
|
13677
|
+
"envoy",
|
|
13678
|
+
"github",
|
|
13679
|
+
"slack",
|
|
13680
|
+
"whatsapp",
|
|
13681
|
+
"ghostwispr",
|
|
13682
|
+
"dispatch"
|
|
13683
|
+
]),
|
|
13573
13684
|
source_event_id: string2().min(1),
|
|
13574
13685
|
source_session: string2().optional(),
|
|
13575
13686
|
topic: custom(isSubject, { message: "topic must be a non-empty subject" }),
|
|
@@ -13831,6 +13942,13 @@ var LegionDaemonApi = {
|
|
|
13831
13942
|
}),
|
|
13832
13943
|
response: object({})
|
|
13833
13944
|
},
|
|
13945
|
+
PhaseComplete: {
|
|
13946
|
+
request: strictObject({
|
|
13947
|
+
grantId: nonEmptyString,
|
|
13948
|
+
summary: nonEmptyString
|
|
13949
|
+
}),
|
|
13950
|
+
response: object({})
|
|
13951
|
+
},
|
|
13834
13952
|
SpawnWorker: {
|
|
13835
13953
|
request: architectCapability.extend({
|
|
13836
13954
|
issue: nonEmptyString,
|
|
@@ -13894,6 +14012,41 @@ var LegionDaemonApi = {
|
|
|
13894
14012
|
response: object({ token: nonEmptyString, appLogin: string2().endsWith("[bot]") })
|
|
13895
14013
|
}
|
|
13896
14014
|
};
|
|
14015
|
+
// ../contracts/src/tool-schema.ts
|
|
14016
|
+
function zodSchemaApi(zod) {
|
|
14017
|
+
const api = zod;
|
|
14018
|
+
return {
|
|
14019
|
+
string: (opts = {}) => {
|
|
14020
|
+
let schema = api.string();
|
|
14021
|
+
if (opts.min !== undefined)
|
|
14022
|
+
schema = schema.min(opts.min);
|
|
14023
|
+
if (opts.max !== undefined)
|
|
14024
|
+
schema = schema.max(opts.max);
|
|
14025
|
+
return schema;
|
|
14026
|
+
},
|
|
14027
|
+
number: (opts = {}) => {
|
|
14028
|
+
let schema = api.number();
|
|
14029
|
+
if (opts.int)
|
|
14030
|
+
schema = schema.int();
|
|
14031
|
+
if (opts.min !== undefined)
|
|
14032
|
+
schema = schema.min(opts.min);
|
|
14033
|
+
if (opts.max !== undefined)
|
|
14034
|
+
schema = schema.max(opts.max);
|
|
14035
|
+
return schema;
|
|
14036
|
+
},
|
|
14037
|
+
boolean: () => api.boolean(),
|
|
14038
|
+
enum: (values) => api.enum(values),
|
|
14039
|
+
array: (item, opts = {}) => {
|
|
14040
|
+
let schema = api.array(item);
|
|
14041
|
+
if (opts.min !== undefined)
|
|
14042
|
+
schema = schema.min(opts.min);
|
|
14043
|
+
if (opts.max !== undefined)
|
|
14044
|
+
schema = schema.max(opts.max);
|
|
14045
|
+
return schema;
|
|
14046
|
+
},
|
|
14047
|
+
object: (shape) => api.object(shape)
|
|
14048
|
+
};
|
|
14049
|
+
}
|
|
13897
14050
|
// ../envoy-client/src/defaults.ts
|
|
13898
14051
|
var DEFAULT_ENVOY_URL = "http://127.0.0.1:9020";
|
|
13899
14052
|
var DEFAULT_HEARTBEAT_MS = 120000;
|
|
@@ -13912,81 +14065,109 @@ function normalizeEnvoyUrl(value) {
|
|
|
13912
14065
|
return value.replace(/\/+$/, "");
|
|
13913
14066
|
}
|
|
13914
14067
|
|
|
13915
|
-
// ../envoy-client/src/dispatch-
|
|
13916
|
-
|
|
13917
|
-
|
|
13918
|
-
|
|
13919
|
-
|
|
13920
|
-
|
|
13921
|
-
|
|
13922
|
-
|
|
13923
|
-
thread: 'Continue an existing thread: "<n>" (an open issue in the repo; a plain work issue is adopted as a thread) or "owner/name#<n>". When set, omit subject, urgency, repo, and parent.',
|
|
13924
|
-
context: `What you are doing, what you found, why you are stuck \u2014 at most ${DISPATCH_CONTEXT_MAX} characters, at most three short paragraphs or a bullet list. The reader has NOT seen your transcript: no nouns you coined this session, no internal identifiers unless the question is about them. GitHub references (#N, owner/repo#N, URLs) may be bare; the dashboard unfurls them.`,
|
|
13925
|
-
question: `The ask, at most ${DISPATCH_QUESTION_MAX} characters, as a list: current state \u2192 desired state \u2192 your recommendation and why; options go in \`ask\`.`,
|
|
13926
|
-
ask: "Structured questions rendered as buttons on the dashboard. Each: { question, header?, options: [{ label, description? }], multiple?, custom? }. Use this whenever the answer is one of N choices.",
|
|
13927
|
-
urgency: "low | med | high | blocking (default med). Opening a thread only.",
|
|
13928
|
-
repo: "owner/name. Opening a thread only; defaults to the working directory's GitHub repo.",
|
|
13929
|
-
parent: "<n> | owner/name#<n>[#<commentId>]. Opening a thread only: link the thread as a sub-issue of an existing issue and append a breadcrumb to the comment."
|
|
13930
|
-
};
|
|
13931
|
-
class DispatchArgumentError extends Error {
|
|
13932
|
-
name = "DispatchArgumentError";
|
|
13933
|
-
}
|
|
13934
|
-
var prose = {
|
|
13935
|
-
context: string2(),
|
|
13936
|
-
question: string2(),
|
|
13937
|
-
ask: array(DispatchQuestionInputSchema).optional()
|
|
13938
|
-
};
|
|
13939
|
-
var OpenThreadCallSchema = strictObject({
|
|
13940
|
-
subject: string2(),
|
|
13941
|
-
...prose,
|
|
13942
|
-
urgency: _enum2(DISPATCH_URGENCIES).optional(),
|
|
13943
|
-
repo: string2().optional(),
|
|
13944
|
-
parent: string2().optional()
|
|
13945
|
-
});
|
|
13946
|
-
var ContinueThreadCallSchema = strictObject({ thread: string2(), ...prose });
|
|
13947
|
-
var dispatchToolShape = {
|
|
13948
|
-
subject: string2().describe(DISPATCH_ARGUMENTS.subject).optional(),
|
|
13949
|
-
thread: string2().describe(DISPATCH_ARGUMENTS.thread).optional(),
|
|
13950
|
-
context: string2().describe(DISPATCH_ARGUMENTS.context),
|
|
13951
|
-
question: string2().describe(DISPATCH_ARGUMENTS.question),
|
|
13952
|
-
ask: array(DispatchQuestionInputSchema).describe(DISPATCH_ARGUMENTS.ask).optional(),
|
|
13953
|
-
urgency: _enum2(DISPATCH_URGENCIES).describe(DISPATCH_ARGUMENTS.urgency).optional(),
|
|
13954
|
-
repo: string2().describe(DISPATCH_ARGUMENTS.repo).optional(),
|
|
13955
|
-
parent: string2().describe(DISPATCH_ARGUMENTS.parent).optional()
|
|
13956
|
-
};
|
|
13957
|
-
var DISPATCH_TOOL_JSON_SCHEMA = toJSONSchema(object(dispatchToolShape));
|
|
13958
|
-
function isContinueCall(call) {
|
|
13959
|
-
return "thread" in call;
|
|
14068
|
+
// ../envoy-client/src/dispatch-config.ts
|
|
14069
|
+
import { readFileSync } from "fs";
|
|
14070
|
+
import { homedir } from "os";
|
|
14071
|
+
import * as path from "path";
|
|
14072
|
+
|
|
14073
|
+
// ../envoy-client/src/errors.ts
|
|
14074
|
+
function messageFor(error) {
|
|
14075
|
+
return error instanceof Error ? error.message : String(error);
|
|
13960
14076
|
}
|
|
13961
|
-
|
|
13962
|
-
|
|
14077
|
+
|
|
14078
|
+
// ../envoy-client/src/dispatch-config.ts
|
|
14079
|
+
var DEFAULT_SERVER_URL = "http://localhost:8766";
|
|
14080
|
+
function normalizeDispatchUrl(url2) {
|
|
14081
|
+
return url2.replace(/\/+$/, "");
|
|
13963
14082
|
}
|
|
13964
|
-
function
|
|
13965
|
-
return
|
|
14083
|
+
function deprecatedMcpUrl(url2) {
|
|
14084
|
+
return normalizeDispatchUrl(url2).replace(/\/mcp$/, "");
|
|
13966
14085
|
}
|
|
13967
|
-
function
|
|
13968
|
-
|
|
13969
|
-
|
|
14086
|
+
function parsedDispatchUrl(value, source) {
|
|
14087
|
+
const url2 = normalizeDispatchUrl(value);
|
|
14088
|
+
try {
|
|
14089
|
+
new URL(url2);
|
|
14090
|
+
return { url: url2, error: null };
|
|
14091
|
+
} catch {
|
|
14092
|
+
return { url: null, error: `${source} must be a valid URL` };
|
|
13970
14093
|
}
|
|
13971
|
-
|
|
13972
|
-
|
|
13973
|
-
|
|
13974
|
-
|
|
13975
|
-
|
|
14094
|
+
}
|
|
14095
|
+
var EnvoyFileSchema = looseObject({
|
|
14096
|
+
$schema: string2().optional(),
|
|
14097
|
+
natsUrls: array(string2()).optional(),
|
|
14098
|
+
dispatch: strictObject({
|
|
14099
|
+
enabled: boolean2().optional(),
|
|
14100
|
+
serverUrl: url().optional(),
|
|
14101
|
+
token: string2().optional()
|
|
14102
|
+
}).optional()
|
|
14103
|
+
});
|
|
14104
|
+
function describeSchemaIssue(filePath, error) {
|
|
14105
|
+
const issue = error.issues[0];
|
|
14106
|
+
if (!issue)
|
|
14107
|
+
return `${filePath}: invalid dispatch config`;
|
|
14108
|
+
if (issue.code === "unrecognized_keys") {
|
|
14109
|
+
const keys = issue.keys.map((key) => `dispatch.${key}`).join(", ");
|
|
14110
|
+
return `${filePath}: unrecognized dispatch key(s): ${keys}`;
|
|
13976
14111
|
}
|
|
13977
|
-
|
|
13978
|
-
|
|
14112
|
+
return `${filePath}: ${issue.path.join(".")}: ${issue.message}`;
|
|
14113
|
+
}
|
|
14114
|
+
function readEnvoyFile(filePath) {
|
|
14115
|
+
let raw;
|
|
14116
|
+
try {
|
|
14117
|
+
raw = readFileSync(filePath, "utf-8");
|
|
14118
|
+
} catch {
|
|
14119
|
+
return { kind: "absent" };
|
|
13979
14120
|
}
|
|
13980
|
-
|
|
13981
|
-
|
|
14121
|
+
let parsedJson;
|
|
14122
|
+
try {
|
|
14123
|
+
parsedJson = JSON.parse(raw);
|
|
14124
|
+
} catch (err) {
|
|
14125
|
+
return { kind: "invalid", reason: `${filePath}: invalid JSON (${messageFor(err)})` };
|
|
13982
14126
|
}
|
|
13983
|
-
const parsed =
|
|
14127
|
+
const parsed = EnvoyFileSchema.safeParse(parsedJson);
|
|
13984
14128
|
if (!parsed.success) {
|
|
13985
|
-
|
|
14129
|
+
return { kind: "invalid", reason: describeSchemaIssue(filePath, parsed.error) };
|
|
14130
|
+
}
|
|
14131
|
+
return { kind: "valid", settings: parsed.data.dispatch ?? null };
|
|
14132
|
+
}
|
|
14133
|
+
function resolveDispatchConfig(env, options = {}) {
|
|
14134
|
+
const explicitUrl = env.DISPATCH_URL;
|
|
14135
|
+
const deprecatedUrl = explicitUrl ? undefined : env.DISPATCH_MCP_URL;
|
|
14136
|
+
const home = options.home ?? env.HOME ?? homedir();
|
|
14137
|
+
const cwd = options.cwd ?? process.cwd();
|
|
14138
|
+
const userFile = readEnvoyFile(path.join(home, ".config", "opencode", "envoy.json"));
|
|
14139
|
+
const repoFile = readEnvoyFile(path.join(cwd, ".opencode", "envoy.json"));
|
|
14140
|
+
for (const file of [userFile, repoFile]) {
|
|
14141
|
+
if (file.kind === "invalid") {
|
|
14142
|
+
return { enabled: false, url: null, token: null, error: file.reason };
|
|
14143
|
+
}
|
|
14144
|
+
}
|
|
14145
|
+
const merged = {
|
|
14146
|
+
...userFile.kind === "valid" ? userFile.settings : null,
|
|
14147
|
+
...repoFile.kind === "valid" ? repoFile.settings : null
|
|
14148
|
+
};
|
|
14149
|
+
const rawUrl = explicitUrl !== undefined ? { value: explicitUrl, source: "DISPATCH_URL" } : deprecatedUrl !== undefined ? { value: deprecatedMcpUrl(deprecatedUrl), source: "DISPATCH_MCP_URL" } : merged.enabled === true ? { value: merged.serverUrl ?? DEFAULT_SERVER_URL, source: "dispatch.serverUrl" } : null;
|
|
14150
|
+
const url2 = rawUrl ? parsedDispatchUrl(rawUrl.value, rawUrl.source) : { url: null, error: null };
|
|
14151
|
+
const token = env.DISPATCH_TOKEN ?? merged.token ?? null;
|
|
14152
|
+
const tokenSource = env.DISPATCH_TOKEN === undefined ? "dispatch.token" : "DISPATCH_TOKEN";
|
|
14153
|
+
if (url2.error !== null)
|
|
14154
|
+
return { enabled: false, url: null, token, error: url2.error };
|
|
14155
|
+
if (url2.url === null)
|
|
14156
|
+
return { enabled: false, url: null, token, error: null };
|
|
14157
|
+
if (!token) {
|
|
14158
|
+
return {
|
|
14159
|
+
enabled: false,
|
|
14160
|
+
url: url2.url,
|
|
14161
|
+
token,
|
|
14162
|
+
error: `${tokenSource} must be a non-empty bearer token`
|
|
14163
|
+
};
|
|
13986
14164
|
}
|
|
13987
|
-
return
|
|
14165
|
+
return { enabled: true, url: url2.url, token, error: null };
|
|
13988
14166
|
}
|
|
13989
14167
|
|
|
14168
|
+
// ../envoy-client/src/dispatch-execute.ts
|
|
14169
|
+
import { resolve as resolvePath } from "path";
|
|
14170
|
+
|
|
13990
14171
|
// ../envoy-client/src/dispatch-cwd.ts
|
|
13991
14172
|
import { execFile } from "child_process";
|
|
13992
14173
|
import { promisify } from "util";
|
|
@@ -14069,229 +14250,592 @@ async function resolveOrigin(env, exec, cwd) {
|
|
|
14069
14250
|
return origin;
|
|
14070
14251
|
}
|
|
14071
14252
|
|
|
14072
|
-
// ../envoy-client/src/
|
|
14073
|
-
function messageFor(error) {
|
|
14074
|
-
return error instanceof Error ? error.message : String(error);
|
|
14075
|
-
}
|
|
14076
|
-
|
|
14077
|
-
// ../envoy-client/src/dispatch-client.ts
|
|
14078
|
-
function ghTokenGetter(cwd, exec = defaultExec) {
|
|
14079
|
-
return async () => {
|
|
14080
|
-
try {
|
|
14081
|
-
const { stdout } = await exec("gh", ["auth", "token"], { cwd });
|
|
14082
|
-
const value = stdout.trim();
|
|
14083
|
-
return value.length > 0 ? value : null;
|
|
14084
|
-
} catch {
|
|
14085
|
-
return null;
|
|
14086
|
-
}
|
|
14087
|
-
};
|
|
14088
|
-
}
|
|
14089
|
-
|
|
14253
|
+
// ../envoy-client/src/dispatch-http.ts
|
|
14090
14254
|
class DispatchServiceError extends Error {
|
|
14091
|
-
|
|
14255
|
+
code;
|
|
14256
|
+
status;
|
|
14257
|
+
candidates;
|
|
14092
14258
|
name = "DispatchServiceError";
|
|
14093
|
-
constructor(
|
|
14259
|
+
constructor(code, status, message, candidates) {
|
|
14094
14260
|
super(message);
|
|
14095
|
-
this.
|
|
14261
|
+
this.code = code;
|
|
14262
|
+
this.status = status;
|
|
14263
|
+
this.candidates = candidates;
|
|
14096
14264
|
}
|
|
14097
14265
|
}
|
|
14098
|
-
function
|
|
14099
|
-
|
|
14100
|
-
return JSON.parse(body);
|
|
14101
|
-
for (const line of body.split(`
|
|
14102
|
-
`)) {
|
|
14103
|
-
const payload = line.match(/^data:\s*(.+)$/)?.[1];
|
|
14104
|
-
if (payload !== undefined)
|
|
14105
|
-
return JSON.parse(payload);
|
|
14106
|
-
}
|
|
14107
|
-
return null;
|
|
14266
|
+
function asErrorShape(value) {
|
|
14267
|
+
return typeof value === "object" && value !== null ? value : {};
|
|
14108
14268
|
}
|
|
14109
|
-
function
|
|
14110
|
-
return
|
|
14269
|
+
function isJson(response) {
|
|
14270
|
+
return response.headers.get("content-type")?.includes("application/json") ?? false;
|
|
14111
14271
|
}
|
|
14112
|
-
|
|
14113
|
-
|
|
14114
|
-
|
|
14115
|
-
|
|
14116
|
-
|
|
14117
|
-
|
|
14118
|
-
|
|
14119
|
-
|
|
14120
|
-
|
|
14121
|
-
|
|
14122
|
-
|
|
14123
|
-
|
|
14124
|
-
|
|
14125
|
-
|
|
14126
|
-
|
|
14127
|
-
|
|
14128
|
-
|
|
14129
|
-
|
|
14130
|
-
|
|
14131
|
-
|
|
14132
|
-
|
|
14272
|
+
|
|
14273
|
+
class DispatchClient {
|
|
14274
|
+
token;
|
|
14275
|
+
fetchImpl;
|
|
14276
|
+
#baseUrl;
|
|
14277
|
+
#resolvedIssues = new Map;
|
|
14278
|
+
#creatingIssues = new Map;
|
|
14279
|
+
constructor(baseUrl, token, fetchImpl = fetch) {
|
|
14280
|
+
this.token = token;
|
|
14281
|
+
this.fetchImpl = fetchImpl;
|
|
14282
|
+
this.#baseUrl = baseUrl.replace(/\/+$/, "");
|
|
14283
|
+
}
|
|
14284
|
+
async issue(input) {
|
|
14285
|
+
return this.#json("POST", ["api", "v1", "issues"], input);
|
|
14286
|
+
}
|
|
14287
|
+
async getIssue(issue) {
|
|
14288
|
+
return this.#json("GET", ["api", "v1", "issues", await this.#resolveIssue(issue)]);
|
|
14289
|
+
}
|
|
14290
|
+
async getIssueEvents(issue, after = 0, limit = 200) {
|
|
14291
|
+
return this.#json("GET", ["api", "v1", "issues", await this.#resolveIssue(issue), "events"], undefined, { after, limit });
|
|
14292
|
+
}
|
|
14293
|
+
async read(issueReference) {
|
|
14294
|
+
const issueKey = await this.#resolveIssue(issueReference);
|
|
14295
|
+
const issue = await this.getIssue(issueKey);
|
|
14296
|
+
const events = await this.getIssueEvents(issueKey, Math.max(0, issue.last_seq - 10), 10);
|
|
14297
|
+
return { issue, events };
|
|
14298
|
+
}
|
|
14299
|
+
async ask(issue, input) {
|
|
14300
|
+
return this.#json("POST", ["api", "v1", "issues", await this.#resolveIssue(issue), "asks"], input);
|
|
14301
|
+
}
|
|
14302
|
+
async comment(issue, input) {
|
|
14303
|
+
return this.#json("POST", ["api", "v1", "issues", await this.#resolveIssue(issue), "comments"], input);
|
|
14304
|
+
}
|
|
14305
|
+
async suggest(issue, input) {
|
|
14306
|
+
const { replace_with, ...comment } = input;
|
|
14307
|
+
return this.#json("POST", ["api", "v1", "issues", await this.#resolveIssue(issue), "comments"], {
|
|
14308
|
+
...comment,
|
|
14309
|
+
suggestion: { replace_with }
|
|
14133
14310
|
});
|
|
14134
|
-
} catch (error) {
|
|
14135
|
-
throw new DispatchServiceError("transport", `dispatch service unreachable at ${options.serviceUrl}: ${messageFor(error)}`);
|
|
14136
14311
|
}
|
|
14137
|
-
|
|
14138
|
-
|
|
14139
|
-
throw new DispatchServiceError("auth", `dispatch service rejected the GitHub token (401): ${body.slice(0, 200)}`);
|
|
14312
|
+
async message(issue, input) {
|
|
14313
|
+
return this.#json("POST", ["api", "v1", "issues", await this.#resolveIssue(issue), "messages"], input);
|
|
14140
14314
|
}
|
|
14141
|
-
|
|
14142
|
-
|
|
14315
|
+
async artifact(issue, input) {
|
|
14316
|
+
const form = new FormData;
|
|
14317
|
+
form.set("name", input.name);
|
|
14318
|
+
if (input.primary !== undefined)
|
|
14319
|
+
form.set("primary", String(input.primary));
|
|
14320
|
+
if (input.summary !== undefined)
|
|
14321
|
+
form.set("summary", input.summary);
|
|
14322
|
+
form.set("actor", JSON.stringify(input.actor));
|
|
14323
|
+
form.set("file", input.file, input.name);
|
|
14324
|
+
return this.#form("POST", ["api", "v1", "issues", await this.#resolveIssue(issue), "artifacts"], form);
|
|
14143
14325
|
}
|
|
14144
|
-
|
|
14145
|
-
|
|
14146
|
-
parsed = parseResponseBody(body, response.headers.get("content-type") ?? "");
|
|
14147
|
-
} catch (error) {
|
|
14148
|
-
throw new DispatchServiceError("transport", `dispatch service sent an unreadable response: ${messageFor(error)}`);
|
|
14326
|
+
async getArtifact(id) {
|
|
14327
|
+
return this.#json("GET", ["api", "v1", "artifacts", id]);
|
|
14149
14328
|
}
|
|
14150
|
-
|
|
14151
|
-
|
|
14152
|
-
throw new DispatchServiceError("tool", rpc.error.message ?? "dispatch service returned an error");
|
|
14329
|
+
async docRead(id, version) {
|
|
14330
|
+
return version === undefined ? this.#json("GET", ["api", "v1", "artifacts", id, "text"]) : this.#json("GET", ["api", "v1", "artifacts", id, "versions", String(version)]);
|
|
14153
14331
|
}
|
|
14154
|
-
|
|
14155
|
-
|
|
14156
|
-
if (rpc.result?.isError)
|
|
14157
|
-
throw new DispatchServiceError("tool", text || "dispatch failed");
|
|
14158
|
-
let result;
|
|
14159
|
-
try {
|
|
14160
|
-
result = JSON.parse(text);
|
|
14161
|
-
} catch {
|
|
14162
|
-
throw new DispatchServiceError("transport", `dispatch service returned a non-JSON result: ${text.slice(0, 200)}`);
|
|
14332
|
+
async docEdit(id, input) {
|
|
14333
|
+
return this.#json("POST", ["api", "v1", "artifacts", id, "edits"], input);
|
|
14163
14334
|
}
|
|
14164
|
-
|
|
14165
|
-
|
|
14335
|
+
async nameArtifactVersion(id, input) {
|
|
14336
|
+
return this.#json("POST", ["api", "v1", "artifacts", id, "versions"], input);
|
|
14337
|
+
}
|
|
14338
|
+
async getAsk(id) {
|
|
14339
|
+
return this.#json("GET", ["api", "v1", "asks", id]);
|
|
14340
|
+
}
|
|
14341
|
+
async getComment(id) {
|
|
14342
|
+
return this.#json("GET", ["api", "v1", "comments", id]);
|
|
14343
|
+
}
|
|
14344
|
+
async getComments(issue, artifact) {
|
|
14345
|
+
return this.#json("GET", ["api", "v1", "issues", await this.#resolveIssue(issue), "comments"], undefined, artifact ? { artifact } : undefined);
|
|
14346
|
+
}
|
|
14347
|
+
async ensureIssue(issueReference, actor) {
|
|
14348
|
+
if (!issueReference.includes("#"))
|
|
14349
|
+
return issueReference;
|
|
14350
|
+
try {
|
|
14351
|
+
return await this.#resolveIssue(issueReference);
|
|
14352
|
+
} catch (error) {
|
|
14353
|
+
if (!(error instanceof DispatchServiceError) || error.status !== 404)
|
|
14354
|
+
throw error;
|
|
14355
|
+
}
|
|
14356
|
+
let creating = this.#creatingIssues.get(issueReference);
|
|
14357
|
+
if (!creating) {
|
|
14358
|
+
creating = this.#createExternalIssue(issueReference, actor);
|
|
14359
|
+
this.#creatingIssues.set(issueReference, creating);
|
|
14360
|
+
}
|
|
14361
|
+
try {
|
|
14362
|
+
return await creating;
|
|
14363
|
+
} finally {
|
|
14364
|
+
if (this.#creatingIssues.get(issueReference) === creating) {
|
|
14365
|
+
this.#creatingIssues.delete(issueReference);
|
|
14366
|
+
}
|
|
14367
|
+
}
|
|
14368
|
+
}
|
|
14369
|
+
async#createExternalIssue(issueReference, actor) {
|
|
14370
|
+
try {
|
|
14371
|
+
const created = await this.#json("POST", ["api", "v1", "issues"], {
|
|
14372
|
+
external: issueReference,
|
|
14373
|
+
actor
|
|
14374
|
+
});
|
|
14375
|
+
this.#resolvedIssues.set(issueReference, Promise.resolve(created.key));
|
|
14376
|
+
return created.key;
|
|
14377
|
+
} catch (error) {
|
|
14378
|
+
if (error instanceof DispatchServiceError && (error.status === 409 || error.status === 500)) {
|
|
14379
|
+
return this.#resolveIssue(issueReference);
|
|
14380
|
+
}
|
|
14381
|
+
throw error;
|
|
14382
|
+
}
|
|
14383
|
+
}
|
|
14384
|
+
async#resolveIssue(issueReference) {
|
|
14385
|
+
if (!issueReference.includes("#"))
|
|
14386
|
+
return issueReference;
|
|
14387
|
+
let resolved = this.#resolvedIssues.get(issueReference);
|
|
14388
|
+
if (!resolved) {
|
|
14389
|
+
resolved = this.#json("GET", ["api", "v1", "issues", "resolve"], undefined, {
|
|
14390
|
+
ref: issueReference
|
|
14391
|
+
}).then((resolution) => resolution.key);
|
|
14392
|
+
this.#resolvedIssues.set(issueReference, resolved);
|
|
14393
|
+
}
|
|
14394
|
+
try {
|
|
14395
|
+
return await resolved;
|
|
14396
|
+
} catch (error) {
|
|
14397
|
+
this.#resolvedIssues.delete(issueReference);
|
|
14398
|
+
throw error;
|
|
14399
|
+
}
|
|
14400
|
+
}
|
|
14401
|
+
async#json(method, path, body, query) {
|
|
14402
|
+
const headers = {
|
|
14403
|
+
Accept: "application/json",
|
|
14404
|
+
Authorization: `Bearer ${this.token}`
|
|
14405
|
+
};
|
|
14406
|
+
if (body !== undefined)
|
|
14407
|
+
headers["Content-Type"] = "application/json";
|
|
14408
|
+
const response = await this.fetchImpl(this.#url(path, query), {
|
|
14409
|
+
method,
|
|
14410
|
+
headers,
|
|
14411
|
+
...body === undefined ? {} : { body: JSON.stringify(body) }
|
|
14412
|
+
});
|
|
14413
|
+
return this.#response(response);
|
|
14414
|
+
}
|
|
14415
|
+
async#form(method, path, body) {
|
|
14416
|
+
const response = await this.fetchImpl(this.#url(path), {
|
|
14417
|
+
method,
|
|
14418
|
+
headers: { Accept: "application/json", Authorization: `Bearer ${this.token}` },
|
|
14419
|
+
body
|
|
14420
|
+
});
|
|
14421
|
+
return this.#response(response);
|
|
14422
|
+
}
|
|
14423
|
+
#url(path, query) {
|
|
14424
|
+
const url = new URL(`${path.map((segment) => encodeURIComponent(segment)).join("/")}`, `${this.#baseUrl}/`);
|
|
14425
|
+
if (query) {
|
|
14426
|
+
for (const [name, value] of Object.entries(query))
|
|
14427
|
+
url.searchParams.set(name, String(value));
|
|
14428
|
+
}
|
|
14429
|
+
return url.toString();
|
|
14430
|
+
}
|
|
14431
|
+
async#response(response) {
|
|
14432
|
+
const text = await response.text();
|
|
14433
|
+
let payload = text;
|
|
14434
|
+
if (text && isJson(response)) {
|
|
14435
|
+
try {
|
|
14436
|
+
payload = JSON.parse(text);
|
|
14437
|
+
} catch {
|
|
14438
|
+
payload = text;
|
|
14439
|
+
}
|
|
14440
|
+
}
|
|
14441
|
+
if (!response.ok) {
|
|
14442
|
+
const error = asErrorShape(payload);
|
|
14443
|
+
throw new DispatchServiceError(error.code ?? `HTTP_${response.status}`, response.status, error.error ?? (typeof payload === "string" && payload ? payload : response.statusText), error.candidates);
|
|
14444
|
+
}
|
|
14445
|
+
return payload;
|
|
14166
14446
|
}
|
|
14167
|
-
return result;
|
|
14168
14447
|
}
|
|
14169
14448
|
|
|
14170
|
-
// ../envoy-client/src/dispatch-
|
|
14171
|
-
var
|
|
14172
|
-
|
|
14173
|
-
|
|
14174
|
-
|
|
14175
|
-
|
|
14176
|
-
|
|
14177
|
-
|
|
14178
|
-
|
|
14179
|
-
|
|
14180
|
-
|
|
14181
|
-
|
|
14182
|
-
repo = resolved;
|
|
14183
|
-
}
|
|
14184
|
-
const resolvedOrigin = await resolveOrigin(input.env, input.exec, cwd);
|
|
14185
|
-
const origin = {
|
|
14186
|
-
...resolvedOrigin,
|
|
14187
|
-
host: input.host,
|
|
14188
|
-
...input.sessionId ? { sessionId: input.sessionId } : {},
|
|
14189
|
-
...input.sessionTitle ? { sessionTitle: input.sessionTitle } : {}
|
|
14190
|
-
};
|
|
14191
|
-
return { ...call, ...repo === undefined ? {} : { repo }, origin };
|
|
14449
|
+
// ../envoy-client/src/dispatch-execute.ts
|
|
14450
|
+
var nativeIssueKeyPattern = /^[A-Z][A-Z0-9]{1,9}-[0-9]+$/;
|
|
14451
|
+
var externalIssueRefPattern = /^([^/\s]+)\/([^/\s#]+)#([1-9][0-9]*)$/;
|
|
14452
|
+
var bareIssueNumberPattern = /^[1-9][0-9]*$/;
|
|
14453
|
+
function dispatchTopic(issue) {
|
|
14454
|
+
return `notifications.dispatch.issue.${issue}.>`;
|
|
14455
|
+
}
|
|
14456
|
+
function stringArg(args, name) {
|
|
14457
|
+
const value = args[name];
|
|
14458
|
+
if (typeof value !== "string")
|
|
14459
|
+
throw new Error(`${name} is required`);
|
|
14460
|
+
return value;
|
|
14192
14461
|
}
|
|
14193
|
-
|
|
14194
|
-
const
|
|
14195
|
-
|
|
14196
|
-
call: input.call,
|
|
14197
|
-
cwd: input.cwd,
|
|
14198
|
-
host: input.host,
|
|
14199
|
-
...input.sessionId === undefined ? {} : { sessionId: input.sessionId },
|
|
14200
|
-
...input.sessionTitle === undefined ? {} : { sessionTitle: input.sessionTitle },
|
|
14201
|
-
env: input.env ?? process.env,
|
|
14202
|
-
exec
|
|
14203
|
-
});
|
|
14204
|
-
return callDispatch({
|
|
14205
|
-
serviceUrl: input.serviceUrl,
|
|
14206
|
-
getToken: input.getToken ?? ghTokenGetter(input.cwd, exec),
|
|
14207
|
-
...input.fetchImpl ? { fetchImpl: input.fetchImpl } : {}
|
|
14208
|
-
}, prepared);
|
|
14462
|
+
function optionalString(args, name) {
|
|
14463
|
+
const value = args[name];
|
|
14464
|
+
return typeof value === "string" ? value : undefined;
|
|
14209
14465
|
}
|
|
14210
|
-
|
|
14211
|
-
|
|
14212
|
-
|
|
14213
|
-
|
|
14214
|
-
|
|
14215
|
-
|
|
14216
|
-
|
|
14217
|
-
|
|
14218
|
-
|
|
14219
|
-
|
|
14220
|
-
|
|
14221
|
-
|
|
14222
|
-
|
|
14223
|
-
});
|
|
14224
|
-
|
|
14225
|
-
|
|
14226
|
-
|
|
14227
|
-
|
|
14228
|
-
|
|
14229
|
-
|
|
14230
|
-
return
|
|
14466
|
+
function optionalBoolean(args, name) {
|
|
14467
|
+
const value = args[name];
|
|
14468
|
+
return typeof value === "boolean" ? value : undefined;
|
|
14469
|
+
}
|
|
14470
|
+
function optionalNumber(args, name) {
|
|
14471
|
+
const value = args[name];
|
|
14472
|
+
return typeof value === "number" ? value : undefined;
|
|
14473
|
+
}
|
|
14474
|
+
function askUrgency(args) {
|
|
14475
|
+
const value = args.urgency;
|
|
14476
|
+
return ASK_URGENCIES.find((urgency) => urgency === value);
|
|
14477
|
+
}
|
|
14478
|
+
function parseDispatchRef(ref) {
|
|
14479
|
+
const match = ref.match(/^dispatch:\/\/([A-Z][A-Z0-9]{1,9}-[1-9][0-9]*)(?:\/(spec)|\/artifact\/([^/@]+)(?:@v(\d+))?|\/ask\/([^/]+)|\/comment\/([^/]+))?$/);
|
|
14480
|
+
if (!match)
|
|
14481
|
+
return null;
|
|
14482
|
+
const [, issue, spec, artifact, version, ask, comment] = match;
|
|
14483
|
+
if (!issue || version !== undefined && Number(version) < 1)
|
|
14484
|
+
return null;
|
|
14485
|
+
if (spec)
|
|
14486
|
+
return { issue, kind: "spec", id: spec };
|
|
14487
|
+
if (artifact) {
|
|
14488
|
+
return {
|
|
14489
|
+
issue,
|
|
14490
|
+
kind: "artifact",
|
|
14491
|
+
id: artifact,
|
|
14492
|
+
...version === undefined ? {} : { version: Number(version) }
|
|
14493
|
+
};
|
|
14231
14494
|
}
|
|
14232
|
-
|
|
14495
|
+
if (ask)
|
|
14496
|
+
return { issue, kind: "ask", id: ask };
|
|
14497
|
+
if (comment)
|
|
14498
|
+
return { issue, kind: "comment", id: comment };
|
|
14499
|
+
return { issue, kind: "issue", id: issue };
|
|
14500
|
+
}
|
|
14501
|
+
function toolSchema(tool) {
|
|
14502
|
+
const spec = dispatchToolSpecs.find((candidate) => candidate.name === tool);
|
|
14503
|
+
if (!spec)
|
|
14504
|
+
throw new Error(`Unknown Dispatch tool: ${tool}`);
|
|
14505
|
+
return object(spec.arguments(zodSchemaApi(exports_external))).strict();
|
|
14506
|
+
}
|
|
14507
|
+
async function resolveIssueArguments(tool, args, cwd, env, exec) {
|
|
14508
|
+
if (tool === "dispatch_issue")
|
|
14509
|
+
return { args, ref: null };
|
|
14510
|
+
const refArgument = args.ref;
|
|
14511
|
+
const ref = typeof refArgument === "string" ? parseDispatchRef(refArgument) ?? (() => {
|
|
14512
|
+
throw new Error("ref must be a valid dispatch:// reference");
|
|
14513
|
+
})() : null;
|
|
14514
|
+
const issueArgument = args.issue;
|
|
14515
|
+
const artifactArgument = args.artifact;
|
|
14516
|
+
const versionArgument = args.version;
|
|
14517
|
+
if (issueArgument !== undefined || ref !== null) {
|
|
14518
|
+
return {
|
|
14519
|
+
args: {
|
|
14520
|
+
...args,
|
|
14521
|
+
...issueArgument === undefined && ref?.issue !== undefined ? { issue: ref.issue } : {},
|
|
14522
|
+
...artifactArgument === undefined && (ref?.kind === "spec" || ref?.kind === "artifact") ? { artifact: ref.id } : {},
|
|
14523
|
+
...versionArgument === undefined && ref?.version !== undefined ? { version: ref.version } : {}
|
|
14524
|
+
},
|
|
14525
|
+
ref
|
|
14526
|
+
};
|
|
14527
|
+
}
|
|
14528
|
+
const legionIssue = env.LEGION_ISSUE;
|
|
14529
|
+
if (!legionIssue)
|
|
14530
|
+
throw new Error("issue is required; supply issue or set LEGION_ISSUE");
|
|
14531
|
+
if (nativeIssueKeyPattern.test(legionIssue) || externalIssueRefPattern.test(legionIssue)) {
|
|
14532
|
+
return { args: { ...args, issue: legionIssue }, ref: null };
|
|
14533
|
+
}
|
|
14534
|
+
if (!bareIssueNumberPattern.test(legionIssue)) {
|
|
14535
|
+
throw new Error("LEGION_ISSUE must be a native issue key (e.g. LEGION-3), an external owner/repo#n reference, or a bare positive issue number");
|
|
14536
|
+
}
|
|
14537
|
+
const repo = await resolveCwdRepo(cwd, exec);
|
|
14538
|
+
if (!repo)
|
|
14539
|
+
throw new Error("issue is required; LEGION_ISSUE needs a GitHub repository in cwd");
|
|
14540
|
+
return { args: { ...args, issue: `${repo}#${legionIssue}` }, ref: null };
|
|
14233
14541
|
}
|
|
14234
|
-
function
|
|
14235
|
-
|
|
14236
|
-
|
|
14237
|
-
|
|
14238
|
-
|
|
14239
|
-
return { kind: "absent" };
|
|
14542
|
+
async function resolveArtifact(client, issueReference, artifactReference) {
|
|
14543
|
+
const issue = await client.getIssue(issueReference);
|
|
14544
|
+
const artifact = artifactReference === undefined || artifactReference === "spec" ? issue.artifacts.find((candidate) => candidate.primary || candidate.id === issue.primary_artifact_id) : issue.artifacts.find((candidate) => candidate.id === artifactReference || candidate.slug === artifactReference);
|
|
14545
|
+
if (!artifact) {
|
|
14546
|
+
throw new Error(`artifact ${artifactReference ?? "spec"} was not found on issue ${issue.key}`);
|
|
14240
14547
|
}
|
|
14241
|
-
|
|
14548
|
+
return { issue, artifact };
|
|
14549
|
+
}
|
|
14550
|
+
function anchor(artifact, args) {
|
|
14551
|
+
const quote = optionalString(args, "quote");
|
|
14552
|
+
if (quote === undefined)
|
|
14553
|
+
return;
|
|
14554
|
+
const occurrence = optionalNumber(args, "occurrence");
|
|
14555
|
+
return { artifact: artifact.id, quote, ...occurrence === undefined ? {} : { occurrence } };
|
|
14556
|
+
}
|
|
14557
|
+
function toolActor(origin, input) {
|
|
14558
|
+
return {
|
|
14559
|
+
kind: "session",
|
|
14560
|
+
id: input.sessionId ?? "unknown",
|
|
14561
|
+
origin: {
|
|
14562
|
+
...origin,
|
|
14563
|
+
host: input.host,
|
|
14564
|
+
...input.sessionTitle === undefined ? {} : { session_title: input.sessionTitle }
|
|
14565
|
+
}
|
|
14566
|
+
};
|
|
14567
|
+
}
|
|
14568
|
+
function issueSummary(issue, events) {
|
|
14569
|
+
const asks = issue.open_asks;
|
|
14570
|
+
return [
|
|
14571
|
+
`Title: ${issue.title}`,
|
|
14572
|
+
`Key: ${issue.key}`,
|
|
14573
|
+
`Status: ${issue.status}`,
|
|
14574
|
+
`Route: ${issue.route ?? "none"}`,
|
|
14575
|
+
"Open asks:",
|
|
14576
|
+
...asks.length === 0 ? ["- none"] : asks.map((ask) => `- ${ask.id}: ${ask.question}`),
|
|
14577
|
+
"Events:",
|
|
14578
|
+
...events.length === 0 ? ["- none"] : events.map((event) => `- #${event.seq} ${event.type} \xB7 ${event.actor.kind} ${event.actor.id} \xB7 ${event.created_at}`)
|
|
14579
|
+
].join(`
|
|
14580
|
+
`);
|
|
14581
|
+
}
|
|
14582
|
+
function askSummary(ask) {
|
|
14583
|
+
const answer = ask.answer;
|
|
14584
|
+
return [
|
|
14585
|
+
`Question: ${ask.question}`,
|
|
14586
|
+
"Options:",
|
|
14587
|
+
...ask.options.length === 0 ? ["- none"] : ask.options.map((option) => `- ${option.label}${option.description ? ` \u2014 ${option.description}` : ""}`),
|
|
14588
|
+
`State: ${ask.state}`,
|
|
14589
|
+
"Answer:",
|
|
14590
|
+
...answer === null ? ["- none"] : [
|
|
14591
|
+
`- By: ${answer.user}`,
|
|
14592
|
+
`- Selected: ${answer.selected.length === 0 ? "none" : answer.selected.join(", ")}`,
|
|
14593
|
+
...answer.text === null ? [] : [`- Text: ${answer.text}`]
|
|
14594
|
+
]
|
|
14595
|
+
].join(`
|
|
14596
|
+
`);
|
|
14597
|
+
}
|
|
14598
|
+
function commentSummary({ comment, replies }) {
|
|
14599
|
+
const root = [
|
|
14600
|
+
`${comment.id} \xB7 ${comment.author.kind} ${comment.author.id}`,
|
|
14601
|
+
...comment.anchor?.quote === undefined ? [] : [`> ${comment.anchor.quote}`],
|
|
14602
|
+
`Body: ${comment.body}`
|
|
14603
|
+
];
|
|
14604
|
+
const chain = replies.flatMap((reply) => [
|
|
14605
|
+
`${reply.id} \xB7 ${reply.author.kind} ${reply.author.id}`,
|
|
14606
|
+
...reply.anchor?.quote === undefined ? [] : [`> ${reply.anchor.quote}`],
|
|
14607
|
+
`Body: ${reply.body}`
|
|
14608
|
+
]);
|
|
14609
|
+
return ["Comment:", ...root, "Reply chain:", ...chain.length === 0 ? ["- none"] : chain].join(`
|
|
14610
|
+
`);
|
|
14611
|
+
}
|
|
14612
|
+
async function openArtifactMarks(client, resolved) {
|
|
14613
|
+
const marks = resolved.issue.open_asks.filter((ask) => ask.state === "open" && ask.anchor?.artifact_id === resolved.artifact.id).map((ask) => `ask ${ask.id}`);
|
|
14614
|
+
let comments;
|
|
14242
14615
|
try {
|
|
14243
|
-
|
|
14244
|
-
} catch (
|
|
14245
|
-
|
|
14616
|
+
comments = await client.getComments(resolved.issue.key, resolved.artifact.id);
|
|
14617
|
+
} catch (error) {
|
|
14618
|
+
if (error instanceof DispatchServiceError && error.status === 404)
|
|
14619
|
+
return marks;
|
|
14620
|
+
throw error;
|
|
14246
14621
|
}
|
|
14247
|
-
|
|
14248
|
-
|
|
14249
|
-
|
|
14622
|
+
return [
|
|
14623
|
+
...marks,
|
|
14624
|
+
...comments.filter((comment) => !comment.resolved && comment.anchor?.artifact_id === resolved.artifact.id).map((comment) => `comment ${comment.id}`)
|
|
14625
|
+
];
|
|
14626
|
+
}
|
|
14627
|
+
async function executeDispatchTool(input) {
|
|
14628
|
+
if (!input.config.enabled || !input.config.url || !input.config.token) {
|
|
14629
|
+
throw new Error("Dispatch is disabled; resolve both DISPATCH_URL and DISPATCH_TOKEN");
|
|
14630
|
+
}
|
|
14631
|
+
const env = input.env ?? process.env;
|
|
14632
|
+
const exec = input.exec ?? defaultExec;
|
|
14633
|
+
const issueArguments = await resolveIssueArguments(input.tool, input.args, input.cwd, env, exec);
|
|
14634
|
+
const args = toolSchema(input.tool).parse(issueArguments.args);
|
|
14635
|
+
const actor = toolActor(await resolveOrigin(env, exec, input.cwd), input);
|
|
14636
|
+
const client = new DispatchClient(input.config.url, input.config.token, input.fetchImpl);
|
|
14637
|
+
const issueKey = input.tool === "dispatch_issue" ? null : await ensureIssue(client, stringArg(args, "issue"), actor);
|
|
14638
|
+
const issue = () => {
|
|
14639
|
+
if (issueKey === null)
|
|
14640
|
+
throw new Error("issue is required");
|
|
14641
|
+
return issueKey;
|
|
14642
|
+
};
|
|
14643
|
+
switch (input.tool) {
|
|
14644
|
+
case "dispatch_issue": {
|
|
14645
|
+
const parent = optionalString(args, "parent");
|
|
14646
|
+
const external = optionalString(args, "external");
|
|
14647
|
+
const spec = optionalString(args, "spec");
|
|
14648
|
+
const created = await client.issue({
|
|
14649
|
+
project: stringArg(args, "project"),
|
|
14650
|
+
title: stringArg(args, "title"),
|
|
14651
|
+
...parent === undefined ? {} : { parent },
|
|
14652
|
+
...external === undefined ? {} : { external },
|
|
14653
|
+
...spec === undefined ? {} : { spec },
|
|
14654
|
+
actor
|
|
14655
|
+
});
|
|
14656
|
+
return {
|
|
14657
|
+
text: `Created ${created.key}: ${created.title}`,
|
|
14658
|
+
details: { issue: created.key, topic: dispatchTopic(created.key) }
|
|
14659
|
+
};
|
|
14660
|
+
}
|
|
14661
|
+
case "dispatch_ask": {
|
|
14662
|
+
const anchorArgs = asObject(args.anchor);
|
|
14663
|
+
const resolved = anchorArgs ? await resolveArtifact(client, issue(), stringArg(anchorArgs, "artifact")) : undefined;
|
|
14664
|
+
const options = args.options;
|
|
14665
|
+
const multiple = optionalBoolean(args, "multiple");
|
|
14666
|
+
const custom = optionalBoolean(args, "custom");
|
|
14667
|
+
const urgency = askUrgency(args);
|
|
14668
|
+
const anchored = anchorArgs && resolved ? anchor(resolved.artifact, anchorArgs) : undefined;
|
|
14669
|
+
const ask = await client.ask(issue(), {
|
|
14670
|
+
question: stringArg(args, "question"),
|
|
14671
|
+
...Array.isArray(options) ? { options } : {},
|
|
14672
|
+
...multiple === undefined ? {} : { multiple },
|
|
14673
|
+
...custom === undefined ? {} : { custom },
|
|
14674
|
+
...urgency === undefined ? {} : { urgency },
|
|
14675
|
+
...anchored === undefined ? {} : { anchor: anchored },
|
|
14676
|
+
actor
|
|
14677
|
+
});
|
|
14678
|
+
return {
|
|
14679
|
+
text: `Opened ask ${ask.id}: ${ask.question}`,
|
|
14680
|
+
details: { issue: ask.issue_key, topic: dispatchTopic(ask.issue_key), ask: ask.id }
|
|
14681
|
+
};
|
|
14682
|
+
}
|
|
14683
|
+
case "dispatch_comment": {
|
|
14684
|
+
const artifactReference = optionalString(args, "artifact");
|
|
14685
|
+
if (optionalString(args, "quote") !== undefined && artifactReference === undefined) {
|
|
14686
|
+
throw new Error("artifact is required when quote is supplied");
|
|
14687
|
+
}
|
|
14688
|
+
const resolved = artifactReference ? await resolveArtifact(client, issue(), artifactReference) : undefined;
|
|
14689
|
+
const anchored = resolved ? anchor(resolved.artifact, args) : undefined;
|
|
14690
|
+
const replyTo = optionalString(args, "reply_to");
|
|
14691
|
+
const comment = await client.comment(issue(), {
|
|
14692
|
+
body: stringArg(args, "body"),
|
|
14693
|
+
...anchored === undefined ? {} : { anchor: anchored },
|
|
14694
|
+
...replyTo === undefined ? {} : { reply_to: replyTo },
|
|
14695
|
+
actor
|
|
14696
|
+
});
|
|
14697
|
+
return {
|
|
14698
|
+
text: `Posted comment ${comment.id}`,
|
|
14699
|
+
details: {
|
|
14700
|
+
issue: comment.issue_key,
|
|
14701
|
+
topic: dispatchTopic(comment.issue_key),
|
|
14702
|
+
comment: comment.id
|
|
14703
|
+
}
|
|
14704
|
+
};
|
|
14705
|
+
}
|
|
14706
|
+
case "dispatch_suggest": {
|
|
14707
|
+
const resolved = await resolveArtifact(client, issue(), stringArg(args, "artifact"));
|
|
14708
|
+
const anchored = anchor(resolved.artifact, args);
|
|
14709
|
+
if (anchored === undefined)
|
|
14710
|
+
throw new Error("quote is required");
|
|
14711
|
+
const body = optionalString(args, "body");
|
|
14712
|
+
const comment = await client.suggest(issue(), {
|
|
14713
|
+
...body === undefined ? {} : { body },
|
|
14714
|
+
anchor: anchored,
|
|
14715
|
+
replace_with: stringArg(args, "replace_with"),
|
|
14716
|
+
actor
|
|
14717
|
+
});
|
|
14718
|
+
return {
|
|
14719
|
+
text: `Posted suggestion ${comment.id}`,
|
|
14720
|
+
details: {
|
|
14721
|
+
issue: comment.issue_key,
|
|
14722
|
+
topic: dispatchTopic(comment.issue_key),
|
|
14723
|
+
comment: comment.id
|
|
14724
|
+
}
|
|
14725
|
+
};
|
|
14726
|
+
}
|
|
14727
|
+
case "dispatch_message": {
|
|
14728
|
+
const message = await client.message(issue(), { body: stringArg(args, "body"), actor });
|
|
14729
|
+
return {
|
|
14730
|
+
text: `Posted message ${message.id}`,
|
|
14731
|
+
details: {
|
|
14732
|
+
issue: message.issue_key,
|
|
14733
|
+
topic: dispatchTopic(message.issue_key),
|
|
14734
|
+
message: message.id
|
|
14735
|
+
}
|
|
14736
|
+
};
|
|
14737
|
+
}
|
|
14738
|
+
case "dispatch_doc_edit": {
|
|
14739
|
+
const resolved = await resolveArtifact(client, issue(), stringArg(args, "artifact"));
|
|
14740
|
+
const ops = args.ops;
|
|
14741
|
+
const summary = optionalString(args, "summary");
|
|
14742
|
+
const edited = await client.docEdit(resolved.artifact.id, {
|
|
14743
|
+
ops,
|
|
14744
|
+
...summary === undefined ? {} : { summary },
|
|
14745
|
+
actor
|
|
14746
|
+
});
|
|
14747
|
+
return {
|
|
14748
|
+
text: edited.version === null ? `Applied ${edited.applied} ops (no new version)` : `Applied ${edited.applied} ops (version ${edited.version.number})`,
|
|
14749
|
+
details: {
|
|
14750
|
+
issue: resolved.issue.key,
|
|
14751
|
+
topic: dispatchTopic(resolved.issue.key),
|
|
14752
|
+
applied: edited.applied,
|
|
14753
|
+
...edited.version === null ? {} : { version: edited.version.number }
|
|
14754
|
+
}
|
|
14755
|
+
};
|
|
14756
|
+
}
|
|
14757
|
+
case "dispatch_doc_read": {
|
|
14758
|
+
const artifactReference = optionalString(args, "artifact") ?? (issueArguments.ref?.kind === "spec" || issueArguments.ref?.kind === "artifact" ? issueArguments.ref.id : undefined);
|
|
14759
|
+
const resolved = await resolveArtifact(client, issue(), artifactReference);
|
|
14760
|
+
const version = optionalNumber(args, "version") ?? issueArguments.ref?.version;
|
|
14761
|
+
const document = await client.docRead(resolved.artifact.id, version);
|
|
14762
|
+
const marks = await openArtifactMarks(client, resolved);
|
|
14763
|
+
return {
|
|
14764
|
+
text: marks.length === 0 ? document.markdown : `${document.markdown}
|
|
14765
|
+
|
|
14766
|
+
Open anchored asks/comments: ${marks.join(", ")}`,
|
|
14767
|
+
details: { issue: resolved.issue.key, topic: dispatchTopic(resolved.issue.key) }
|
|
14768
|
+
};
|
|
14769
|
+
}
|
|
14770
|
+
case "dispatch_artifact": {
|
|
14771
|
+
const primary = optionalBoolean(args, "primary");
|
|
14772
|
+
const summary = optionalString(args, "summary");
|
|
14773
|
+
const result = await client.artifact(issue(), {
|
|
14774
|
+
name: stringArg(args, "name"),
|
|
14775
|
+
file: Bun.file(resolvePath(input.cwd, stringArg(args, "path"))),
|
|
14776
|
+
...primary === undefined ? {} : { primary },
|
|
14777
|
+
...summary === undefined ? {} : { summary },
|
|
14778
|
+
actor
|
|
14779
|
+
});
|
|
14780
|
+
return {
|
|
14781
|
+
text: `Uploaded ${result.artifact.name} as version ${result.version.number}`,
|
|
14782
|
+
details: {
|
|
14783
|
+
issue: result.artifact.issue_key,
|
|
14784
|
+
topic: dispatchTopic(result.artifact.issue_key),
|
|
14785
|
+
artifact: result.artifact.id,
|
|
14786
|
+
version: result.version.number
|
|
14787
|
+
}
|
|
14788
|
+
};
|
|
14789
|
+
}
|
|
14790
|
+
case "dispatch_read": {
|
|
14791
|
+
if (issueArguments.ref?.kind === "ask") {
|
|
14792
|
+
const ask = await client.getAsk(issueArguments.ref.id);
|
|
14793
|
+
return {
|
|
14794
|
+
text: askSummary(ask),
|
|
14795
|
+
details: { issue: ask.issue_key, topic: dispatchTopic(ask.issue_key) }
|
|
14796
|
+
};
|
|
14797
|
+
}
|
|
14798
|
+
if (issueArguments.ref?.kind === "comment") {
|
|
14799
|
+
const comment = await client.getComment(issueArguments.ref.id);
|
|
14800
|
+
return {
|
|
14801
|
+
text: commentSummary(comment),
|
|
14802
|
+
details: {
|
|
14803
|
+
issue: comment.comment.issue_key,
|
|
14804
|
+
topic: dispatchTopic(comment.comment.issue_key)
|
|
14805
|
+
}
|
|
14806
|
+
};
|
|
14807
|
+
}
|
|
14808
|
+
const read = await client.read(issue());
|
|
14809
|
+
return {
|
|
14810
|
+
text: issueSummary(read.issue, read.events),
|
|
14811
|
+
details: { issue: read.issue.key, topic: dispatchTopic(read.issue.key) }
|
|
14812
|
+
};
|
|
14813
|
+
}
|
|
14814
|
+
default:
|
|
14815
|
+
throw new Error(`Unknown Dispatch tool: ${input.tool}`);
|
|
14250
14816
|
}
|
|
14251
|
-
return { kind: "valid", settings: parsed.data.dispatch ?? null };
|
|
14252
14817
|
}
|
|
14253
|
-
function
|
|
14254
|
-
|
|
14255
|
-
|
|
14256
|
-
|
|
14257
|
-
|
|
14258
|
-
|
|
14259
|
-
|
|
14260
|
-
|
|
14261
|
-
|
|
14262
|
-
if (file.kind === "invalid")
|
|
14263
|
-
return { url: null, error: file.reason };
|
|
14818
|
+
async function ensureIssue(client, issueReference, actor) {
|
|
14819
|
+
try {
|
|
14820
|
+
return await client.ensureIssue(issueReference, actor);
|
|
14821
|
+
} catch (error) {
|
|
14822
|
+
if (error instanceof DispatchServiceError && error.code === "PROJECT_UNMAPPED") {
|
|
14823
|
+
const repository = issueReference.slice(0, issueReference.lastIndexOf("#"));
|
|
14824
|
+
throw new Error(`repository ${repository} is not mapped in DISPATCH_REPO_PROJECTS`);
|
|
14825
|
+
}
|
|
14826
|
+
throw error;
|
|
14264
14827
|
}
|
|
14265
|
-
|
|
14266
|
-
|
|
14267
|
-
|
|
14268
|
-
};
|
|
14269
|
-
if (merged.enabled !== true)
|
|
14270
|
-
return { url: null, error: null };
|
|
14271
|
-
const baseUrl = (merged.serverUrl ?? DEFAULT_SERVER_URL).replace(/\/+$/, "");
|
|
14272
|
-
return { url: `${baseUrl}/mcp`, error: null };
|
|
14828
|
+
}
|
|
14829
|
+
function asObject(value) {
|
|
14830
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
14273
14831
|
}
|
|
14274
14832
|
|
|
14275
14833
|
// ../envoy-client/src/dispatch-subscribe.ts
|
|
14276
|
-
|
|
14277
|
-
|
|
14278
|
-
return tool === DISPATCH_TOOL_NAME;
|
|
14279
|
-
}
|
|
14280
|
-
function dispatchThreadTopic(owner, repo, thread) {
|
|
14281
|
-
return `notifications.github.${owner}.${repo}.issue.${thread}.>`;
|
|
14282
|
-
}
|
|
14283
|
-
function dispatchSubscriptionTopic(tool, output) {
|
|
14284
|
-
if (!isDispatchTool(tool))
|
|
14285
|
-
return null;
|
|
14286
|
-
const match = ISSUE_URL_RE.exec(output);
|
|
14287
|
-
if (!match)
|
|
14288
|
-
return null;
|
|
14289
|
-
const owner = match[1];
|
|
14290
|
-
const repo = match[2];
|
|
14291
|
-
const thread = Number(match[3]);
|
|
14292
|
-
if (!Number.isInteger(thread) || thread <= 0)
|
|
14834
|
+
function dispatchSubscriptionTopic(details) {
|
|
14835
|
+
if (typeof details !== "object" || details === null || !("topic" in details))
|
|
14293
14836
|
return null;
|
|
14294
|
-
|
|
14837
|
+
const { topic } = details;
|
|
14838
|
+
return typeof topic === "string" && topic.startsWith("notifications.dispatch.") ? topic : null;
|
|
14295
14839
|
}
|
|
14296
14840
|
|
|
14297
14841
|
// ../envoy-client/src/tool-contract.ts
|
|
@@ -14305,10 +14849,19 @@ function messageMetadataShape(schema) {
|
|
|
14305
14849
|
supersedes: schema.string().optional(),
|
|
14306
14850
|
urgency: schema.enum(URGENCY_VALUES).optional(),
|
|
14307
14851
|
expects_reply: schema.enum(EXPECTS_REPLY_VALUES).optional(),
|
|
14308
|
-
expires_at: schema.number(
|
|
14852
|
+
expires_at: schema.number({ int: true }).optional()
|
|
14853
|
+
};
|
|
14854
|
+
}
|
|
14855
|
+
var MessageMetadataSchema = object(messageMetadataShape(zodSchemaApi(exports_external)));
|
|
14856
|
+
function toMessageMetadata(args) {
|
|
14857
|
+
return {
|
|
14858
|
+
...args.in_reply_to === undefined ? {} : { inReplyTo: args.in_reply_to },
|
|
14859
|
+
...args.supersedes === undefined ? {} : { supersedes: args.supersedes },
|
|
14860
|
+
...args.urgency === undefined ? {} : { urgency: args.urgency },
|
|
14861
|
+
...args.expects_reply === undefined ? {} : { expectsReply: args.expects_reply },
|
|
14862
|
+
...args.expires_at === undefined ? {} : { expiresAt: args.expires_at }
|
|
14309
14863
|
};
|
|
14310
14864
|
}
|
|
14311
|
-
var MessageMetadataSchema = object(messageMetadataShape(exports_external));
|
|
14312
14865
|
function messageArguments(schema) {
|
|
14313
14866
|
return { message: schema.string(), ...messageMetadataShape(schema) };
|
|
14314
14867
|
}
|
|
@@ -14698,33 +15251,12 @@ var publishSpec = toolSpec("envoy_publish");
|
|
|
14698
15251
|
var roleSetSpec = toolSpec("envoy_role_set");
|
|
14699
15252
|
var whoamiSpec = toolSpec("envoy_whoami");
|
|
14700
15253
|
var sessionsSpec = toolSpec("envoy_sessions");
|
|
14701
|
-
var dispatchQuestionOption = tool.schema.strictObject({
|
|
14702
|
-
label: tool.schema.string().min(1),
|
|
14703
|
-
description: tool.schema.string().optional()
|
|
14704
|
-
});
|
|
14705
|
-
var dispatchQuestion = tool.schema.strictObject({
|
|
14706
|
-
question: tool.schema.string().min(1),
|
|
14707
|
-
header: tool.schema.string().optional(),
|
|
14708
|
-
options: tool.schema.array(dispatchQuestionOption).optional(),
|
|
14709
|
-
multiple: tool.schema.boolean().optional(),
|
|
14710
|
-
custom: tool.schema.boolean().optional()
|
|
14711
|
-
});
|
|
14712
|
-
var dispatchArgs = {
|
|
14713
|
-
subject: tool.schema.string().describe(DISPATCH_ARGUMENTS.subject).optional(),
|
|
14714
|
-
thread: tool.schema.string().describe(DISPATCH_ARGUMENTS.thread).optional(),
|
|
14715
|
-
context: tool.schema.string().describe(DISPATCH_ARGUMENTS.context),
|
|
14716
|
-
question: tool.schema.string().describe(DISPATCH_ARGUMENTS.question),
|
|
14717
|
-
ask: tool.schema.array(dispatchQuestion).describe(DISPATCH_ARGUMENTS.ask).optional(),
|
|
14718
|
-
urgency: tool.schema.enum(DISPATCH_URGENCIES).describe(DISPATCH_ARGUMENTS.urgency).optional(),
|
|
14719
|
-
repo: tool.schema.string().describe(DISPATCH_ARGUMENTS.repo).optional(),
|
|
14720
|
-
parent: tool.schema.string().describe(DISPATCH_ARGUMENTS.parent).optional()
|
|
14721
|
-
};
|
|
14722
15254
|
var server_default = async (input) => {
|
|
14723
15255
|
const cwd = process.cwd();
|
|
14724
15256
|
const dispatchConfig = resolveDispatchConfig(process.env, { cwd });
|
|
14725
|
-
if (dispatchConfig.
|
|
14726
|
-
|
|
14727
|
-
|
|
15257
|
+
if (!dispatchConfig.enabled) {
|
|
15258
|
+
logger.warn(`envoy: dispatch tools disabled \u2014 ${dispatchConfig.error ?? "no Dispatch URL configured"}`);
|
|
15259
|
+
}
|
|
14728
15260
|
const envoyDefaults = envoyDefaultsFromEnvironment(process.env);
|
|
14729
15261
|
const envoy = createEnvoyClient({ baseUrl: envoyDefaults.envoyUrl, fetch: globalThis.fetch });
|
|
14730
15262
|
let activeSessionID = null;
|
|
@@ -14795,25 +15327,29 @@ var server_default = async (input) => {
|
|
|
14795
15327
|
clearInterval(timer);
|
|
14796
15328
|
clearInterval(heartbeatInterval);
|
|
14797
15329
|
});
|
|
14798
|
-
const
|
|
14799
|
-
|
|
14800
|
-
|
|
14801
|
-
|
|
14802
|
-
|
|
14803
|
-
|
|
14804
|
-
|
|
14805
|
-
|
|
14806
|
-
|
|
14807
|
-
|
|
14808
|
-
|
|
14809
|
-
|
|
14810
|
-
|
|
14811
|
-
|
|
14812
|
-
|
|
14813
|
-
|
|
14814
|
-
|
|
14815
|
-
|
|
14816
|
-
|
|
15330
|
+
const dispatchTools = {};
|
|
15331
|
+
if (dispatchConfig.enabled) {
|
|
15332
|
+
for (const spec of dispatchToolSpecs) {
|
|
15333
|
+
dispatchTools[spec.name] = tool({
|
|
15334
|
+
description: spec.description,
|
|
15335
|
+
args: spec.arguments(zodSchemaApi(tool.schema)),
|
|
15336
|
+
async execute(args, ctx) {
|
|
15337
|
+
ctx.metadata({ title: "Dispatch" });
|
|
15338
|
+
const result = await executeDispatchTool({
|
|
15339
|
+
tool: spec.name,
|
|
15340
|
+
args,
|
|
15341
|
+
cwd: ctx.directory,
|
|
15342
|
+
host: "opencode",
|
|
15343
|
+
sessionId: ctx.sessionID,
|
|
15344
|
+
sessionTitle: await fetchTitle(ctx.sessionID) ?? undefined,
|
|
15345
|
+
config: dispatchConfig,
|
|
15346
|
+
env: process.env
|
|
15347
|
+
});
|
|
15348
|
+
return { title: "Dispatch", output: result.text, metadata: result.details };
|
|
15349
|
+
}
|
|
15350
|
+
});
|
|
15351
|
+
}
|
|
15352
|
+
}
|
|
14817
15353
|
return {
|
|
14818
15354
|
config: (cfg) => {
|
|
14819
15355
|
if (skillsDirectory) {
|
|
@@ -14874,7 +15410,7 @@ var server_default = async (input) => {
|
|
|
14874
15410
|
}
|
|
14875
15411
|
},
|
|
14876
15412
|
"tool.execute.after": async (input, output) => {
|
|
14877
|
-
const topic = dispatchSubscriptionTopic(
|
|
15413
|
+
const topic = dispatchSubscriptionTopic(output.metadata);
|
|
14878
15414
|
if (!topic)
|
|
14879
15415
|
return;
|
|
14880
15416
|
try {
|
|
@@ -14896,10 +15432,10 @@ var server_default = async (input) => {
|
|
|
14896
15432
|
clearInterval(heartbeatInterval);
|
|
14897
15433
|
},
|
|
14898
15434
|
tool: {
|
|
14899
|
-
...
|
|
15435
|
+
...dispatchTools,
|
|
14900
15436
|
envoy_subscribe: tool({
|
|
14901
15437
|
description: subscribeSpec.description,
|
|
14902
|
-
args:
|
|
15438
|
+
args: subscribeSpec.arguments(zodSchemaApi(tool.schema)),
|
|
14903
15439
|
async execute(args, ctx) {
|
|
14904
15440
|
ctx.metadata({ title: "Envoy subscribe" });
|
|
14905
15441
|
return JSON.stringify(await envoy.subscribe({
|
|
@@ -14914,16 +15450,19 @@ var server_default = async (input) => {
|
|
|
14914
15450
|
}),
|
|
14915
15451
|
envoy_unsubscribe: tool({
|
|
14916
15452
|
description: unsubscribeSpec.description,
|
|
14917
|
-
args:
|
|
15453
|
+
args: unsubscribeSpec.arguments(zodSchemaApi(tool.schema)),
|
|
14918
15454
|
async execute(args, ctx) {
|
|
14919
15455
|
ctx.metadata({ title: "Envoy unsubscribe" });
|
|
14920
|
-
await envoy.unsubscribe({
|
|
15456
|
+
await envoy.unsubscribe({
|
|
15457
|
+
sessionID: ctx.sessionID,
|
|
15458
|
+
topics: args.topics ?? []
|
|
15459
|
+
});
|
|
14921
15460
|
return "ok";
|
|
14922
15461
|
}
|
|
14923
15462
|
}),
|
|
14924
15463
|
envoy_list: tool({
|
|
14925
15464
|
description: listSpec.description,
|
|
14926
|
-
args:
|
|
15465
|
+
args: listSpec.arguments(zodSchemaApi(tool.schema)),
|
|
14927
15466
|
async execute(_args, ctx) {
|
|
14928
15467
|
ctx.metadata({ title: "Envoy list" });
|
|
14929
15468
|
return JSON.stringify(await envoy.getInterest(ctx.sessionID));
|
|
@@ -14931,31 +15470,33 @@ var server_default = async (input) => {
|
|
|
14931
15470
|
}),
|
|
14932
15471
|
envoy_send: tool({
|
|
14933
15472
|
description: sendSpec.description,
|
|
14934
|
-
args:
|
|
15473
|
+
args: sendSpec.arguments(zodSchemaApi(tool.schema)),
|
|
14935
15474
|
async execute(args, ctx) {
|
|
14936
15475
|
ctx.metadata({ title: "Envoy send" });
|
|
14937
15476
|
return JSON.stringify(await envoy.send({
|
|
14938
15477
|
sourceSessionID: ctx.sessionID,
|
|
14939
15478
|
targetSessionID: args.session_id,
|
|
14940
|
-
message: args.message
|
|
15479
|
+
message: args.message,
|
|
15480
|
+
...toMessageMetadata(args)
|
|
14941
15481
|
}));
|
|
14942
15482
|
}
|
|
14943
15483
|
}),
|
|
14944
15484
|
envoy_publish: tool({
|
|
14945
15485
|
description: publishSpec.description,
|
|
14946
|
-
args:
|
|
15486
|
+
args: publishSpec.arguments(zodSchemaApi(tool.schema)),
|
|
14947
15487
|
async execute(args, ctx) {
|
|
14948
15488
|
ctx.metadata({ title: "Envoy publish" });
|
|
14949
15489
|
return JSON.stringify(await envoy.publish({
|
|
14950
15490
|
sourceSessionID: ctx.sessionID,
|
|
14951
15491
|
topic: args.topic,
|
|
14952
|
-
message: args.message
|
|
15492
|
+
message: args.message,
|
|
15493
|
+
...toMessageMetadata(args)
|
|
14953
15494
|
}));
|
|
14954
15495
|
}
|
|
14955
15496
|
}),
|
|
14956
15497
|
envoy_role_set: tool({
|
|
14957
15498
|
description: roleSetSpec.description,
|
|
14958
|
-
args:
|
|
15499
|
+
args: roleSetSpec.arguments(zodSchemaApi(tool.schema)),
|
|
14959
15500
|
async execute(args, ctx) {
|
|
14960
15501
|
ctx.metadata({ title: "Set Envoy role" });
|
|
14961
15502
|
return JSON.stringify(await envoy.setRole({ sessionID: ctx.sessionID, role: args.role }));
|
|
@@ -14963,7 +15504,7 @@ var server_default = async (input) => {
|
|
|
14963
15504
|
}),
|
|
14964
15505
|
envoy_whoami: tool({
|
|
14965
15506
|
description: whoamiSpec.description,
|
|
14966
|
-
args:
|
|
15507
|
+
args: whoamiSpec.arguments(zodSchemaApi(tool.schema)),
|
|
14967
15508
|
async execute(_args, ctx) {
|
|
14968
15509
|
ctx.metadata({ title: "Envoy whoami" });
|
|
14969
15510
|
const sessionID = ctx.sessionID;
|
|
@@ -14978,11 +15519,15 @@ var server_default = async (input) => {
|
|
|
14978
15519
|
}),
|
|
14979
15520
|
envoy_sessions: tool({
|
|
14980
15521
|
description: sessionsSpec.description,
|
|
14981
|
-
args:
|
|
15522
|
+
args: sessionsSpec.arguments(zodSchemaApi(tool.schema)),
|
|
14982
15523
|
async execute(args, ctx) {
|
|
14983
15524
|
ctx.metadata({ title: "Envoy sessions" });
|
|
14984
|
-
const sessions = await envoy.listSessions(
|
|
14985
|
-
|
|
15525
|
+
const sessions = await envoy.listSessions({
|
|
15526
|
+
...args.dir === undefined ? {} : { directory: args.dir },
|
|
15527
|
+
...args.title === undefined ? {} : { title: args.title }
|
|
15528
|
+
});
|
|
15529
|
+
const machine = args.machine;
|
|
15530
|
+
return JSON.stringify(machine ? sessions.filter((session) => session.machine_id === machine) : sessions, null, 2);
|
|
14986
15531
|
}
|
|
14987
15532
|
})
|
|
14988
15533
|
}
|