@sjawhar/opencode-legion-envoy 0.40.0 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/server.js +319 -107
- package/package.json +1 -1
- package/skills/dispatch/SKILL.md +51 -43
package/dist/src/server.js
CHANGED
|
@@ -13673,6 +13673,18 @@ function dispatchToolSchema(spec, z, opts) {
|
|
|
13673
13673
|
return spec.validation === undefined ? z.object(shape, opts) : z.refineObject(shape, spec.validation.check, spec.validation.message, opts);
|
|
13674
13674
|
}
|
|
13675
13675
|
var ISSUE_REFERENCE = "An issue is a native KEY or external owner/repo#n reference; an external reference creates its native issue in the repository's dashboard-configured project or, failing that, the default project (DISPATCH_DEFAULT_PROJECT).";
|
|
13676
|
+
var OWNER_REFERENCE = "Exactly one of issue and project is required. An issue is a native KEY or external owner/repo#n reference; a project is a project key such as CORE and addresses an unlinked project document named by artifact.";
|
|
13677
|
+
function documentOwnerValidation(requireArtifact) {
|
|
13678
|
+
return {
|
|
13679
|
+
check: (value) => {
|
|
13680
|
+
const input = value;
|
|
13681
|
+
const hasIssue = typeof input.issue === "string";
|
|
13682
|
+
const hasProject = typeof input.project === "string";
|
|
13683
|
+
return (hasIssue !== hasProject || !hasIssue && !hasProject && typeof input.ref === "string") && (!hasProject || !requireArtifact || typeof input.artifact === "string");
|
|
13684
|
+
},
|
|
13685
|
+
message: "Exactly one of issue and project is required; with project, artifact names the document."
|
|
13686
|
+
};
|
|
13687
|
+
}
|
|
13676
13688
|
var SPEC_SECTIONS = [
|
|
13677
13689
|
"Decisions needed",
|
|
13678
13690
|
"Acceptance",
|
|
@@ -13700,9 +13712,11 @@ var dispatchToolSpecs = [
|
|
|
13700
13712
|
},
|
|
13701
13713
|
{
|
|
13702
13714
|
name: "dispatch_ask",
|
|
13703
|
-
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. ${
|
|
13715
|
+
description: "Open a durable, answerable decision on an issue or project document. 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. ${OWNER_REFERENCE}`,
|
|
13704
13716
|
arguments: (z) => ({
|
|
13705
|
-
issue: z.string().describe(ISSUE_REFERENCE),
|
|
13717
|
+
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
13718
|
+
project: z.string().describe("Project key owning the document.").optional(),
|
|
13719
|
+
artifact: z.string().describe("Project document slug or id.").optional(),
|
|
13706
13720
|
question: z.string({ max: 800 }).describe("Decision question, at most 800 characters."),
|
|
13707
13721
|
options: z.array(z.object({
|
|
13708
13722
|
label: z.string().describe("Selectable option label."),
|
|
@@ -13715,7 +13729,8 @@ var dispatchToolSpecs = [
|
|
|
13715
13729
|
quote: z.string().describe("Exact text the decision concerns."),
|
|
13716
13730
|
occurrence: z.number({ int: true, min: 0 }).describe("Zero-based occurrence of the quote.").optional()
|
|
13717
13731
|
}).describe("Optional document location for the question.").optional()
|
|
13718
|
-
})
|
|
13732
|
+
}),
|
|
13733
|
+
validation: documentOwnerValidation(true)
|
|
13719
13734
|
},
|
|
13720
13735
|
{
|
|
13721
13736
|
name: "dispatch_edit_ask",
|
|
@@ -13749,28 +13764,32 @@ var dispatchToolSpecs = [
|
|
|
13749
13764
|
},
|
|
13750
13765
|
{
|
|
13751
13766
|
name: "dispatch_comment",
|
|
13752
|
-
description: "Add review feedback to an issue or document quote, or reply to a question asked with dispatch_ask. " + "Do not use it for an exact replacement; use " + `dispatch_suggest instead. Body is at most 2,000 characters. ${
|
|
13767
|
+
description: "Add review feedback to an issue or project document quote, or reply to a question asked with dispatch_ask. " + "Do not use it for an exact replacement; use " + `dispatch_suggest instead. Body is at most 2,000 characters. ${OWNER_REFERENCE}`,
|
|
13753
13768
|
arguments: (z) => ({
|
|
13754
|
-
issue: z.string().describe(ISSUE_REFERENCE),
|
|
13769
|
+
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
13770
|
+
project: z.string().describe("Project key owning the document.").optional(),
|
|
13755
13771
|
artifact: z.string().describe("Artifact slug or id required when quote is given.").optional(),
|
|
13756
13772
|
quote: z.string().describe("Optional exact quoted document text.").optional(),
|
|
13757
13773
|
occurrence: z.number({ int: true, min: 0 }).describe("Optional zero-based occurrence of quote.").optional(),
|
|
13758
13774
|
body: z.string({ max: 2000 }).describe("Review comment, at most 2,000 characters."),
|
|
13759
13775
|
reply_to: z.string().describe("Optional comment id to reply to.").optional(),
|
|
13760
13776
|
reply_to_ask: z.string().describe("Optional ask id to reply to, threading this comment under that question. Mutually " + "exclusive with reply_to.").optional()
|
|
13761
|
-
})
|
|
13777
|
+
}),
|
|
13778
|
+
validation: documentOwnerValidation(true)
|
|
13762
13779
|
},
|
|
13763
13780
|
{
|
|
13764
13781
|
name: "dispatch_suggest",
|
|
13765
|
-
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. ${
|
|
13782
|
+
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. ${OWNER_REFERENCE}`,
|
|
13766
13783
|
arguments: (z) => ({
|
|
13767
|
-
issue: z.string().describe(ISSUE_REFERENCE),
|
|
13784
|
+
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
13785
|
+
project: z.string().describe("Project key owning the document.").optional(),
|
|
13768
13786
|
artifact: z.string().describe("Artifact slug or id containing the quoted text."),
|
|
13769
13787
|
quote: z.string().describe("Exact document text to replace."),
|
|
13770
13788
|
replace_with: z.string().describe("Replacement text."),
|
|
13771
13789
|
body: z.string({ max: 2000 }).describe("Optional rationale, at most 2,000 characters.").optional(),
|
|
13772
13790
|
occurrence: z.number({ int: true, min: 0 }).describe("Optional zero-based occurrence of quote.").optional()
|
|
13773
|
-
})
|
|
13791
|
+
}),
|
|
13792
|
+
validation: documentOwnerValidation(true)
|
|
13774
13793
|
},
|
|
13775
13794
|
{
|
|
13776
13795
|
name: "dispatch_message",
|
|
@@ -13782,9 +13801,10 @@ var dispatchToolSpecs = [
|
|
|
13782
13801
|
},
|
|
13783
13802
|
{
|
|
13784
13803
|
name: "dispatch_doc_edit",
|
|
13785
|
-
description: "Apply deterministic text edits to
|
|
13804
|
+
description: "Apply deterministic text edits to an issue or project document. Do not use it for review feedback or for reading; use " + `dispatch_comment, dispatch_suggest, or dispatch_doc_read instead. ${OWNER_REFERENCE} ${SPEC_WRITING_GUIDANCE}`,
|
|
13786
13805
|
arguments: (z) => ({
|
|
13787
|
-
issue: z.string().describe(ISSUE_REFERENCE),
|
|
13806
|
+
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
13807
|
+
project: z.string().describe("Project key owning the document.").optional(),
|
|
13788
13808
|
artifact: z.string().describe("Artifact slug or id for the document."),
|
|
13789
13809
|
ops: z.array(z.object({
|
|
13790
13810
|
op: z.enum(DOC_EDIT_OPS).describe("Edit operation."),
|
|
@@ -13796,23 +13816,27 @@ var dispatchToolSpecs = [
|
|
|
13796
13816
|
before: z.string().describe("Anchor before which to insert.").optional()
|
|
13797
13817
|
})).describe("Flat tagged edits; the server validates fields required for each operation."),
|
|
13798
13818
|
summary: z.string().describe("Optional named-version summary.").optional()
|
|
13799
|
-
})
|
|
13819
|
+
}),
|
|
13820
|
+
validation: documentOwnerValidation(true)
|
|
13800
13821
|
},
|
|
13801
13822
|
{
|
|
13802
13823
|
name: "dispatch_doc_read",
|
|
13803
|
-
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
|
|
13824
|
+
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, issue, or project plus artifact; issue plus an omitted artifact reads the primary document. " + OWNER_REFERENCE,
|
|
13804
13825
|
arguments: (z) => ({
|
|
13805
13826
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
13806
|
-
|
|
13827
|
+
project: z.string().describe("Project key owning the document.").optional(),
|
|
13828
|
+
artifact: z.string().describe("Optional artifact slug or id; primary document by default for an issue.").optional(),
|
|
13807
13829
|
version: z.number({ int: true, min: 1 }).describe("Optional version number.").optional(),
|
|
13808
13830
|
ref: z.string().describe("Optional dispatch:// document reference.").optional()
|
|
13809
|
-
})
|
|
13831
|
+
}),
|
|
13832
|
+
validation: documentOwnerValidation(true)
|
|
13810
13833
|
},
|
|
13811
13834
|
{
|
|
13812
13835
|
name: "dispatch_artifact",
|
|
13813
|
-
description: "Attach a local file or inline text as an issue artifact. Do not use it to edit a live document; use " + `dispatch_doc_edit instead. Exactly one of path or content is required; artifacts are limited to 25 MiB. ${
|
|
13836
|
+
description: "Attach a local file or inline text as an issue artifact or project document. Do not use it to edit a live document; use " + `dispatch_doc_edit instead. Exactly one of path or content is required; artifacts are limited to 25 MiB. ${OWNER_REFERENCE}`,
|
|
13814
13837
|
arguments: (z) => ({
|
|
13815
|
-
issue: z.string().describe(ISSUE_REFERENCE),
|
|
13838
|
+
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
13839
|
+
project: z.string().describe("Project key for an unlinked document.").optional(),
|
|
13816
13840
|
name: z.string().describe("Artifact filename shown in Dispatch."),
|
|
13817
13841
|
path: z.string().describe("Local path to the file to upload.").optional(),
|
|
13818
13842
|
content: z.string().describe("Inline text to store as a Markdown document.").optional(),
|
|
@@ -13821,18 +13845,21 @@ var dispatchToolSpecs = [
|
|
|
13821
13845
|
validation: {
|
|
13822
13846
|
check: (value) => {
|
|
13823
13847
|
const input = value;
|
|
13824
|
-
return typeof input.path === "string" !== (typeof input.content === "string");
|
|
13848
|
+
return documentOwnerValidation(false).check(value) && typeof input.path === "string" !== (typeof input.content === "string");
|
|
13825
13849
|
},
|
|
13826
|
-
message: "Exactly one of path or content is required."
|
|
13850
|
+
message: "Exactly one of path or content is required. Exactly one of issue and project is required; with project, artifact names the document."
|
|
13827
13851
|
}
|
|
13828
13852
|
},
|
|
13829
13853
|
{
|
|
13830
13854
|
name: "dispatch_read",
|
|
13831
|
-
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
|
|
13855
|
+
description: "Read an issue or project-document summary, targeted ask, or targeted comment reply chain. Do not use it for document " + "contents; use dispatch_doc_read instead. Supply ref, issue, or project plus artifact. " + OWNER_REFERENCE,
|
|
13832
13856
|
arguments: (z) => ({
|
|
13833
13857
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
13834
|
-
|
|
13835
|
-
|
|
13858
|
+
project: z.string().describe("Project key owning the document.").optional(),
|
|
13859
|
+
artifact: z.string().describe("Project document slug or id.").optional(),
|
|
13860
|
+
ref: z.string().describe("Optional dispatch:// issue or document reference.").optional()
|
|
13861
|
+
}),
|
|
13862
|
+
validation: documentOwnerValidation(true)
|
|
13836
13863
|
},
|
|
13837
13864
|
{
|
|
13838
13865
|
name: "dispatch_search",
|
|
@@ -13960,10 +13987,15 @@ var handoffMessageSchema = object({
|
|
|
13960
13987
|
});
|
|
13961
13988
|
// ../contracts/src/subject.ts
|
|
13962
13989
|
var AGENT_TOPIC_PREFIX = "notifications.agent.";
|
|
13963
|
-
var
|
|
13990
|
+
var DISPATCH_TOPIC_PREFIX = "notifications.dispatch.";
|
|
13991
|
+
var DISPATCH_ISSUE_TOPIC_PREFIX = `${DISPATCH_TOPIC_PREFIX}issue.`;
|
|
13992
|
+
var DISPATCH_DOCUMENT_TOPIC_PREFIX = `${DISPATCH_TOPIC_PREFIX}document.`;
|
|
13964
13993
|
function dispatchIssueSubject(issueKey, type) {
|
|
13965
13994
|
return `${DISPATCH_ISSUE_TOPIC_PREFIX}${issueKey}.${type}`;
|
|
13966
13995
|
}
|
|
13996
|
+
function dispatchDocumentSubject(project, slug, type) {
|
|
13997
|
+
return `${DISPATCH_DOCUMENT_TOPIC_PREFIX}${project}.${slug}.${type}`;
|
|
13998
|
+
}
|
|
13967
13999
|
function agentSubject(session) {
|
|
13968
14000
|
return `${AGENT_TOPIC_PREFIX}${session}`;
|
|
13969
14001
|
}
|
|
@@ -14482,6 +14514,25 @@ class DispatchClient {
|
|
|
14482
14514
|
async listIssues(options = {}) {
|
|
14483
14515
|
return this.#json("GET", ["api", "v1", "issues"], undefined, options);
|
|
14484
14516
|
}
|
|
14517
|
+
async listProjectArtifacts(project, unlinked = false) {
|
|
14518
|
+
return this.#json("GET", ["api", "v1", "projects", project, "artifacts"], undefined, unlinked ? { unlinked: "true" } : undefined);
|
|
14519
|
+
}
|
|
14520
|
+
async projectArtifact(project, input) {
|
|
14521
|
+
const artifactPath = ["api", "v1", "projects", project, "artifacts"];
|
|
14522
|
+
if ("content" in input)
|
|
14523
|
+
return this.#json("POST", artifactPath, input);
|
|
14524
|
+
const form = new FormData;
|
|
14525
|
+
form.set("name", input.name);
|
|
14526
|
+
if (input.summary !== undefined)
|
|
14527
|
+
form.set("summary", input.summary);
|
|
14528
|
+
if (input.actor !== undefined)
|
|
14529
|
+
form.set("actor", JSON.stringify(input.actor));
|
|
14530
|
+
form.set("file", input.file, input.name);
|
|
14531
|
+
return this.#form("POST", artifactPath, form);
|
|
14532
|
+
}
|
|
14533
|
+
async getProjectArtifact(project, slug) {
|
|
14534
|
+
return this.#json("GET", ["api", "v1", "projects", project, "artifacts", slug]);
|
|
14535
|
+
}
|
|
14485
14536
|
async search(query, options = {}) {
|
|
14486
14537
|
return this.#json("GET", ["api", "v1", "search"], undefined, { q: query, ...options });
|
|
14487
14538
|
}
|
|
@@ -14509,6 +14560,12 @@ class DispatchClient {
|
|
|
14509
14560
|
async comment(issue, input) {
|
|
14510
14561
|
return this.#json("POST", ["api", "v1", "issues", await this.#resolveIssue(issue), "comments"], input);
|
|
14511
14562
|
}
|
|
14563
|
+
async getArtifactAsks(id, state) {
|
|
14564
|
+
return this.#json("GET", ["api", "v1", "artifacts", id, "asks"], undefined, state === undefined ? undefined : { state });
|
|
14565
|
+
}
|
|
14566
|
+
async artifactAsk(id, input) {
|
|
14567
|
+
return this.#json("POST", ["api", "v1", "artifacts", id, "asks"], input);
|
|
14568
|
+
}
|
|
14512
14569
|
async suggest(issue, input) {
|
|
14513
14570
|
const { replace_with, ...comment } = input;
|
|
14514
14571
|
return this.#json("POST", ["api", "v1", "issues", await this.#resolveIssue(issue), "comments"], {
|
|
@@ -14516,6 +14573,19 @@ class DispatchClient {
|
|
|
14516
14573
|
suggestion: { replace_with }
|
|
14517
14574
|
});
|
|
14518
14575
|
}
|
|
14576
|
+
async getArtifactComments(id) {
|
|
14577
|
+
return this.#json("GET", ["api", "v1", "artifacts", id, "comments"]);
|
|
14578
|
+
}
|
|
14579
|
+
async artifactComment(id, input) {
|
|
14580
|
+
return this.#json("POST", ["api", "v1", "artifacts", id, "comments"], input);
|
|
14581
|
+
}
|
|
14582
|
+
async artifactSuggest(id, input) {
|
|
14583
|
+
const { replace_with, ...comment } = input;
|
|
14584
|
+
return this.#json("POST", ["api", "v1", "artifacts", id, "comments"], {
|
|
14585
|
+
...comment,
|
|
14586
|
+
suggestion: { replace_with }
|
|
14587
|
+
});
|
|
14588
|
+
}
|
|
14519
14589
|
async message(issue, input) {
|
|
14520
14590
|
return this.#json("POST", ["api", "v1", "issues", await this.#resolveIssue(issue), "messages"], input);
|
|
14521
14591
|
}
|
|
@@ -14544,6 +14614,12 @@ class DispatchClient {
|
|
|
14544
14614
|
async nameArtifactVersion(id, input) {
|
|
14545
14615
|
return this.#json("POST", ["api", "v1", "artifacts", id, "versions"], input);
|
|
14546
14616
|
}
|
|
14617
|
+
async getArtifactEvents(id, after = 0, limit = 200) {
|
|
14618
|
+
return this.#json("GET", ["api", "v1", "artifacts", id, "events"], undefined, { after, limit });
|
|
14619
|
+
}
|
|
14620
|
+
async getArtifactReferences(id) {
|
|
14621
|
+
return this.#json("GET", ["api", "v1", "artifacts", id, "references"]);
|
|
14622
|
+
}
|
|
14547
14623
|
async getAsk(id) {
|
|
14548
14624
|
return this.#json("GET", ["api", "v1", "asks", id]);
|
|
14549
14625
|
}
|
|
@@ -14553,6 +14629,15 @@ class DispatchClient {
|
|
|
14553
14629
|
async getComments(issue, artifact) {
|
|
14554
14630
|
return this.#json("GET", ["api", "v1", "issues", await this.#resolveIssue(issue), "comments"], undefined, artifact ? { artifact } : undefined);
|
|
14555
14631
|
}
|
|
14632
|
+
async getIssueReferences(issue) {
|
|
14633
|
+
return this.#json("GET", [
|
|
14634
|
+
"api",
|
|
14635
|
+
"v1",
|
|
14636
|
+
"issues",
|
|
14637
|
+
await this.#resolveIssue(issue),
|
|
14638
|
+
"references"
|
|
14639
|
+
]);
|
|
14640
|
+
}
|
|
14556
14641
|
async ensureIssue(issueReference, actor) {
|
|
14557
14642
|
if (!issueReference.includes("#"))
|
|
14558
14643
|
return issueReference;
|
|
@@ -14659,16 +14744,27 @@ class DispatchClient {
|
|
|
14659
14744
|
}
|
|
14660
14745
|
|
|
14661
14746
|
// ../envoy-client/src/dispatch-execute.ts
|
|
14662
|
-
|
|
14663
|
-
|
|
14664
|
-
|
|
14665
|
-
|
|
14666
|
-
|
|
14667
|
-
|
|
14668
|
-
|
|
14669
|
-
|
|
14670
|
-
|
|
14671
|
-
|
|
14747
|
+
function documentResultDetails(artifact) {
|
|
14748
|
+
return {
|
|
14749
|
+
project: artifact.project,
|
|
14750
|
+
artifact: artifact.id,
|
|
14751
|
+
document: `${artifact.project}/${artifact.slug}`,
|
|
14752
|
+
topic: dispatchDocumentSubject(artifact.project, artifact.slug, ">")
|
|
14753
|
+
};
|
|
14754
|
+
}
|
|
14755
|
+
function writeResultDetails(resolved, fields) {
|
|
14756
|
+
if (resolved.owner.kind === "project") {
|
|
14757
|
+
return { ...documentResultDetails(resolved.artifact), ...fields };
|
|
14758
|
+
}
|
|
14759
|
+
if (resolved.issue === undefined)
|
|
14760
|
+
throw new Error("issue document is missing its issue");
|
|
14761
|
+
return {
|
|
14762
|
+
issue: resolved.issue.key,
|
|
14763
|
+
topic: dispatchIssueSubject(resolved.issue.key, ">"),
|
|
14764
|
+
...fields
|
|
14765
|
+
};
|
|
14766
|
+
}
|
|
14767
|
+
async function askResultDetails(client, ask, resolved) {
|
|
14672
14768
|
if (ask.issue_key !== null) {
|
|
14673
14769
|
return {
|
|
14674
14770
|
issue: ask.issue_key,
|
|
@@ -14679,12 +14775,18 @@ async function askResultDetails(client, ask) {
|
|
|
14679
14775
|
if (ask.artifact_id === undefined || ask.artifact_id === null) {
|
|
14680
14776
|
throw new Error("document ask is missing its artifact ID");
|
|
14681
14777
|
}
|
|
14682
|
-
const artifact = await client.getArtifact(ask.artifact_id);
|
|
14683
|
-
return {
|
|
14684
|
-
topic: `notifications.dispatch.document.${artifact.project}.${artifact.slug}.>`,
|
|
14685
|
-
ask: ask.id
|
|
14686
|
-
};
|
|
14778
|
+
const artifact = resolved?.artifact ?? await client.getArtifact(ask.artifact_id);
|
|
14779
|
+
return { ...documentResultDetails(artifact), ask: ask.id };
|
|
14687
14780
|
}
|
|
14781
|
+
var nativeIssueKeyPattern = /^[A-Z][A-Z0-9]{1,9}-[0-9]+$/;
|
|
14782
|
+
var externalIssueRefPattern = /^([^/\s]+)\/([^/\s#]+)#([1-9][0-9]*)$/;
|
|
14783
|
+
var bareIssueNumberPattern = /^[1-9][0-9]*$/;
|
|
14784
|
+
var issueFreeTools = {
|
|
14785
|
+
dispatch_issue: true,
|
|
14786
|
+
dispatch_edit_ask: true,
|
|
14787
|
+
dispatch_resolve_ask: true,
|
|
14788
|
+
dispatch_search: true
|
|
14789
|
+
};
|
|
14688
14790
|
function canonicalExternalIssueRef(value) {
|
|
14689
14791
|
const match = value.trim().match(externalIssueRefPattern);
|
|
14690
14792
|
return match ? `${canonicalRepo(match[1] ?? "", match[2] ?? "")}#${match[3]}` : value;
|
|
@@ -14729,31 +14831,54 @@ function askUrgency(args) {
|
|
|
14729
14831
|
return ASK_URGENCIES.find((urgency) => urgency === value);
|
|
14730
14832
|
}
|
|
14731
14833
|
function parseDispatchRef(ref) {
|
|
14732
|
-
const
|
|
14733
|
-
if (
|
|
14834
|
+
const projectDocument = ref.match(/^dispatch:\/\/([A-Z][A-Z0-9]{1,9})\/artifact\/([^/@]+)(?:@v(\d+))?(?:\/(ask|comment)\/([^/]+))?$/);
|
|
14835
|
+
if (projectDocument) {
|
|
14836
|
+
const [, project, artifact, version, targetKind, targetID] = projectDocument;
|
|
14837
|
+
if (project === undefined || artifact === undefined || version !== undefined && Number(version) < 1) {
|
|
14838
|
+
return null;
|
|
14839
|
+
}
|
|
14840
|
+
if (targetKind === undefined) {
|
|
14841
|
+
return {
|
|
14842
|
+
owner: { kind: "project", project },
|
|
14843
|
+
kind: "artifact",
|
|
14844
|
+
id: artifact,
|
|
14845
|
+
...version === undefined ? {} : { version: Number(version) }
|
|
14846
|
+
};
|
|
14847
|
+
}
|
|
14848
|
+
if (targetID === undefined || targetKind !== "ask" && targetKind !== "comment")
|
|
14849
|
+
return null;
|
|
14850
|
+
return {
|
|
14851
|
+
owner: { kind: "project", project },
|
|
14852
|
+
kind: targetKind,
|
|
14853
|
+
id: targetID
|
|
14854
|
+
};
|
|
14855
|
+
}
|
|
14856
|
+
const issueReference = ref.match(/^dispatch:\/\/([A-Z][A-Z0-9]{1,9}-[1-9][0-9]*)(?:\/(spec)|\/(log)|\/(children)|\/artifact\/([^/@]+)(?:@v(\d+))?|\/ask\/([^/]+)|\/comment\/([^/]+))?$/);
|
|
14857
|
+
if (!issueReference)
|
|
14734
14858
|
return null;
|
|
14735
|
-
const [, issue, spec, log, children, artifact, version, ask, comment] =
|
|
14859
|
+
const [, issue, spec, log, children, artifact, version, ask, comment] = issueReference;
|
|
14736
14860
|
if (!issue || version !== undefined && Number(version) < 1)
|
|
14737
14861
|
return null;
|
|
14862
|
+
const owner = { kind: "issue", issue };
|
|
14738
14863
|
if (spec)
|
|
14739
|
-
return {
|
|
14864
|
+
return { owner, kind: "spec", id: spec };
|
|
14740
14865
|
if (log)
|
|
14741
|
-
return {
|
|
14866
|
+
return { owner, kind: "log", id: log };
|
|
14742
14867
|
if (children)
|
|
14743
|
-
return {
|
|
14868
|
+
return { owner, kind: "children", id: children };
|
|
14744
14869
|
if (artifact) {
|
|
14745
14870
|
return {
|
|
14746
|
-
|
|
14871
|
+
owner,
|
|
14747
14872
|
kind: "artifact",
|
|
14748
14873
|
id: artifact,
|
|
14749
14874
|
...version === undefined ? {} : { version: Number(version) }
|
|
14750
14875
|
};
|
|
14751
14876
|
}
|
|
14752
14877
|
if (ask)
|
|
14753
|
-
return {
|
|
14878
|
+
return { owner, kind: "ask", id: ask };
|
|
14754
14879
|
if (comment)
|
|
14755
|
-
return {
|
|
14756
|
-
return {
|
|
14880
|
+
return { owner, kind: "comment", id: comment };
|
|
14881
|
+
return { owner, kind: "issue", id: issue };
|
|
14757
14882
|
}
|
|
14758
14883
|
function askId(args) {
|
|
14759
14884
|
const ask = stringArg(args, "ask");
|
|
@@ -14771,32 +14896,68 @@ function toolSchema(tool) {
|
|
|
14771
14896
|
throw new Error(`Unknown Dispatch tool: ${tool}`);
|
|
14772
14897
|
return dispatchToolSchema(spec, zodSchemaApi(exports_external), { strict: true });
|
|
14773
14898
|
}
|
|
14774
|
-
async function
|
|
14775
|
-
if (issueFreeTools
|
|
14776
|
-
return { args, ref: null };
|
|
14899
|
+
async function resolveOwnerArguments(tool, args, cwd, env, exec) {
|
|
14900
|
+
if (issueFreeTools[tool] === true)
|
|
14901
|
+
return { args, ref: null, owner: null };
|
|
14777
14902
|
const refArgument = args.ref;
|
|
14778
14903
|
const ref = typeof refArgument === "string" ? parseDispatchRef(refArgument) ?? (() => {
|
|
14779
14904
|
throw new Error("ref must be a valid dispatch:// reference");
|
|
14780
14905
|
})() : null;
|
|
14781
14906
|
const issueArgument = args.issue;
|
|
14907
|
+
const projectArgument = args.project;
|
|
14782
14908
|
const artifactArgument = args.artifact;
|
|
14783
14909
|
const versionArgument = args.version;
|
|
14910
|
+
if (issueArgument !== undefined && projectArgument !== undefined) {
|
|
14911
|
+
throw new Error("exactly one of issue and project is required");
|
|
14912
|
+
}
|
|
14913
|
+
if (typeof projectArgument === "string") {
|
|
14914
|
+
if (!/^[A-Z][A-Z0-9]{1,9}$/.test(projectArgument)) {
|
|
14915
|
+
throw new Error("project must be a project key such as CORE");
|
|
14916
|
+
}
|
|
14917
|
+
if (ref?.owner.kind === "project" && (ref.owner.project !== projectArgument || artifactArgument !== undefined && artifactArgument !== ref.id)) {
|
|
14918
|
+
throw new Error("project and ref must name the same document");
|
|
14919
|
+
}
|
|
14920
|
+
return {
|
|
14921
|
+
args: {
|
|
14922
|
+
...args,
|
|
14923
|
+
...artifactArgument === undefined && ref?.owner.kind === "project" ? { artifact: ref.id } : {},
|
|
14924
|
+
...versionArgument === undefined && ref?.version !== undefined ? { version: ref.version } : {}
|
|
14925
|
+
},
|
|
14926
|
+
ref,
|
|
14927
|
+
owner: { kind: "project", project: projectArgument }
|
|
14928
|
+
};
|
|
14929
|
+
}
|
|
14930
|
+
if (ref?.owner.kind === "project") {
|
|
14931
|
+
return {
|
|
14932
|
+
args: {
|
|
14933
|
+
...args,
|
|
14934
|
+
project: ref.owner.project,
|
|
14935
|
+
...artifactArgument === undefined ? { artifact: ref.id } : {},
|
|
14936
|
+
...versionArgument === undefined && ref.version !== undefined ? { version: ref.version } : {}
|
|
14937
|
+
},
|
|
14938
|
+
ref,
|
|
14939
|
+
owner: ref.owner
|
|
14940
|
+
};
|
|
14941
|
+
}
|
|
14784
14942
|
if (issueArgument !== undefined || ref !== null) {
|
|
14943
|
+
const issue = typeof issueArgument === "string" ? canonicalExternalIssueRef(issueArgument) : ref?.owner.kind === "issue" ? ref.owner.issue : undefined;
|
|
14785
14944
|
return {
|
|
14786
14945
|
args: {
|
|
14787
14946
|
...args,
|
|
14788
|
-
...
|
|
14947
|
+
...issue === undefined ? {} : { issue },
|
|
14789
14948
|
...artifactArgument === undefined && (ref?.kind === "spec" || ref?.kind === "artifact") ? { artifact: ref.id } : {},
|
|
14790
14949
|
...versionArgument === undefined && ref?.version !== undefined ? { version: ref.version } : {}
|
|
14791
14950
|
},
|
|
14792
|
-
ref
|
|
14951
|
+
ref,
|
|
14952
|
+
owner: issue === undefined ? null : { kind: "issue", issue }
|
|
14793
14953
|
};
|
|
14794
14954
|
}
|
|
14795
14955
|
const legionIssue = env.LEGION_ISSUE;
|
|
14796
14956
|
if (!legionIssue)
|
|
14797
14957
|
throw new Error("issue is required; supply issue or set LEGION_ISSUE");
|
|
14798
14958
|
if (nativeIssueKeyPattern.test(legionIssue) || externalIssueRefPattern.test(legionIssue)) {
|
|
14799
|
-
|
|
14959
|
+
const issue = canonicalExternalIssueRef(legionIssue);
|
|
14960
|
+
return { args: { ...args, issue }, ref: null, owner: { kind: "issue", issue } };
|
|
14800
14961
|
}
|
|
14801
14962
|
if (!bareIssueNumberPattern.test(legionIssue)) {
|
|
14802
14963
|
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");
|
|
@@ -14804,15 +14965,25 @@ async function resolveIssueArguments(tool, args, cwd, env, exec) {
|
|
|
14804
14965
|
const repo = await resolveCwdRepo(cwd, exec);
|
|
14805
14966
|
if (!repo)
|
|
14806
14967
|
throw new Error("issue is required; LEGION_ISSUE needs a GitHub repository in cwd");
|
|
14807
|
-
|
|
14968
|
+
const issue = `${repo}#${legionIssue}`;
|
|
14969
|
+
return { args: { ...args, issue }, ref: null, owner: { kind: "issue", issue } };
|
|
14808
14970
|
}
|
|
14809
|
-
async function resolveArtifact(client,
|
|
14810
|
-
|
|
14971
|
+
async function resolveArtifact(client, owner, artifactReference) {
|
|
14972
|
+
if (owner.kind === "project") {
|
|
14973
|
+
if (artifactReference === undefined) {
|
|
14974
|
+
throw new Error("artifact is required for a project document");
|
|
14975
|
+
}
|
|
14976
|
+
return {
|
|
14977
|
+
owner,
|
|
14978
|
+
artifact: await client.getProjectArtifact(owner.project, artifactReference)
|
|
14979
|
+
};
|
|
14980
|
+
}
|
|
14981
|
+
const issue = await client.getIssue(owner.issue);
|
|
14811
14982
|
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);
|
|
14812
14983
|
if (!artifact) {
|
|
14813
14984
|
throw new Error(`artifact ${artifactReference ?? "spec"} was not found on issue ${issue.key}`);
|
|
14814
14985
|
}
|
|
14815
|
-
return { issue, artifact };
|
|
14986
|
+
return { owner, issue, artifact };
|
|
14816
14987
|
}
|
|
14817
14988
|
function anchor(artifact, args) {
|
|
14818
14989
|
const quote = optionalString(args, "quote");
|
|
@@ -14832,7 +15003,7 @@ function toolActor(origin, input) {
|
|
|
14832
15003
|
}
|
|
14833
15004
|
};
|
|
14834
15005
|
}
|
|
14835
|
-
function issueSummary(issue, events) {
|
|
15006
|
+
function issueSummary(issue, events, references) {
|
|
14836
15007
|
const asks = issue.open_asks;
|
|
14837
15008
|
return [
|
|
14838
15009
|
`Title: ${issue.title}`,
|
|
@@ -14841,6 +15012,9 @@ function issueSummary(issue, events) {
|
|
|
14841
15012
|
`Route: ${issue.route ?? "none"}`,
|
|
14842
15013
|
"Open asks:",
|
|
14843
15014
|
...asks.length === 0 ? ["- none"] : asks.map((ask) => `- ${ask.id}: ${ask.question}`),
|
|
15015
|
+
"References:",
|
|
15016
|
+
...typeof references === "string" ? [`- ${references}`] : references.members.length === 0 ? ["- none"] : references.members.map(({ artifact, depth, via }) => `- ${artifact.project}/${artifact.slug} \xB7 depth ${depth} via ${via.kind} ${via.id}`),
|
|
15017
|
+
...typeof references === "string" || !references.truncated ? [] : ["- more references beyond 8 hops"],
|
|
14844
15018
|
"Events:",
|
|
14845
15019
|
...events.length === 0 ? ["- none"] : events.map((event) => `- #${event.seq} ${event.type} \xB7 ${event.actor.kind} ${event.actor.id} \xB7 ${event.created_at}`)
|
|
14846
15020
|
].join(`
|
|
@@ -14905,10 +15079,11 @@ function commentSummary({ comment, replies }) {
|
|
|
14905
15079
|
`);
|
|
14906
15080
|
}
|
|
14907
15081
|
async function openArtifactMarks(client, resolved) {
|
|
14908
|
-
const
|
|
15082
|
+
const asks = resolved.owner.kind === "project" ? await client.getArtifactAsks(resolved.artifact.id) : resolved.issue?.open_asks ?? [];
|
|
15083
|
+
const marks = asks.filter((ask) => ask.state === "open" && ask.anchor?.artifact_id === resolved.artifact.id).map((ask) => `ask ${ask.id}`);
|
|
14909
15084
|
let comments;
|
|
14910
15085
|
try {
|
|
14911
|
-
comments = await client.getComments(resolved.issue
|
|
15086
|
+
comments = resolved.owner.kind === "project" ? await client.getArtifactComments(resolved.artifact.id) : await client.getComments(resolved.issue?.key ?? "", resolved.artifact.id);
|
|
14912
15087
|
} catch (error) {
|
|
14913
15088
|
if (error instanceof DispatchServiceError && error.status === 404)
|
|
14914
15089
|
return marks;
|
|
@@ -14927,15 +15102,23 @@ async function executeDispatchTool(input) {
|
|
|
14927
15102
|
}
|
|
14928
15103
|
const env = input.env ?? process.env;
|
|
14929
15104
|
const exec = input.exec ?? defaultExec;
|
|
14930
|
-
const
|
|
14931
|
-
const args = toolSchema(input.tool).parse(
|
|
15105
|
+
const ownerArguments = await resolveOwnerArguments(input.tool, input.args, input.cwd, env, exec);
|
|
15106
|
+
const args = toolSchema(input.tool).parse(ownerArguments.args);
|
|
14932
15107
|
const actor = toolActor(await resolveOrigin(env, exec, input.cwd), input);
|
|
14933
15108
|
const client = new DispatchClient(configUrl, configToken, input.fetchImpl);
|
|
14934
|
-
const
|
|
15109
|
+
const owner = ownerArguments.owner?.kind === "issue" ? {
|
|
15110
|
+
kind: "issue",
|
|
15111
|
+
issue: await ensureIssue(client, ownerArguments.owner.issue, actor)
|
|
15112
|
+
} : ownerArguments.owner;
|
|
14935
15113
|
const issue = () => {
|
|
14936
|
-
if (
|
|
15114
|
+
if (owner?.kind !== "issue")
|
|
14937
15115
|
throw new Error("issue is required");
|
|
14938
|
-
return
|
|
15116
|
+
return owner.issue;
|
|
15117
|
+
};
|
|
15118
|
+
const documentOwner = () => {
|
|
15119
|
+
if (owner === null)
|
|
15120
|
+
throw new Error("issue or project is required");
|
|
15121
|
+
return owner;
|
|
14939
15122
|
};
|
|
14940
15123
|
switch (input.tool) {
|
|
14941
15124
|
case "dispatch_issue": {
|
|
@@ -15015,22 +15198,25 @@ async function executeDispatchTool(input) {
|
|
|
15015
15198
|
}
|
|
15016
15199
|
case "dispatch_ask": {
|
|
15017
15200
|
const anchorArgs = asObject(args.anchor);
|
|
15018
|
-
const
|
|
15201
|
+
const owner = documentOwner();
|
|
15202
|
+
const artifactReference = optionalString(args, "artifact") ?? (anchorArgs === null ? undefined : optionalString(anchorArgs, "artifact"));
|
|
15203
|
+
const resolved = owner.kind === "project" || artifactReference === undefined ? owner.kind === "project" ? await resolveArtifact(client, owner, artifactReference) : undefined : await resolveArtifact(client, owner, artifactReference);
|
|
15019
15204
|
const options = args.options;
|
|
15020
15205
|
const multiple = optionalBoolean(args, "multiple");
|
|
15021
15206
|
const urgency = askUrgency(args);
|
|
15022
15207
|
const anchored = anchorArgs && resolved ? anchor(resolved.artifact, anchorArgs) : undefined;
|
|
15023
|
-
const
|
|
15208
|
+
const askInput = {
|
|
15024
15209
|
question: stringArg(args, "question"),
|
|
15025
15210
|
...Array.isArray(options) ? { options } : {},
|
|
15026
15211
|
...multiple === undefined ? {} : { multiple },
|
|
15027
15212
|
...urgency === undefined ? {} : { urgency },
|
|
15028
15213
|
...anchored === undefined ? {} : { anchor: anchored },
|
|
15029
15214
|
actor
|
|
15030
|
-
}
|
|
15215
|
+
};
|
|
15216
|
+
const ask = resolved?.owner.kind === "project" ? await client.artifactAsk(resolved.artifact.id, askInput) : await client.ask(issue(), askInput);
|
|
15031
15217
|
return {
|
|
15032
15218
|
text: `Opened ask ${ask.id}: ${ask.question}`,
|
|
15033
|
-
details: await askResultDetails(client, ask)
|
|
15219
|
+
details: await askResultDetails(client, ask, resolved)
|
|
15034
15220
|
};
|
|
15035
15221
|
}
|
|
15036
15222
|
case "dispatch_edit_ask": {
|
|
@@ -15055,48 +15241,47 @@ async function executeDispatchTool(input) {
|
|
|
15055
15241
|
if (optionalString(args, "quote") !== undefined && artifactReference === undefined) {
|
|
15056
15242
|
throw new Error("artifact is required when quote is supplied");
|
|
15057
15243
|
}
|
|
15058
|
-
const
|
|
15244
|
+
const owner = documentOwner();
|
|
15245
|
+
const resolved = owner.kind === "project" || artifactReference === undefined ? owner.kind === "project" ? await resolveArtifact(client, owner, artifactReference) : undefined : await resolveArtifact(client, owner, artifactReference);
|
|
15059
15246
|
const anchored = resolved ? anchor(resolved.artifact, args) : undefined;
|
|
15060
15247
|
const replyTo = optionalString(args, "reply_to");
|
|
15061
15248
|
const replyToAsk = optionalString(args, "reply_to_ask");
|
|
15062
15249
|
if (replyTo !== undefined && replyToAsk !== undefined) {
|
|
15063
15250
|
throw new Error("reply_to and reply_to_ask cannot both be set");
|
|
15064
15251
|
}
|
|
15065
|
-
const
|
|
15252
|
+
const commentInput = {
|
|
15066
15253
|
body: stringArg(args, "body"),
|
|
15067
15254
|
...anchored === undefined ? {} : { anchor: anchored },
|
|
15068
15255
|
...replyTo === undefined ? {} : { reply_to: replyTo },
|
|
15069
15256
|
...replyToAsk === undefined ? {} : { ask_id: replyToAsk },
|
|
15070
15257
|
actor
|
|
15071
|
-
}
|
|
15258
|
+
};
|
|
15259
|
+
const comment = resolved?.owner.kind === "project" ? await client.artifactComment(resolved.artifact.id, commentInput) : await client.comment(issue(), commentInput);
|
|
15072
15260
|
return {
|
|
15073
15261
|
text: `Posted comment ${comment.id}`,
|
|
15074
|
-
details: {
|
|
15262
|
+
details: resolved === undefined ? {
|
|
15075
15263
|
issue: comment.issue_key,
|
|
15076
|
-
topic: dispatchIssueSubject(
|
|
15264
|
+
topic: dispatchIssueSubject(issue(), ">"),
|
|
15077
15265
|
comment: comment.id
|
|
15078
|
-
}
|
|
15266
|
+
} : writeResultDetails(resolved, { comment: comment.id })
|
|
15079
15267
|
};
|
|
15080
15268
|
}
|
|
15081
15269
|
case "dispatch_suggest": {
|
|
15082
|
-
const resolved = await resolveArtifact(client,
|
|
15270
|
+
const resolved = await resolveArtifact(client, documentOwner(), stringArg(args, "artifact"));
|
|
15083
15271
|
const anchored = anchor(resolved.artifact, args);
|
|
15084
15272
|
if (anchored === undefined)
|
|
15085
15273
|
throw new Error("quote is required");
|
|
15086
15274
|
const body = optionalString(args, "body");
|
|
15087
|
-
const
|
|
15275
|
+
const suggestionInput = {
|
|
15088
15276
|
...body === undefined ? {} : { body },
|
|
15089
15277
|
anchor: anchored,
|
|
15090
15278
|
replace_with: stringArg(args, "replace_with"),
|
|
15091
15279
|
actor
|
|
15092
|
-
}
|
|
15280
|
+
};
|
|
15281
|
+
const comment = resolved.owner.kind === "project" ? await client.artifactSuggest(resolved.artifact.id, suggestionInput) : await client.suggest(issue(), suggestionInput);
|
|
15093
15282
|
return {
|
|
15094
15283
|
text: `Posted suggestion ${comment.id}`,
|
|
15095
|
-
details: {
|
|
15096
|
-
issue: comment.issue_key,
|
|
15097
|
-
topic: dispatchIssueSubject(comment.issue_key, ">"),
|
|
15098
|
-
comment: comment.id
|
|
15099
|
-
}
|
|
15284
|
+
details: writeResultDetails(resolved, { comment: comment.id })
|
|
15100
15285
|
};
|
|
15101
15286
|
}
|
|
15102
15287
|
case "dispatch_message": {
|
|
@@ -15111,7 +15296,7 @@ async function executeDispatchTool(input) {
|
|
|
15111
15296
|
};
|
|
15112
15297
|
}
|
|
15113
15298
|
case "dispatch_doc_edit": {
|
|
15114
|
-
const resolved = await resolveArtifact(client,
|
|
15299
|
+
const resolved = await resolveArtifact(client, documentOwner(), stringArg(args, "artifact"));
|
|
15115
15300
|
const ops = args.ops;
|
|
15116
15301
|
const summary = optionalString(args, "summary");
|
|
15117
15302
|
const edited = await client.docEdit(resolved.artifact.id, {
|
|
@@ -15121,32 +15306,33 @@ async function executeDispatchTool(input) {
|
|
|
15121
15306
|
});
|
|
15122
15307
|
return {
|
|
15123
15308
|
text: edited.version === null ? `Applied ${edited.applied} ops (no new version)` : `Applied ${edited.applied} ops (version ${edited.version.number})`,
|
|
15124
|
-
details: {
|
|
15125
|
-
issue: resolved.issue.key,
|
|
15126
|
-
topic: dispatchIssueSubject(resolved.issue.key, ">"),
|
|
15309
|
+
details: writeResultDetails(resolved, {
|
|
15127
15310
|
applied: edited.applied,
|
|
15128
15311
|
...edited.version === null ? {} : { version: edited.version.number }
|
|
15129
|
-
}
|
|
15312
|
+
})
|
|
15130
15313
|
};
|
|
15131
15314
|
}
|
|
15132
15315
|
case "dispatch_doc_read": {
|
|
15133
|
-
const artifactReference = optionalString(args, "artifact") ?? (
|
|
15134
|
-
const resolved = await resolveArtifact(client,
|
|
15135
|
-
const version = optionalNumber(args, "version") ??
|
|
15316
|
+
const artifactReference = optionalString(args, "artifact") ?? (ownerArguments.ref?.kind === "spec" || ownerArguments.ref?.kind === "artifact" ? ownerArguments.ref.id : undefined);
|
|
15317
|
+
const resolved = await resolveArtifact(client, documentOwner(), artifactReference);
|
|
15318
|
+
const version = optionalNumber(args, "version") ?? ownerArguments.ref?.version;
|
|
15136
15319
|
const document = await client.docRead(resolved.artifact.id, version);
|
|
15137
15320
|
const marks = await openArtifactMarks(client, resolved);
|
|
15138
15321
|
return {
|
|
15139
15322
|
text: marks.length === 0 ? document.markdown : `${document.markdown}
|
|
15140
15323
|
|
|
15141
15324
|
Open anchored asks/comments: ${marks.join(", ")}`,
|
|
15142
|
-
details:
|
|
15325
|
+
details: resolved.owner.kind === "project" ? {
|
|
15326
|
+
project: resolved.artifact.project,
|
|
15327
|
+
document: `${resolved.artifact.project}/${resolved.artifact.slug}`
|
|
15328
|
+
} : { issue: resolved.issue?.key }
|
|
15143
15329
|
};
|
|
15144
15330
|
}
|
|
15145
15331
|
case "dispatch_artifact": {
|
|
15146
15332
|
const summary = optionalString(args, "summary");
|
|
15147
15333
|
const name = stringArg(args, "name");
|
|
15148
15334
|
const content = optionalString(args, "content");
|
|
15149
|
-
const
|
|
15335
|
+
const artifactInput = content === undefined ? {
|
|
15150
15336
|
name,
|
|
15151
15337
|
file: Bun.file(resolvePath(input.cwd, stringArg(args, "path"))),
|
|
15152
15338
|
...summary === undefined ? {} : { summary },
|
|
@@ -15156,47 +15342,73 @@ Open anchored asks/comments: ${marks.join(", ")}`,
|
|
|
15156
15342
|
content,
|
|
15157
15343
|
...summary === undefined ? {} : { summary },
|
|
15158
15344
|
actor
|
|
15159
|
-
}
|
|
15345
|
+
};
|
|
15346
|
+
const artifactOwner = documentOwner();
|
|
15347
|
+
const result = artifactOwner.kind === "project" ? await client.projectArtifact(artifactOwner.project, artifactInput) : await client.artifact(issue(), artifactInput);
|
|
15160
15348
|
return {
|
|
15161
15349
|
text: `Uploaded ${result.artifact.name} as version ${result.version.number}`,
|
|
15162
|
-
details: {
|
|
15163
|
-
|
|
15164
|
-
|
|
15350
|
+
details: artifactOwner.kind === "project" ? {
|
|
15351
|
+
...documentResultDetails(result.artifact),
|
|
15352
|
+
version: result.version.number
|
|
15353
|
+
} : {
|
|
15354
|
+
issue: issue(),
|
|
15355
|
+
topic: dispatchIssueSubject(issue(), ">"),
|
|
15165
15356
|
artifact: result.artifact.id,
|
|
15166
15357
|
version: result.version.number
|
|
15167
15358
|
}
|
|
15168
15359
|
};
|
|
15169
15360
|
}
|
|
15170
15361
|
case "dispatch_read": {
|
|
15171
|
-
if (
|
|
15172
|
-
const askRead = await client.getAsk(
|
|
15362
|
+
if (ownerArguments.ref?.kind === "ask") {
|
|
15363
|
+
const askRead = await client.getAsk(ownerArguments.ref.id);
|
|
15173
15364
|
return {
|
|
15174
15365
|
text: askSummary(askRead),
|
|
15175
|
-
details: { issue:
|
|
15366
|
+
details: ownerArguments.ref.owner.kind === "project" ? { project: ownerArguments.ref.owner.project } : { issue: ownerArguments.ref.owner.issue }
|
|
15176
15367
|
};
|
|
15177
15368
|
}
|
|
15178
|
-
if (
|
|
15179
|
-
const comment = await client.getComment(
|
|
15369
|
+
if (ownerArguments.ref?.kind === "comment") {
|
|
15370
|
+
const comment = await client.getComment(ownerArguments.ref.id);
|
|
15180
15371
|
return {
|
|
15181
15372
|
text: commentSummary(comment),
|
|
15182
|
-
details: { issue: comment.comment.issue_key }
|
|
15373
|
+
details: ownerArguments.ref.owner.kind === "project" ? { project: ownerArguments.ref.owner.project } : { issue: comment.comment.issue_key }
|
|
15374
|
+
};
|
|
15375
|
+
}
|
|
15376
|
+
if (documentOwner().kind === "project") {
|
|
15377
|
+
const resolved = await resolveArtifact(client, documentOwner(), stringArg(args, "artifact"));
|
|
15378
|
+
return {
|
|
15379
|
+
text: [
|
|
15380
|
+
`Document: ${resolved.artifact.project} / ${resolved.artifact.name}`,
|
|
15381
|
+
`Reference: dispatch://${resolved.artifact.project}/artifact/${resolved.artifact.slug}`,
|
|
15382
|
+
`Versions: ${resolved.artifact.versions.length}`
|
|
15383
|
+
].join(`
|
|
15384
|
+
`),
|
|
15385
|
+
details: {
|
|
15386
|
+
project: resolved.artifact.project,
|
|
15387
|
+
document: `${resolved.artifact.project}/${resolved.artifact.slug}`
|
|
15388
|
+
}
|
|
15183
15389
|
};
|
|
15184
15390
|
}
|
|
15185
15391
|
const read = await client.read(issue());
|
|
15186
|
-
if (
|
|
15392
|
+
if (ownerArguments.ref?.kind === "log") {
|
|
15187
15393
|
return {
|
|
15188
15394
|
text: logSummary(read.issue, read.events),
|
|
15189
15395
|
details: { issue: read.issue.key }
|
|
15190
15396
|
};
|
|
15191
15397
|
}
|
|
15192
|
-
if (
|
|
15398
|
+
if (ownerArguments.ref?.kind === "children") {
|
|
15193
15399
|
return {
|
|
15194
15400
|
text: childrenSummary(read.issue),
|
|
15195
15401
|
details: { issue: read.issue.key }
|
|
15196
15402
|
};
|
|
15197
15403
|
}
|
|
15404
|
+
let references;
|
|
15405
|
+
try {
|
|
15406
|
+
references = await client.getIssueReferences(read.issue.key);
|
|
15407
|
+
} catch (error) {
|
|
15408
|
+
references = error instanceof DispatchServiceError && error.status === 404 ? "unavailable" : `unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
15409
|
+
}
|
|
15198
15410
|
return {
|
|
15199
|
-
text: issueSummary(read.issue, read.events),
|
|
15411
|
+
text: issueSummary(read.issue, read.events, references),
|
|
15200
15412
|
details: { issue: read.issue.key }
|
|
15201
15413
|
};
|
|
15202
15414
|
}
|
package/package.json
CHANGED
package/skills/dispatch/SKILL.md
CHANGED
|
@@ -44,13 +44,16 @@ transcript of your thinking. Use exactly these document headings in this order.
|
|
|
44
44
|
- [ ] The spec covers one implementation plan's worth of work.
|
|
45
45
|
- [ ] Every requirement has exactly one reading.
|
|
46
46
|
|
|
47
|
-
## Your
|
|
47
|
+
## Your owner
|
|
48
48
|
|
|
49
|
-
Every session works on an issue. Legion pre-fills `issue` from
|
|
50
|
-
such as `LEGION-3`, an external `owner/repo#n`
|
|
51
|
-
against the cwd repository). Otherwise pass
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
Every session works on an issue or project document. Legion pre-fills `issue` from
|
|
50
|
+
`LEGION_ISSUE`: use a native issue key such as `LEGION-3`, an external `owner/repo#n`
|
|
51
|
+
reference, or a bare positive number (resolved against the cwd repository). Otherwise pass
|
|
52
|
+
exactly one owner to every owner-scoped tool: `issue` for an issue, or `project` and
|
|
53
|
+
`artifact` for an unlinked project document. A project key such as `CORE` with artifact
|
|
54
|
+
`design-notes` identifies `dispatch://CORE/artifact/design-notes`. On first use, an external
|
|
55
|
+
issue reference creates its native issue in the project configured for that repository in
|
|
56
|
+
Dispatch Settings, then falls back to `DISPATCH_DEFAULT_PROJECT`.
|
|
54
57
|
|
|
55
58
|
Architects create newly tracked child work with:
|
|
56
59
|
```ts
|
|
@@ -78,7 +81,9 @@ call with `force: true` when it is genuinely new work.
|
|
|
78
81
|
Open a decision with:
|
|
79
82
|
```ts
|
|
80
83
|
dispatch_ask({
|
|
81
|
-
issue
|
|
84
|
+
issue?,
|
|
85
|
+
project?,
|
|
86
|
+
artifact?,
|
|
82
87
|
question,
|
|
83
88
|
options?: { label, description? }[],
|
|
84
89
|
multiple?,
|
|
@@ -86,7 +91,8 @@ dispatch_ask({
|
|
|
86
91
|
anchor?: { artifact, quote, occurrence? },
|
|
87
92
|
})
|
|
88
93
|
```
|
|
89
|
-
It returns `details` `{ issue, topic, ask }
|
|
94
|
+
It returns `details` `{ issue, topic, ask }` for an issue or `{ project, artifact, document,
|
|
95
|
+
topic, ask }` for a project document. Options are buttons: never enumerate choices in
|
|
90
96
|
prose. Put the recommendation in `question`, and put each selectable choice in `options`.
|
|
91
97
|
Anchor a document question with `anchor: { artifact, quote, occurrence? }`; `occurrence` is
|
|
92
98
|
zero-based and selects a repeated quote. The server writes the resulting mark. The HTTP API also
|
|
@@ -132,16 +138,16 @@ Write and update the issue specification according to [Writing a spec](#writing-
|
|
|
132
138
|
Read the current document before changing it:
|
|
133
139
|
|
|
134
140
|
```ts
|
|
135
|
-
dispatch_doc_read({ issue?, artifact?, version?, ref? })
|
|
141
|
+
dispatch_doc_read({ issue?, project?, artifact?, version?, ref? })
|
|
136
142
|
```
|
|
137
|
-
It returns live or versioned markdown with open marks
|
|
138
|
-
|
|
139
|
-
|
|
143
|
+
It returns live or versioned markdown with open marks. `issue` with an omitted `artifact`
|
|
144
|
+
reads the issue specification; a project needs `artifact`; and a
|
|
145
|
+
`dispatch://PROJECT/artifact/<slug>` ref supplies both. Then write narrative with:
|
|
140
146
|
```ts
|
|
141
|
-
dispatch_doc_edit({ issue
|
|
147
|
+
dispatch_doc_edit({ issue?, project?, artifact, ops, summary? })
|
|
142
148
|
```
|
|
143
|
-
It returns
|
|
144
|
-
`EditOp` shape:
|
|
149
|
+
It returns issue or project-document owner details plus `applied`, optional `version`, and its
|
|
150
|
+
write `topic`. `ops` is an array of this exact `EditOp` shape:
|
|
145
151
|
|
|
146
152
|
```ts
|
|
147
153
|
type EditOp = {
|
|
@@ -171,23 +177,24 @@ into a message.
|
|
|
171
177
|
Add feedback with:
|
|
172
178
|
|
|
173
179
|
```ts
|
|
174
|
-
dispatch_comment({ issue
|
|
180
|
+
dispatch_comment({ issue?, project?, artifact?, quote?, occurrence?, body, reply_to?, reply_to_ask? })
|
|
175
181
|
```
|
|
176
182
|
|
|
177
|
-
It returns
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
thread
|
|
181
|
-
`
|
|
182
|
-
dashboard.
|
|
183
|
+
It returns issue or project-document owner details plus `comment` and, for writes, `topic`.
|
|
184
|
+
`quote` requires `artifact`; omit both for a floating issue comment. A reply
|
|
185
|
+
(`reply_to`/`reply_to_ask`) takes no `quote`; it belongs to its parent's anchor. Use `reply_to`
|
|
186
|
+
to continue a comment thread at its root; a reply to a resolved thread reopens it. Use
|
|
187
|
+
`reply_to_ask` to reply directly under a question asked with `dispatch_ask`. The two are
|
|
188
|
+
mutually exclusive. Comments are edited only by their author from the dashboard.
|
|
183
189
|
|
|
184
190
|
Propose an exact replacement instead of describing it:
|
|
185
191
|
|
|
186
192
|
```ts
|
|
187
|
-
dispatch_suggest({ issue
|
|
193
|
+
dispatch_suggest({ issue?, project?, artifact, quote, replace_with, body?, occurrence? })
|
|
188
194
|
```
|
|
189
195
|
|
|
190
|
-
It returns
|
|
196
|
+
It returns issue or project-document owner details plus `comment` and its write `topic`. A
|
|
197
|
+
human accepts or rejects a suggestion. On
|
|
191
198
|
`TARGET_AMBIGUOUS`, add zero-based `occurrence`. On `TARGET_NOT_FOUND`, re-read the document
|
|
192
199
|
before retrying. `INVALID_ANCHOR` requires exactly one nonempty anchor `quote` or `mark_id`;
|
|
193
200
|
`ANCHOR_MISSING` means a browser mark was not observed in the live tree, and
|
|
@@ -201,19 +208,20 @@ reject an invalid actor or route.
|
|
|
201
208
|
Attach an image, diagram, or local file with:
|
|
202
209
|
|
|
203
210
|
```ts
|
|
204
|
-
dispatch_artifact({ issue
|
|
211
|
+
dispatch_artifact({ issue?, project?, name, path, summary? })
|
|
205
212
|
```
|
|
206
213
|
|
|
207
214
|
Or, when the text is already in the call, post a Markdown document directly:
|
|
208
215
|
|
|
209
216
|
```ts
|
|
210
|
-
dispatch_artifact({ issue
|
|
217
|
+
dispatch_artifact({ issue?, project?, name: "spec.md", content: "# Design\n..." })
|
|
211
218
|
```
|
|
212
219
|
|
|
213
|
-
Exactly one of `
|
|
214
|
-
`
|
|
215
|
-
|
|
216
|
-
the
|
|
220
|
+
Exactly one of `issue` and `project` is required. A project upload creates an unlinked project
|
|
221
|
+
document; it must not include `artifact`. Exactly one of `path` and `content` is required. The
|
|
222
|
+
inline form sends JSON with `Content-Type: application/json`. It returns issue or
|
|
223
|
+
project-document owner details plus `artifact`, `version`, and its write `topic`. Uploading the
|
|
224
|
+
same `name` creates its next version. Use `content` when the text is already in the call.
|
|
217
225
|
|
|
218
226
|
## Messages
|
|
219
227
|
|
|
@@ -228,23 +236,20 @@ message does not wake anyone. Do not use it for status, a decision, or document
|
|
|
228
236
|
|
|
229
237
|
## What comes back
|
|
230
238
|
|
|
231
|
-
A write result's `details.topic` subscribes the host to
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
The issue topic carries every Dispatch event; `notify` only controls agent wake and routed delivery.
|
|
238
|
-
After a restart, catch up with:
|
|
239
|
+
A write result's `details.topic` subscribes the host to its owner. Issue writes use
|
|
240
|
+
`notifications.dispatch.issue.<KEY>.>`; project-document writes use
|
|
241
|
+
`notifications.dispatch.document.<PROJECT>.<SLUG>.>`. The owner topic carries every Dispatch
|
|
242
|
+
event; `notify` only controls agent wake and routed delivery. After a restart, catch up with:
|
|
239
243
|
|
|
240
244
|
```ts
|
|
241
|
-
dispatch_read({ issue?, ref? })
|
|
245
|
+
dispatch_read({ issue?, project?, artifact?, ref? })
|
|
242
246
|
```
|
|
243
247
|
|
|
244
|
-
With an issue ref, it returns the issue summary, open asks, and recent events with
|
|
245
|
-
`{ issue }`. With
|
|
246
|
-
|
|
247
|
-
|
|
248
|
+
With an issue ref, it returns the issue summary, open asks, references, and recent events with
|
|
249
|
+
`details` `{ issue }`. With a project document owner or ref, it returns a document summary with
|
|
250
|
+
`details` `{ project, document }`. With an ask ref, it returns that ask's question, options,
|
|
251
|
+
state, answer, and its reply thread. With a comment ref, it returns that comment and its quoted
|
|
252
|
+
reply chain. Reads do not subscribe; use `dispatch_doc_read` for document contents.
|
|
248
253
|
|
|
249
254
|
## References
|
|
250
255
|
|
|
@@ -256,6 +261,9 @@ dispatch://KEY/spec
|
|
|
256
261
|
dispatch://KEY/artifact/<slug>[@vN]
|
|
257
262
|
dispatch://KEY/ask/<id>
|
|
258
263
|
dispatch://KEY/comment/<id>
|
|
264
|
+
dispatch://PROJECT/artifact/<slug>[@vN]
|
|
265
|
+
dispatch://PROJECT/artifact/<slug>/ask/<id>
|
|
266
|
+
dispatch://PROJECT/artifact/<slug>/comment/<id>
|
|
259
267
|
```
|
|
260
268
|
|
|
261
269
|
## Before / after
|