@sjawhar/opencode-legion-envoy 0.40.0 → 0.41.1
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 +342 -112
- package/package.json +1 -1
- package/skills/dispatch/SKILL.md +74 -55
package/dist/src/server.js
CHANGED
|
@@ -13603,15 +13603,22 @@ var AskEditedEventPayloadSchema = object({
|
|
|
13603
13603
|
edited_by: object({ kind: string2(), id: string2() }).passthrough()
|
|
13604
13604
|
});
|
|
13605
13605
|
var CommentEventPayloadSchema = object({
|
|
13606
|
+
id: string2().optional(),
|
|
13606
13607
|
artifact_name: string2().optional(),
|
|
13607
13608
|
body: string2().optional(),
|
|
13608
13609
|
reply_to: string2().nullish(),
|
|
13609
13610
|
ask_id: string2().nullish(),
|
|
13610
13611
|
ask_question: string2().optional(),
|
|
13611
13612
|
anchor: object({ quote: string2().optional() }).nullish(),
|
|
13612
|
-
suggestion: object({ replace_with: string2().optional() }).nullish()
|
|
13613
|
+
suggestion: object({ replace_with: string2().optional() }).nullish(),
|
|
13614
|
+
author: object({ kind: string2(), id: string2().optional() }).passthrough().optional(),
|
|
13615
|
+
created_at: string2().optional()
|
|
13616
|
+
});
|
|
13617
|
+
var MessageEventPayloadSchema = object({
|
|
13618
|
+
id: string2().optional(),
|
|
13619
|
+
body: string2().optional(),
|
|
13620
|
+
author: object({ kind: string2(), id: string2().optional() }).passthrough().optional()
|
|
13613
13621
|
});
|
|
13614
|
-
var MessageEventPayloadSchema = object({ body: string2().optional() });
|
|
13615
13622
|
var ChildStatusEventPayloadSchema = object({
|
|
13616
13623
|
child_key: string2().optional(),
|
|
13617
13624
|
from: string2().optional(),
|
|
@@ -13673,6 +13680,18 @@ function dispatchToolSchema(spec, z, opts) {
|
|
|
13673
13680
|
return spec.validation === undefined ? z.object(shape, opts) : z.refineObject(shape, spec.validation.check, spec.validation.message, opts);
|
|
13674
13681
|
}
|
|
13675
13682
|
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).";
|
|
13683
|
+
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.";
|
|
13684
|
+
function documentOwnerValidation(requireArtifact) {
|
|
13685
|
+
return {
|
|
13686
|
+
check: (value) => {
|
|
13687
|
+
const input = value;
|
|
13688
|
+
const hasIssue = typeof input.issue === "string";
|
|
13689
|
+
const hasProject = typeof input.project === "string";
|
|
13690
|
+
return (hasIssue !== hasProject || !hasIssue && !hasProject && typeof input.ref === "string") && (!hasProject || !requireArtifact || typeof input.artifact === "string");
|
|
13691
|
+
},
|
|
13692
|
+
message: "Exactly one of issue and project is required; with project, artifact names the document."
|
|
13693
|
+
};
|
|
13694
|
+
}
|
|
13676
13695
|
var SPEC_SECTIONS = [
|
|
13677
13696
|
"Decisions needed",
|
|
13678
13697
|
"Acceptance",
|
|
@@ -13700,9 +13719,11 @@ var dispatchToolSpecs = [
|
|
|
13700
13719
|
},
|
|
13701
13720
|
{
|
|
13702
13721
|
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. ${
|
|
13722
|
+
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
13723
|
arguments: (z) => ({
|
|
13705
|
-
issue: z.string().describe(ISSUE_REFERENCE),
|
|
13724
|
+
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
13725
|
+
project: z.string().describe("Project key owning the document.").optional(),
|
|
13726
|
+
artifact: z.string().describe("Project document slug or id.").optional(),
|
|
13706
13727
|
question: z.string({ max: 800 }).describe("Decision question, at most 800 characters."),
|
|
13707
13728
|
options: z.array(z.object({
|
|
13708
13729
|
label: z.string().describe("Selectable option label."),
|
|
@@ -13715,7 +13736,8 @@ var dispatchToolSpecs = [
|
|
|
13715
13736
|
quote: z.string().describe("Exact text the decision concerns."),
|
|
13716
13737
|
occurrence: z.number({ int: true, min: 0 }).describe("Zero-based occurrence of the quote.").optional()
|
|
13717
13738
|
}).describe("Optional document location for the question.").optional()
|
|
13718
|
-
})
|
|
13739
|
+
}),
|
|
13740
|
+
validation: documentOwnerValidation(true)
|
|
13719
13741
|
},
|
|
13720
13742
|
{
|
|
13721
13743
|
name: "dispatch_edit_ask",
|
|
@@ -13749,28 +13771,32 @@ var dispatchToolSpecs = [
|
|
|
13749
13771
|
},
|
|
13750
13772
|
{
|
|
13751
13773
|
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. ${
|
|
13774
|
+
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
13775
|
arguments: (z) => ({
|
|
13754
|
-
issue: z.string().describe(ISSUE_REFERENCE),
|
|
13776
|
+
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
13777
|
+
project: z.string().describe("Project key owning the document.").optional(),
|
|
13755
13778
|
artifact: z.string().describe("Artifact slug or id required when quote is given.").optional(),
|
|
13756
13779
|
quote: z.string().describe("Optional exact quoted document text.").optional(),
|
|
13757
13780
|
occurrence: z.number({ int: true, min: 0 }).describe("Optional zero-based occurrence of quote.").optional(),
|
|
13758
13781
|
body: z.string({ max: 2000 }).describe("Review comment, at most 2,000 characters."),
|
|
13759
13782
|
reply_to: z.string().describe("Optional comment id to reply to.").optional(),
|
|
13760
13783
|
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
|
-
})
|
|
13784
|
+
}),
|
|
13785
|
+
validation: documentOwnerValidation(true)
|
|
13762
13786
|
},
|
|
13763
13787
|
{
|
|
13764
13788
|
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. ${
|
|
13789
|
+
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
13790
|
arguments: (z) => ({
|
|
13767
|
-
issue: z.string().describe(ISSUE_REFERENCE),
|
|
13791
|
+
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
13792
|
+
project: z.string().describe("Project key owning the document.").optional(),
|
|
13768
13793
|
artifact: z.string().describe("Artifact slug or id containing the quoted text."),
|
|
13769
13794
|
quote: z.string().describe("Exact document text to replace."),
|
|
13770
13795
|
replace_with: z.string().describe("Replacement text."),
|
|
13771
13796
|
body: z.string({ max: 2000 }).describe("Optional rationale, at most 2,000 characters.").optional(),
|
|
13772
13797
|
occurrence: z.number({ int: true, min: 0 }).describe("Optional zero-based occurrence of quote.").optional()
|
|
13773
|
-
})
|
|
13798
|
+
}),
|
|
13799
|
+
validation: documentOwnerValidation(true)
|
|
13774
13800
|
},
|
|
13775
13801
|
{
|
|
13776
13802
|
name: "dispatch_message",
|
|
@@ -13782,9 +13808,10 @@ var dispatchToolSpecs = [
|
|
|
13782
13808
|
},
|
|
13783
13809
|
{
|
|
13784
13810
|
name: "dispatch_doc_edit",
|
|
13785
|
-
description: "Apply deterministic text edits to
|
|
13811
|
+
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
13812
|
arguments: (z) => ({
|
|
13787
|
-
issue: z.string().describe(ISSUE_REFERENCE),
|
|
13813
|
+
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
13814
|
+
project: z.string().describe("Project key owning the document.").optional(),
|
|
13788
13815
|
artifact: z.string().describe("Artifact slug or id for the document."),
|
|
13789
13816
|
ops: z.array(z.object({
|
|
13790
13817
|
op: z.enum(DOC_EDIT_OPS).describe("Edit operation."),
|
|
@@ -13796,23 +13823,27 @@ var dispatchToolSpecs = [
|
|
|
13796
13823
|
before: z.string().describe("Anchor before which to insert.").optional()
|
|
13797
13824
|
})).describe("Flat tagged edits; the server validates fields required for each operation."),
|
|
13798
13825
|
summary: z.string().describe("Optional named-version summary.").optional()
|
|
13799
|
-
})
|
|
13826
|
+
}),
|
|
13827
|
+
validation: documentOwnerValidation(true)
|
|
13800
13828
|
},
|
|
13801
13829
|
{
|
|
13802
13830
|
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
|
|
13831
|
+
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
13832
|
arguments: (z) => ({
|
|
13805
13833
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
13806
|
-
|
|
13834
|
+
project: z.string().describe("Project key owning the document.").optional(),
|
|
13835
|
+
artifact: z.string().describe("Optional artifact slug or id; primary document by default for an issue.").optional(),
|
|
13807
13836
|
version: z.number({ int: true, min: 1 }).describe("Optional version number.").optional(),
|
|
13808
13837
|
ref: z.string().describe("Optional dispatch:// document reference.").optional()
|
|
13809
|
-
})
|
|
13838
|
+
}),
|
|
13839
|
+
validation: documentOwnerValidation(true)
|
|
13810
13840
|
},
|
|
13811
13841
|
{
|
|
13812
13842
|
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. ${
|
|
13843
|
+
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
13844
|
arguments: (z) => ({
|
|
13815
|
-
issue: z.string().describe(ISSUE_REFERENCE),
|
|
13845
|
+
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
13846
|
+
project: z.string().describe("Project key for an unlinked document.").optional(),
|
|
13816
13847
|
name: z.string().describe("Artifact filename shown in Dispatch."),
|
|
13817
13848
|
path: z.string().describe("Local path to the file to upload.").optional(),
|
|
13818
13849
|
content: z.string().describe("Inline text to store as a Markdown document.").optional(),
|
|
@@ -13821,18 +13852,21 @@ var dispatchToolSpecs = [
|
|
|
13821
13852
|
validation: {
|
|
13822
13853
|
check: (value) => {
|
|
13823
13854
|
const input = value;
|
|
13824
|
-
return typeof input.path === "string" !== (typeof input.content === "string");
|
|
13855
|
+
return documentOwnerValidation(false).check(value) && typeof input.path === "string" !== (typeof input.content === "string");
|
|
13825
13856
|
},
|
|
13826
|
-
message: "Exactly one of path or content is required."
|
|
13857
|
+
message: "Exactly one of path or content is required. Exactly one of issue and project is required; with project, artifact names the document."
|
|
13827
13858
|
}
|
|
13828
13859
|
},
|
|
13829
13860
|
{
|
|
13830
13861
|
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
|
|
13862
|
+
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
13863
|
arguments: (z) => ({
|
|
13833
13864
|
issue: z.string().describe(ISSUE_REFERENCE).optional(),
|
|
13834
|
-
|
|
13835
|
-
|
|
13865
|
+
project: z.string().describe("Project key owning the document.").optional(),
|
|
13866
|
+
artifact: z.string().describe("Project document slug or id.").optional(),
|
|
13867
|
+
ref: z.string().describe("Optional dispatch:// issue or document reference.").optional()
|
|
13868
|
+
}),
|
|
13869
|
+
validation: documentOwnerValidation(true)
|
|
13836
13870
|
},
|
|
13837
13871
|
{
|
|
13838
13872
|
name: "dispatch_search",
|
|
@@ -13960,10 +13994,15 @@ var handoffMessageSchema = object({
|
|
|
13960
13994
|
});
|
|
13961
13995
|
// ../contracts/src/subject.ts
|
|
13962
13996
|
var AGENT_TOPIC_PREFIX = "notifications.agent.";
|
|
13963
|
-
var
|
|
13997
|
+
var DISPATCH_TOPIC_PREFIX = "notifications.dispatch.";
|
|
13998
|
+
var DISPATCH_ISSUE_TOPIC_PREFIX = `${DISPATCH_TOPIC_PREFIX}issue.`;
|
|
13999
|
+
var DISPATCH_DOCUMENT_TOPIC_PREFIX = `${DISPATCH_TOPIC_PREFIX}document.`;
|
|
13964
14000
|
function dispatchIssueSubject(issueKey, type) {
|
|
13965
14001
|
return `${DISPATCH_ISSUE_TOPIC_PREFIX}${issueKey}.${type}`;
|
|
13966
14002
|
}
|
|
14003
|
+
function dispatchDocumentSubject(project, slug, type) {
|
|
14004
|
+
return `${DISPATCH_DOCUMENT_TOPIC_PREFIX}${project}.${slug}.${type}`;
|
|
14005
|
+
}
|
|
13967
14006
|
function agentSubject(session) {
|
|
13968
14007
|
return `${AGENT_TOPIC_PREFIX}${session}`;
|
|
13969
14008
|
}
|
|
@@ -14482,6 +14521,25 @@ class DispatchClient {
|
|
|
14482
14521
|
async listIssues(options = {}) {
|
|
14483
14522
|
return this.#json("GET", ["api", "v1", "issues"], undefined, options);
|
|
14484
14523
|
}
|
|
14524
|
+
async listProjectArtifacts(project, unlinked = false) {
|
|
14525
|
+
return this.#json("GET", ["api", "v1", "projects", project, "artifacts"], undefined, unlinked ? { unlinked: "true" } : undefined);
|
|
14526
|
+
}
|
|
14527
|
+
async projectArtifact(project, input) {
|
|
14528
|
+
const artifactPath = ["api", "v1", "projects", project, "artifacts"];
|
|
14529
|
+
if ("content" in input)
|
|
14530
|
+
return this.#json("POST", artifactPath, input);
|
|
14531
|
+
const form = new FormData;
|
|
14532
|
+
form.set("name", input.name);
|
|
14533
|
+
if (input.summary !== undefined)
|
|
14534
|
+
form.set("summary", input.summary);
|
|
14535
|
+
if (input.actor !== undefined)
|
|
14536
|
+
form.set("actor", JSON.stringify(input.actor));
|
|
14537
|
+
form.set("file", input.file, input.name);
|
|
14538
|
+
return this.#form("POST", artifactPath, form);
|
|
14539
|
+
}
|
|
14540
|
+
async getProjectArtifact(project, slug) {
|
|
14541
|
+
return this.#json("GET", ["api", "v1", "projects", project, "artifacts", slug]);
|
|
14542
|
+
}
|
|
14485
14543
|
async search(query, options = {}) {
|
|
14486
14544
|
return this.#json("GET", ["api", "v1", "search"], undefined, { q: query, ...options });
|
|
14487
14545
|
}
|
|
@@ -14509,6 +14567,12 @@ class DispatchClient {
|
|
|
14509
14567
|
async comment(issue, input) {
|
|
14510
14568
|
return this.#json("POST", ["api", "v1", "issues", await this.#resolveIssue(issue), "comments"], input);
|
|
14511
14569
|
}
|
|
14570
|
+
async getArtifactAsks(id, state) {
|
|
14571
|
+
return this.#json("GET", ["api", "v1", "artifacts", id, "asks"], undefined, state === undefined ? undefined : { state });
|
|
14572
|
+
}
|
|
14573
|
+
async artifactAsk(id, input) {
|
|
14574
|
+
return this.#json("POST", ["api", "v1", "artifacts", id, "asks"], input);
|
|
14575
|
+
}
|
|
14512
14576
|
async suggest(issue, input) {
|
|
14513
14577
|
const { replace_with, ...comment } = input;
|
|
14514
14578
|
return this.#json("POST", ["api", "v1", "issues", await this.#resolveIssue(issue), "comments"], {
|
|
@@ -14516,6 +14580,19 @@ class DispatchClient {
|
|
|
14516
14580
|
suggestion: { replace_with }
|
|
14517
14581
|
});
|
|
14518
14582
|
}
|
|
14583
|
+
async getArtifactComments(id) {
|
|
14584
|
+
return this.#json("GET", ["api", "v1", "artifacts", id, "comments"]);
|
|
14585
|
+
}
|
|
14586
|
+
async artifactComment(id, input) {
|
|
14587
|
+
return this.#json("POST", ["api", "v1", "artifacts", id, "comments"], input);
|
|
14588
|
+
}
|
|
14589
|
+
async artifactSuggest(id, input) {
|
|
14590
|
+
const { replace_with, ...comment } = input;
|
|
14591
|
+
return this.#json("POST", ["api", "v1", "artifacts", id, "comments"], {
|
|
14592
|
+
...comment,
|
|
14593
|
+
suggestion: { replace_with }
|
|
14594
|
+
});
|
|
14595
|
+
}
|
|
14519
14596
|
async message(issue, input) {
|
|
14520
14597
|
return this.#json("POST", ["api", "v1", "issues", await this.#resolveIssue(issue), "messages"], input);
|
|
14521
14598
|
}
|
|
@@ -14544,6 +14621,12 @@ class DispatchClient {
|
|
|
14544
14621
|
async nameArtifactVersion(id, input) {
|
|
14545
14622
|
return this.#json("POST", ["api", "v1", "artifacts", id, "versions"], input);
|
|
14546
14623
|
}
|
|
14624
|
+
async getArtifactEvents(id, after = 0, limit = 200) {
|
|
14625
|
+
return this.#json("GET", ["api", "v1", "artifacts", id, "events"], undefined, { after, limit });
|
|
14626
|
+
}
|
|
14627
|
+
async getArtifactReferences(id) {
|
|
14628
|
+
return this.#json("GET", ["api", "v1", "artifacts", id, "references"]);
|
|
14629
|
+
}
|
|
14547
14630
|
async getAsk(id) {
|
|
14548
14631
|
return this.#json("GET", ["api", "v1", "asks", id]);
|
|
14549
14632
|
}
|
|
@@ -14553,6 +14636,15 @@ class DispatchClient {
|
|
|
14553
14636
|
async getComments(issue, artifact) {
|
|
14554
14637
|
return this.#json("GET", ["api", "v1", "issues", await this.#resolveIssue(issue), "comments"], undefined, artifact ? { artifact } : undefined);
|
|
14555
14638
|
}
|
|
14639
|
+
async getIssueReferences(issue) {
|
|
14640
|
+
return this.#json("GET", [
|
|
14641
|
+
"api",
|
|
14642
|
+
"v1",
|
|
14643
|
+
"issues",
|
|
14644
|
+
await this.#resolveIssue(issue),
|
|
14645
|
+
"references"
|
|
14646
|
+
]);
|
|
14647
|
+
}
|
|
14556
14648
|
async ensureIssue(issueReference, actor) {
|
|
14557
14649
|
if (!issueReference.includes("#"))
|
|
14558
14650
|
return issueReference;
|
|
@@ -14659,16 +14751,27 @@ class DispatchClient {
|
|
|
14659
14751
|
}
|
|
14660
14752
|
|
|
14661
14753
|
// ../envoy-client/src/dispatch-execute.ts
|
|
14662
|
-
|
|
14663
|
-
|
|
14664
|
-
|
|
14665
|
-
|
|
14666
|
-
|
|
14667
|
-
|
|
14668
|
-
|
|
14669
|
-
|
|
14670
|
-
|
|
14671
|
-
|
|
14754
|
+
function documentResultDetails(artifact) {
|
|
14755
|
+
return {
|
|
14756
|
+
project: artifact.project,
|
|
14757
|
+
artifact: artifact.id,
|
|
14758
|
+
document: `${artifact.project}/${artifact.slug}`,
|
|
14759
|
+
topic: dispatchDocumentSubject(artifact.project, artifact.slug, ">")
|
|
14760
|
+
};
|
|
14761
|
+
}
|
|
14762
|
+
function writeResultDetails(resolved, fields) {
|
|
14763
|
+
if (resolved.owner.kind === "project") {
|
|
14764
|
+
return { ...documentResultDetails(resolved.artifact), ...fields };
|
|
14765
|
+
}
|
|
14766
|
+
if (resolved.issue === undefined)
|
|
14767
|
+
throw new Error("issue document is missing its issue");
|
|
14768
|
+
return {
|
|
14769
|
+
issue: resolved.issue.key,
|
|
14770
|
+
topic: dispatchIssueSubject(resolved.issue.key, ">"),
|
|
14771
|
+
...fields
|
|
14772
|
+
};
|
|
14773
|
+
}
|
|
14774
|
+
async function askResultDetails(client, ask, resolved) {
|
|
14672
14775
|
if (ask.issue_key !== null) {
|
|
14673
14776
|
return {
|
|
14674
14777
|
issue: ask.issue_key,
|
|
@@ -14679,12 +14782,18 @@ async function askResultDetails(client, ask) {
|
|
|
14679
14782
|
if (ask.artifact_id === undefined || ask.artifact_id === null) {
|
|
14680
14783
|
throw new Error("document ask is missing its artifact ID");
|
|
14681
14784
|
}
|
|
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
|
-
};
|
|
14785
|
+
const artifact = resolved?.artifact ?? await client.getArtifact(ask.artifact_id);
|
|
14786
|
+
return { ...documentResultDetails(artifact), ask: ask.id };
|
|
14687
14787
|
}
|
|
14788
|
+
var nativeIssueKeyPattern = /^[A-Z][A-Z0-9]{1,9}-[0-9]+$/;
|
|
14789
|
+
var externalIssueRefPattern = /^([^/\s]+)\/([^/\s#]+)#([1-9][0-9]*)$/;
|
|
14790
|
+
var bareIssueNumberPattern = /^[1-9][0-9]*$/;
|
|
14791
|
+
var issueFreeTools = {
|
|
14792
|
+
dispatch_issue: true,
|
|
14793
|
+
dispatch_edit_ask: true,
|
|
14794
|
+
dispatch_resolve_ask: true,
|
|
14795
|
+
dispatch_search: true
|
|
14796
|
+
};
|
|
14688
14797
|
function canonicalExternalIssueRef(value) {
|
|
14689
14798
|
const match = value.trim().match(externalIssueRefPattern);
|
|
14690
14799
|
return match ? `${canonicalRepo(match[1] ?? "", match[2] ?? "")}#${match[3]}` : value;
|
|
@@ -14729,31 +14838,54 @@ function askUrgency(args) {
|
|
|
14729
14838
|
return ASK_URGENCIES.find((urgency) => urgency === value);
|
|
14730
14839
|
}
|
|
14731
14840
|
function parseDispatchRef(ref) {
|
|
14732
|
-
const
|
|
14733
|
-
if (
|
|
14841
|
+
const projectDocument = ref.match(/^dispatch:\/\/([A-Z][A-Z0-9]{1,9})\/artifact\/([^/@]+)(?:@v(\d+))?(?:\/(ask|comment)\/([^/]+))?$/);
|
|
14842
|
+
if (projectDocument) {
|
|
14843
|
+
const [, project, artifact, version, targetKind, targetID] = projectDocument;
|
|
14844
|
+
if (project === undefined || artifact === undefined || version !== undefined && Number(version) < 1) {
|
|
14845
|
+
return null;
|
|
14846
|
+
}
|
|
14847
|
+
if (targetKind === undefined) {
|
|
14848
|
+
return {
|
|
14849
|
+
owner: { kind: "project", project },
|
|
14850
|
+
kind: "artifact",
|
|
14851
|
+
id: artifact,
|
|
14852
|
+
...version === undefined ? {} : { version: Number(version) }
|
|
14853
|
+
};
|
|
14854
|
+
}
|
|
14855
|
+
if (targetID === undefined || targetKind !== "ask" && targetKind !== "comment")
|
|
14856
|
+
return null;
|
|
14857
|
+
return {
|
|
14858
|
+
owner: { kind: "project", project },
|
|
14859
|
+
kind: targetKind,
|
|
14860
|
+
id: targetID
|
|
14861
|
+
};
|
|
14862
|
+
}
|
|
14863
|
+
const issueReference = ref.match(/^dispatch:\/\/([A-Z][A-Z0-9]{1,9}-[1-9][0-9]*)(?:\/(spec)|\/(log)|\/(children)|\/artifact\/([^/@]+)(?:@v(\d+))?|\/ask\/([^/]+)|\/comment\/([^/]+))?$/);
|
|
14864
|
+
if (!issueReference)
|
|
14734
14865
|
return null;
|
|
14735
|
-
const [, issue, spec, log, children, artifact, version, ask, comment] =
|
|
14866
|
+
const [, issue, spec, log, children, artifact, version, ask, comment] = issueReference;
|
|
14736
14867
|
if (!issue || version !== undefined && Number(version) < 1)
|
|
14737
14868
|
return null;
|
|
14869
|
+
const owner = { kind: "issue", issue };
|
|
14738
14870
|
if (spec)
|
|
14739
|
-
return {
|
|
14871
|
+
return { owner, kind: "spec", id: spec };
|
|
14740
14872
|
if (log)
|
|
14741
|
-
return {
|
|
14873
|
+
return { owner, kind: "log", id: log };
|
|
14742
14874
|
if (children)
|
|
14743
|
-
return {
|
|
14875
|
+
return { owner, kind: "children", id: children };
|
|
14744
14876
|
if (artifact) {
|
|
14745
14877
|
return {
|
|
14746
|
-
|
|
14878
|
+
owner,
|
|
14747
14879
|
kind: "artifact",
|
|
14748
14880
|
id: artifact,
|
|
14749
14881
|
...version === undefined ? {} : { version: Number(version) }
|
|
14750
14882
|
};
|
|
14751
14883
|
}
|
|
14752
14884
|
if (ask)
|
|
14753
|
-
return {
|
|
14885
|
+
return { owner, kind: "ask", id: ask };
|
|
14754
14886
|
if (comment)
|
|
14755
|
-
return {
|
|
14756
|
-
return {
|
|
14887
|
+
return { owner, kind: "comment", id: comment };
|
|
14888
|
+
return { owner, kind: "issue", id: issue };
|
|
14757
14889
|
}
|
|
14758
14890
|
function askId(args) {
|
|
14759
14891
|
const ask = stringArg(args, "ask");
|
|
@@ -14771,32 +14903,68 @@ function toolSchema(tool) {
|
|
|
14771
14903
|
throw new Error(`Unknown Dispatch tool: ${tool}`);
|
|
14772
14904
|
return dispatchToolSchema(spec, zodSchemaApi(exports_external), { strict: true });
|
|
14773
14905
|
}
|
|
14774
|
-
async function
|
|
14775
|
-
if (issueFreeTools
|
|
14776
|
-
return { args, ref: null };
|
|
14906
|
+
async function resolveOwnerArguments(tool, args, cwd, env, exec) {
|
|
14907
|
+
if (issueFreeTools[tool] === true)
|
|
14908
|
+
return { args, ref: null, owner: null };
|
|
14777
14909
|
const refArgument = args.ref;
|
|
14778
14910
|
const ref = typeof refArgument === "string" ? parseDispatchRef(refArgument) ?? (() => {
|
|
14779
|
-
throw new Error("ref must be a valid dispatch:// reference");
|
|
14911
|
+
throw new Error("ref must be a valid dispatch:// reference such as dispatch://KEY-1, " + "dispatch://KEY-1/ask/<uuid>, dispatch://KEY-1/comment/<uuid>, " + "dispatch://KEY-1/artifact/<slug>, or dispatch://PROJECT/artifact/<slug>");
|
|
14780
14912
|
})() : null;
|
|
14781
14913
|
const issueArgument = args.issue;
|
|
14914
|
+
const projectArgument = args.project;
|
|
14782
14915
|
const artifactArgument = args.artifact;
|
|
14783
14916
|
const versionArgument = args.version;
|
|
14917
|
+
if (issueArgument !== undefined && projectArgument !== undefined) {
|
|
14918
|
+
throw new Error("exactly one of issue and project is required");
|
|
14919
|
+
}
|
|
14920
|
+
if (typeof projectArgument === "string") {
|
|
14921
|
+
if (!/^[A-Z][A-Z0-9]{1,9}$/.test(projectArgument)) {
|
|
14922
|
+
throw new Error("project must be a project key such as CORE");
|
|
14923
|
+
}
|
|
14924
|
+
if (ref?.owner.kind === "project" && (ref.owner.project !== projectArgument || artifactArgument !== undefined && artifactArgument !== ref.id)) {
|
|
14925
|
+
throw new Error("project and ref must name the same document");
|
|
14926
|
+
}
|
|
14927
|
+
return {
|
|
14928
|
+
args: {
|
|
14929
|
+
...args,
|
|
14930
|
+
...artifactArgument === undefined && ref?.owner.kind === "project" ? { artifact: ref.id } : {},
|
|
14931
|
+
...versionArgument === undefined && ref?.version !== undefined ? { version: ref.version } : {}
|
|
14932
|
+
},
|
|
14933
|
+
ref,
|
|
14934
|
+
owner: { kind: "project", project: projectArgument }
|
|
14935
|
+
};
|
|
14936
|
+
}
|
|
14937
|
+
if (ref?.owner.kind === "project") {
|
|
14938
|
+
return {
|
|
14939
|
+
args: {
|
|
14940
|
+
...args,
|
|
14941
|
+
project: ref.owner.project,
|
|
14942
|
+
...artifactArgument === undefined ? { artifact: ref.id } : {},
|
|
14943
|
+
...versionArgument === undefined && ref.version !== undefined ? { version: ref.version } : {}
|
|
14944
|
+
},
|
|
14945
|
+
ref,
|
|
14946
|
+
owner: ref.owner
|
|
14947
|
+
};
|
|
14948
|
+
}
|
|
14784
14949
|
if (issueArgument !== undefined || ref !== null) {
|
|
14950
|
+
const issue = typeof issueArgument === "string" ? canonicalExternalIssueRef(issueArgument) : ref?.owner.kind === "issue" ? ref.owner.issue : undefined;
|
|
14785
14951
|
return {
|
|
14786
14952
|
args: {
|
|
14787
14953
|
...args,
|
|
14788
|
-
...
|
|
14954
|
+
...issue === undefined ? {} : { issue },
|
|
14789
14955
|
...artifactArgument === undefined && (ref?.kind === "spec" || ref?.kind === "artifact") ? { artifact: ref.id } : {},
|
|
14790
14956
|
...versionArgument === undefined && ref?.version !== undefined ? { version: ref.version } : {}
|
|
14791
14957
|
},
|
|
14792
|
-
ref
|
|
14958
|
+
ref,
|
|
14959
|
+
owner: issue === undefined ? null : { kind: "issue", issue }
|
|
14793
14960
|
};
|
|
14794
14961
|
}
|
|
14795
14962
|
const legionIssue = env.LEGION_ISSUE;
|
|
14796
14963
|
if (!legionIssue)
|
|
14797
14964
|
throw new Error("issue is required; supply issue or set LEGION_ISSUE");
|
|
14798
14965
|
if (nativeIssueKeyPattern.test(legionIssue) || externalIssueRefPattern.test(legionIssue)) {
|
|
14799
|
-
|
|
14966
|
+
const issue = canonicalExternalIssueRef(legionIssue);
|
|
14967
|
+
return { args: { ...args, issue }, ref: null, owner: { kind: "issue", issue } };
|
|
14800
14968
|
}
|
|
14801
14969
|
if (!bareIssueNumberPattern.test(legionIssue)) {
|
|
14802
14970
|
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 +14972,35 @@ async function resolveIssueArguments(tool, args, cwd, env, exec) {
|
|
|
14804
14972
|
const repo = await resolveCwdRepo(cwd, exec);
|
|
14805
14973
|
if (!repo)
|
|
14806
14974
|
throw new Error("issue is required; LEGION_ISSUE needs a GitHub repository in cwd");
|
|
14807
|
-
|
|
14975
|
+
const issue = `${repo}#${legionIssue}`;
|
|
14976
|
+
return { args: { ...args, issue }, ref: null, owner: { kind: "issue", issue } };
|
|
14808
14977
|
}
|
|
14809
|
-
async function resolveArtifact(client,
|
|
14810
|
-
|
|
14811
|
-
|
|
14978
|
+
async function resolveArtifact(client, owner, artifactReference) {
|
|
14979
|
+
if (owner.kind === "project") {
|
|
14980
|
+
if (artifactReference === undefined) {
|
|
14981
|
+
throw new Error("artifact is required for a project document");
|
|
14982
|
+
}
|
|
14983
|
+
try {
|
|
14984
|
+
return {
|
|
14985
|
+
owner,
|
|
14986
|
+
artifact: await client.getProjectArtifact(owner.project, artifactReference)
|
|
14987
|
+
};
|
|
14988
|
+
} catch (error) {
|
|
14989
|
+
if (!(error instanceof DispatchServiceError) || error.status !== 404)
|
|
14990
|
+
throw error;
|
|
14991
|
+
const artifacts = await client.listProjectArtifacts(owner.project);
|
|
14992
|
+
const artifact = artifacts.find((candidate) => candidate.name === artifactReference);
|
|
14993
|
+
if (!artifact)
|
|
14994
|
+
throw error;
|
|
14995
|
+
return { owner, artifact };
|
|
14996
|
+
}
|
|
14997
|
+
}
|
|
14998
|
+
const issue = await client.getIssue(owner.issue);
|
|
14999
|
+
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 || candidate.name === artifactReference);
|
|
14812
15000
|
if (!artifact) {
|
|
14813
15001
|
throw new Error(`artifact ${artifactReference ?? "spec"} was not found on issue ${issue.key}`);
|
|
14814
15002
|
}
|
|
14815
|
-
return { issue, artifact };
|
|
15003
|
+
return { owner, issue, artifact };
|
|
14816
15004
|
}
|
|
14817
15005
|
function anchor(artifact, args) {
|
|
14818
15006
|
const quote = optionalString(args, "quote");
|
|
@@ -14832,7 +15020,7 @@ function toolActor(origin, input) {
|
|
|
14832
15020
|
}
|
|
14833
15021
|
};
|
|
14834
15022
|
}
|
|
14835
|
-
function issueSummary(issue, events) {
|
|
15023
|
+
function issueSummary(issue, events, references) {
|
|
14836
15024
|
const asks = issue.open_asks;
|
|
14837
15025
|
return [
|
|
14838
15026
|
`Title: ${issue.title}`,
|
|
@@ -14841,6 +15029,9 @@ function issueSummary(issue, events) {
|
|
|
14841
15029
|
`Route: ${issue.route ?? "none"}`,
|
|
14842
15030
|
"Open asks:",
|
|
14843
15031
|
...asks.length === 0 ? ["- none"] : asks.map((ask) => `- ${ask.id}: ${ask.question}`),
|
|
15032
|
+
"References:",
|
|
15033
|
+
...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}`),
|
|
15034
|
+
...typeof references === "string" || !references.truncated ? [] : ["- more references beyond 8 hops"],
|
|
14844
15035
|
"Events:",
|
|
14845
15036
|
...events.length === 0 ? ["- none"] : events.map((event) => `- #${event.seq} ${event.type} \xB7 ${event.actor.kind} ${event.actor.id} \xB7 ${event.created_at}`)
|
|
14846
15037
|
].join(`
|
|
@@ -14905,10 +15096,11 @@ function commentSummary({ comment, replies }) {
|
|
|
14905
15096
|
`);
|
|
14906
15097
|
}
|
|
14907
15098
|
async function openArtifactMarks(client, resolved) {
|
|
14908
|
-
const
|
|
15099
|
+
const asks = resolved.owner.kind === "project" ? await client.getArtifactAsks(resolved.artifact.id) : resolved.issue?.open_asks ?? [];
|
|
15100
|
+
const marks = asks.filter((ask) => ask.state === "open" && ask.anchor?.artifact_id === resolved.artifact.id).map((ask) => `ask ${ask.id}`);
|
|
14909
15101
|
let comments;
|
|
14910
15102
|
try {
|
|
14911
|
-
comments = await client.getComments(resolved.issue
|
|
15103
|
+
comments = resolved.owner.kind === "project" ? await client.getArtifactComments(resolved.artifact.id) : await client.getComments(resolved.issue?.key ?? "", resolved.artifact.id);
|
|
14912
15104
|
} catch (error) {
|
|
14913
15105
|
if (error instanceof DispatchServiceError && error.status === 404)
|
|
14914
15106
|
return marks;
|
|
@@ -14927,15 +15119,23 @@ async function executeDispatchTool(input) {
|
|
|
14927
15119
|
}
|
|
14928
15120
|
const env = input.env ?? process.env;
|
|
14929
15121
|
const exec = input.exec ?? defaultExec;
|
|
14930
|
-
const
|
|
14931
|
-
const args = toolSchema(input.tool).parse(
|
|
15122
|
+
const ownerArguments = await resolveOwnerArguments(input.tool, input.args, input.cwd, env, exec);
|
|
15123
|
+
const args = toolSchema(input.tool).parse(ownerArguments.args);
|
|
14932
15124
|
const actor = toolActor(await resolveOrigin(env, exec, input.cwd), input);
|
|
14933
15125
|
const client = new DispatchClient(configUrl, configToken, input.fetchImpl);
|
|
14934
|
-
const
|
|
15126
|
+
const owner = ownerArguments.owner?.kind === "issue" ? {
|
|
15127
|
+
kind: "issue",
|
|
15128
|
+
issue: await ensureIssue(client, ownerArguments.owner.issue, actor)
|
|
15129
|
+
} : ownerArguments.owner;
|
|
14935
15130
|
const issue = () => {
|
|
14936
|
-
if (
|
|
15131
|
+
if (owner?.kind !== "issue")
|
|
14937
15132
|
throw new Error("issue is required");
|
|
14938
|
-
return
|
|
15133
|
+
return owner.issue;
|
|
15134
|
+
};
|
|
15135
|
+
const documentOwner = () => {
|
|
15136
|
+
if (owner === null)
|
|
15137
|
+
throw new Error("issue or project is required");
|
|
15138
|
+
return owner;
|
|
14939
15139
|
};
|
|
14940
15140
|
switch (input.tool) {
|
|
14941
15141
|
case "dispatch_issue": {
|
|
@@ -15015,22 +15215,25 @@ async function executeDispatchTool(input) {
|
|
|
15015
15215
|
}
|
|
15016
15216
|
case "dispatch_ask": {
|
|
15017
15217
|
const anchorArgs = asObject(args.anchor);
|
|
15018
|
-
const
|
|
15218
|
+
const owner = documentOwner();
|
|
15219
|
+
const artifactReference = optionalString(args, "artifact") ?? (anchorArgs === null ? undefined : optionalString(anchorArgs, "artifact"));
|
|
15220
|
+
const resolved = owner.kind === "project" || artifactReference === undefined ? owner.kind === "project" ? await resolveArtifact(client, owner, artifactReference) : undefined : await resolveArtifact(client, owner, artifactReference);
|
|
15019
15221
|
const options = args.options;
|
|
15020
15222
|
const multiple = optionalBoolean(args, "multiple");
|
|
15021
15223
|
const urgency = askUrgency(args);
|
|
15022
15224
|
const anchored = anchorArgs && resolved ? anchor(resolved.artifact, anchorArgs) : undefined;
|
|
15023
|
-
const
|
|
15225
|
+
const askInput = {
|
|
15024
15226
|
question: stringArg(args, "question"),
|
|
15025
15227
|
...Array.isArray(options) ? { options } : {},
|
|
15026
15228
|
...multiple === undefined ? {} : { multiple },
|
|
15027
15229
|
...urgency === undefined ? {} : { urgency },
|
|
15028
15230
|
...anchored === undefined ? {} : { anchor: anchored },
|
|
15029
15231
|
actor
|
|
15030
|
-
}
|
|
15232
|
+
};
|
|
15233
|
+
const ask = resolved?.owner.kind === "project" ? await client.artifactAsk(resolved.artifact.id, askInput) : await client.ask(issue(), askInput);
|
|
15031
15234
|
return {
|
|
15032
15235
|
text: `Opened ask ${ask.id}: ${ask.question}`,
|
|
15033
|
-
details: await askResultDetails(client, ask)
|
|
15236
|
+
details: await askResultDetails(client, ask, resolved)
|
|
15034
15237
|
};
|
|
15035
15238
|
}
|
|
15036
15239
|
case "dispatch_edit_ask": {
|
|
@@ -15055,48 +15258,47 @@ async function executeDispatchTool(input) {
|
|
|
15055
15258
|
if (optionalString(args, "quote") !== undefined && artifactReference === undefined) {
|
|
15056
15259
|
throw new Error("artifact is required when quote is supplied");
|
|
15057
15260
|
}
|
|
15058
|
-
const
|
|
15261
|
+
const owner = documentOwner();
|
|
15262
|
+
const resolved = owner.kind === "project" || artifactReference === undefined ? owner.kind === "project" ? await resolveArtifact(client, owner, artifactReference) : undefined : await resolveArtifact(client, owner, artifactReference);
|
|
15059
15263
|
const anchored = resolved ? anchor(resolved.artifact, args) : undefined;
|
|
15060
15264
|
const replyTo = optionalString(args, "reply_to");
|
|
15061
15265
|
const replyToAsk = optionalString(args, "reply_to_ask");
|
|
15062
15266
|
if (replyTo !== undefined && replyToAsk !== undefined) {
|
|
15063
15267
|
throw new Error("reply_to and reply_to_ask cannot both be set");
|
|
15064
15268
|
}
|
|
15065
|
-
const
|
|
15269
|
+
const commentInput = {
|
|
15066
15270
|
body: stringArg(args, "body"),
|
|
15067
15271
|
...anchored === undefined ? {} : { anchor: anchored },
|
|
15068
15272
|
...replyTo === undefined ? {} : { reply_to: replyTo },
|
|
15069
15273
|
...replyToAsk === undefined ? {} : { ask_id: replyToAsk },
|
|
15070
15274
|
actor
|
|
15071
|
-
}
|
|
15275
|
+
};
|
|
15276
|
+
const comment = resolved?.owner.kind === "project" ? await client.artifactComment(resolved.artifact.id, commentInput) : await client.comment(issue(), commentInput);
|
|
15072
15277
|
return {
|
|
15073
15278
|
text: `Posted comment ${comment.id}`,
|
|
15074
|
-
details: {
|
|
15279
|
+
details: resolved === undefined ? {
|
|
15075
15280
|
issue: comment.issue_key,
|
|
15076
|
-
topic: dispatchIssueSubject(
|
|
15281
|
+
topic: dispatchIssueSubject(issue(), ">"),
|
|
15077
15282
|
comment: comment.id
|
|
15078
|
-
}
|
|
15283
|
+
} : writeResultDetails(resolved, { comment: comment.id })
|
|
15079
15284
|
};
|
|
15080
15285
|
}
|
|
15081
15286
|
case "dispatch_suggest": {
|
|
15082
|
-
const resolved = await resolveArtifact(client,
|
|
15287
|
+
const resolved = await resolveArtifact(client, documentOwner(), stringArg(args, "artifact"));
|
|
15083
15288
|
const anchored = anchor(resolved.artifact, args);
|
|
15084
15289
|
if (anchored === undefined)
|
|
15085
15290
|
throw new Error("quote is required");
|
|
15086
15291
|
const body = optionalString(args, "body");
|
|
15087
|
-
const
|
|
15292
|
+
const suggestionInput = {
|
|
15088
15293
|
...body === undefined ? {} : { body },
|
|
15089
15294
|
anchor: anchored,
|
|
15090
15295
|
replace_with: stringArg(args, "replace_with"),
|
|
15091
15296
|
actor
|
|
15092
|
-
}
|
|
15297
|
+
};
|
|
15298
|
+
const comment = resolved.owner.kind === "project" ? await client.artifactSuggest(resolved.artifact.id, suggestionInput) : await client.suggest(issue(), suggestionInput);
|
|
15093
15299
|
return {
|
|
15094
15300
|
text: `Posted suggestion ${comment.id}`,
|
|
15095
|
-
details: {
|
|
15096
|
-
issue: comment.issue_key,
|
|
15097
|
-
topic: dispatchIssueSubject(comment.issue_key, ">"),
|
|
15098
|
-
comment: comment.id
|
|
15099
|
-
}
|
|
15301
|
+
details: writeResultDetails(resolved, { comment: comment.id })
|
|
15100
15302
|
};
|
|
15101
15303
|
}
|
|
15102
15304
|
case "dispatch_message": {
|
|
@@ -15111,7 +15313,7 @@ async function executeDispatchTool(input) {
|
|
|
15111
15313
|
};
|
|
15112
15314
|
}
|
|
15113
15315
|
case "dispatch_doc_edit": {
|
|
15114
|
-
const resolved = await resolveArtifact(client,
|
|
15316
|
+
const resolved = await resolveArtifact(client, documentOwner(), stringArg(args, "artifact"));
|
|
15115
15317
|
const ops = args.ops;
|
|
15116
15318
|
const summary = optionalString(args, "summary");
|
|
15117
15319
|
const edited = await client.docEdit(resolved.artifact.id, {
|
|
@@ -15121,32 +15323,33 @@ async function executeDispatchTool(input) {
|
|
|
15121
15323
|
});
|
|
15122
15324
|
return {
|
|
15123
15325
|
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, ">"),
|
|
15326
|
+
details: writeResultDetails(resolved, {
|
|
15127
15327
|
applied: edited.applied,
|
|
15128
15328
|
...edited.version === null ? {} : { version: edited.version.number }
|
|
15129
|
-
}
|
|
15329
|
+
})
|
|
15130
15330
|
};
|
|
15131
15331
|
}
|
|
15132
15332
|
case "dispatch_doc_read": {
|
|
15133
|
-
const artifactReference = optionalString(args, "artifact") ?? (
|
|
15134
|
-
const resolved = await resolveArtifact(client,
|
|
15135
|
-
const version = optionalNumber(args, "version") ??
|
|
15333
|
+
const artifactReference = optionalString(args, "artifact") ?? (ownerArguments.ref?.kind === "spec" || ownerArguments.ref?.kind === "artifact" ? ownerArguments.ref.id : undefined);
|
|
15334
|
+
const resolved = await resolveArtifact(client, documentOwner(), artifactReference);
|
|
15335
|
+
const version = optionalNumber(args, "version") ?? ownerArguments.ref?.version;
|
|
15136
15336
|
const document = await client.docRead(resolved.artifact.id, version);
|
|
15137
15337
|
const marks = await openArtifactMarks(client, resolved);
|
|
15138
15338
|
return {
|
|
15139
15339
|
text: marks.length === 0 ? document.markdown : `${document.markdown}
|
|
15140
15340
|
|
|
15141
15341
|
Open anchored asks/comments: ${marks.join(", ")}`,
|
|
15142
|
-
details:
|
|
15342
|
+
details: resolved.owner.kind === "project" ? {
|
|
15343
|
+
project: resolved.artifact.project,
|
|
15344
|
+
document: `${resolved.artifact.project}/${resolved.artifact.slug}`
|
|
15345
|
+
} : { issue: resolved.issue?.key }
|
|
15143
15346
|
};
|
|
15144
15347
|
}
|
|
15145
15348
|
case "dispatch_artifact": {
|
|
15146
15349
|
const summary = optionalString(args, "summary");
|
|
15147
15350
|
const name = stringArg(args, "name");
|
|
15148
15351
|
const content = optionalString(args, "content");
|
|
15149
|
-
const
|
|
15352
|
+
const artifactInput = content === undefined ? {
|
|
15150
15353
|
name,
|
|
15151
15354
|
file: Bun.file(resolvePath(input.cwd, stringArg(args, "path"))),
|
|
15152
15355
|
...summary === undefined ? {} : { summary },
|
|
@@ -15156,47 +15359,74 @@ Open anchored asks/comments: ${marks.join(", ")}`,
|
|
|
15156
15359
|
content,
|
|
15157
15360
|
...summary === undefined ? {} : { summary },
|
|
15158
15361
|
actor
|
|
15159
|
-
}
|
|
15362
|
+
};
|
|
15363
|
+
const artifactOwner = documentOwner();
|
|
15364
|
+
const result = artifactOwner.kind === "project" ? await client.projectArtifact(artifactOwner.project, artifactInput) : await client.artifact(issue(), artifactInput);
|
|
15365
|
+
const artifactRef = artifactOwner.kind === "project" ? `dispatch://${artifactOwner.project}/artifact/${result.artifact.slug}` : `dispatch://${issue()}/artifact/${result.artifact.slug}`;
|
|
15160
15366
|
return {
|
|
15161
|
-
text: `Uploaded ${result.artifact.name} as version ${result.version.number}`,
|
|
15162
|
-
details: {
|
|
15163
|
-
|
|
15164
|
-
|
|
15367
|
+
text: `Uploaded ${result.artifact.name} as version ${result.version.number} (artifact slug ${result.artifact.slug}; ${artifactRef})`,
|
|
15368
|
+
details: artifactOwner.kind === "project" ? {
|
|
15369
|
+
...documentResultDetails(result.artifact),
|
|
15370
|
+
version: result.version.number
|
|
15371
|
+
} : {
|
|
15372
|
+
issue: issue(),
|
|
15373
|
+
topic: dispatchIssueSubject(issue(), ">"),
|
|
15165
15374
|
artifact: result.artifact.id,
|
|
15166
15375
|
version: result.version.number
|
|
15167
15376
|
}
|
|
15168
15377
|
};
|
|
15169
15378
|
}
|
|
15170
15379
|
case "dispatch_read": {
|
|
15171
|
-
if (
|
|
15172
|
-
const askRead = await client.getAsk(
|
|
15380
|
+
if (ownerArguments.ref?.kind === "ask") {
|
|
15381
|
+
const askRead = await client.getAsk(ownerArguments.ref.id);
|
|
15173
15382
|
return {
|
|
15174
15383
|
text: askSummary(askRead),
|
|
15175
|
-
details: { issue:
|
|
15384
|
+
details: ownerArguments.ref.owner.kind === "project" ? { project: ownerArguments.ref.owner.project } : { issue: ownerArguments.ref.owner.issue }
|
|
15176
15385
|
};
|
|
15177
15386
|
}
|
|
15178
|
-
if (
|
|
15179
|
-
const comment = await client.getComment(
|
|
15387
|
+
if (ownerArguments.ref?.kind === "comment") {
|
|
15388
|
+
const comment = await client.getComment(ownerArguments.ref.id);
|
|
15180
15389
|
return {
|
|
15181
15390
|
text: commentSummary(comment),
|
|
15182
|
-
details: { issue: comment.comment.issue_key }
|
|
15391
|
+
details: ownerArguments.ref.owner.kind === "project" ? { project: ownerArguments.ref.owner.project } : { issue: comment.comment.issue_key }
|
|
15392
|
+
};
|
|
15393
|
+
}
|
|
15394
|
+
if (documentOwner().kind === "project") {
|
|
15395
|
+
const resolved = await resolveArtifact(client, documentOwner(), stringArg(args, "artifact"));
|
|
15396
|
+
return {
|
|
15397
|
+
text: [
|
|
15398
|
+
`Document: ${resolved.artifact.project} / ${resolved.artifact.name}`,
|
|
15399
|
+
`Reference: dispatch://${resolved.artifact.project}/artifact/${resolved.artifact.slug}`,
|
|
15400
|
+
`Versions: ${resolved.artifact.versions.length}`
|
|
15401
|
+
].join(`
|
|
15402
|
+
`),
|
|
15403
|
+
details: {
|
|
15404
|
+
project: resolved.artifact.project,
|
|
15405
|
+
document: `${resolved.artifact.project}/${resolved.artifact.slug}`
|
|
15406
|
+
}
|
|
15183
15407
|
};
|
|
15184
15408
|
}
|
|
15185
15409
|
const read = await client.read(issue());
|
|
15186
|
-
if (
|
|
15410
|
+
if (ownerArguments.ref?.kind === "log") {
|
|
15187
15411
|
return {
|
|
15188
15412
|
text: logSummary(read.issue, read.events),
|
|
15189
15413
|
details: { issue: read.issue.key }
|
|
15190
15414
|
};
|
|
15191
15415
|
}
|
|
15192
|
-
if (
|
|
15416
|
+
if (ownerArguments.ref?.kind === "children") {
|
|
15193
15417
|
return {
|
|
15194
15418
|
text: childrenSummary(read.issue),
|
|
15195
15419
|
details: { issue: read.issue.key }
|
|
15196
15420
|
};
|
|
15197
15421
|
}
|
|
15422
|
+
let references;
|
|
15423
|
+
try {
|
|
15424
|
+
references = await client.getIssueReferences(read.issue.key);
|
|
15425
|
+
} catch (error) {
|
|
15426
|
+
references = error instanceof DispatchServiceError && error.status === 404 ? "unavailable" : `unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
15427
|
+
}
|
|
15198
15428
|
return {
|
|
15199
|
-
text: issueSummary(read.issue, read.events),
|
|
15429
|
+
text: issueSummary(read.issue, read.events, references),
|
|
15200
15430
|
details: { issue: read.issue.key }
|
|
15201
15431
|
};
|
|
15202
15432
|
}
|
package/package.json
CHANGED
package/skills/dispatch/SKILL.md
CHANGED
|
@@ -5,8 +5,8 @@ description: "Use when asking Sami a question, updating the spec, commenting on
|
|
|
5
5
|
|
|
6
6
|
# Dispatch
|
|
7
7
|
|
|
8
|
-
Dispatch is your issue's living spec, asks, comments, and artifacts. The
|
|
9
|
-
scratch pad. Anything meant for a human goes through a `dispatch_*` tool.
|
|
8
|
+
Dispatch is your issue's or project document's living spec, asks, comments, and artifacts. The
|
|
9
|
+
transcript is your scratch pad. Anything meant for a human goes through a `dispatch_*` tool.
|
|
10
10
|
|
|
11
11
|
The server enforces high signal: an ask question is at most 800 characters with at most eight
|
|
12
12
|
options; comment and message bodies are at most 2,000 characters; an artifact is at most 25 MiB.
|
|
@@ -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,17 @@ 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
|
-
|
|
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:
|
|
139
146
|
|
|
140
147
|
```ts
|
|
141
|
-
dispatch_doc_edit({ issue
|
|
148
|
+
dispatch_doc_edit({ issue?, project?, artifact, ops, summary? })
|
|
142
149
|
```
|
|
143
|
-
It returns
|
|
144
|
-
`EditOp` shape:
|
|
150
|
+
It returns issue or project-document owner details plus `applied`, optional `version`, and its
|
|
151
|
+
write `topic`. `ops` is an array of this exact `EditOp` shape:
|
|
145
152
|
|
|
146
153
|
```ts
|
|
147
154
|
type EditOp = {
|
|
@@ -156,38 +163,48 @@ type EditOp = {
|
|
|
156
163
|
```
|
|
157
164
|
|
|
158
165
|
Target `replace` and `delete` by the document's plain text: inline-code and link text match
|
|
159
|
-
without Markdown syntax, and a table-cell anchor is its cell text.
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
166
|
+
without Markdown syntax, and a table-cell anchor is its cell text. Quote code-block contents
|
|
167
|
+
without their Markdown fences. A quote must stay within one textblock; split changes that
|
|
168
|
+
span separate blocks into separate operations.
|
|
169
|
+
|
|
170
|
+
`replace` requires `find` and `with`; `delete` requires `find`; `insert` requires `markdown`
|
|
171
|
+
and exactly one of `after` or `before`. An insert anchor is a quote, `"start"`, `"end"`, or
|
|
172
|
+
`"heading:Title"`. Ordinary inserts create a sibling block before or after the quote or
|
|
173
|
+
heading's enclosing document block; `"start"` and `"end"` select the document edges.
|
|
174
|
+
|
|
175
|
+
At a table-cell quote, pipe-table body-row fragments extend that table before or after the
|
|
176
|
+
matched row; omit table header and delimiter rows. Short rows are padded to the table width;
|
|
177
|
+
rows wider than the table are rejected.
|
|
178
|
+
Deleting a cell's quoted text removes that text, not the surrounding row or table.
|
|
179
|
+
Use `replace` for inline continuation. Use zero-based `occurrence` for a repeated target;
|
|
180
|
+
re-read a missing or ambiguous target before retrying. Pass `summary` to name the version
|
|
181
|
+
when recording a decision. Never paste progress into a message.
|
|
168
182
|
|
|
169
183
|
## Comments and suggestions
|
|
170
184
|
|
|
171
185
|
Add feedback with:
|
|
172
186
|
|
|
173
187
|
```ts
|
|
174
|
-
dispatch_comment({ issue
|
|
188
|
+
dispatch_comment({ issue?, project?, artifact?, quote?, occurrence?, body, reply_to?, reply_to_ask? })
|
|
175
189
|
```
|
|
176
190
|
|
|
177
|
-
It returns
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
thread
|
|
181
|
-
`
|
|
182
|
-
dashboard.
|
|
191
|
+
It returns issue or project-document owner details plus `comment` and, for writes, `topic`.
|
|
192
|
+
`quote` requires `artifact`; omit both for a floating issue comment. A reply
|
|
193
|
+
(`reply_to`/`reply_to_ask`) takes no `quote`; it belongs to its parent's anchor. Use `reply_to`
|
|
194
|
+
to continue a comment thread at its root; a reply to a resolved thread reopens it. Use
|
|
195
|
+
`reply_to_ask` to reply directly under a question asked with `dispatch_ask`. The two are
|
|
196
|
+
mutually exclusive. Comments are edited only by their author from the dashboard. A delivered
|
|
197
|
+
`comment.created` event carries the comment `id`; reply to it with
|
|
198
|
+
`dispatch_comment({ reply_to: <id> })`.
|
|
183
199
|
|
|
184
200
|
Propose an exact replacement instead of describing it:
|
|
185
201
|
|
|
186
202
|
```ts
|
|
187
|
-
dispatch_suggest({ issue
|
|
203
|
+
dispatch_suggest({ issue?, project?, artifact, quote, replace_with, body?, occurrence? })
|
|
188
204
|
```
|
|
189
205
|
|
|
190
|
-
It returns
|
|
206
|
+
It returns issue or project-document owner details plus `comment` and its write `topic`. A
|
|
207
|
+
human accepts or rejects a suggestion. On
|
|
191
208
|
`TARGET_AMBIGUOUS`, add zero-based `occurrence`. On `TARGET_NOT_FOUND`, re-read the document
|
|
192
209
|
before retrying. `INVALID_ANCHOR` requires exactly one nonempty anchor `quote` or `mark_id`;
|
|
193
210
|
`ANCHOR_MISSING` means a browser mark was not observed in the live tree, and
|
|
@@ -201,19 +218,21 @@ reject an invalid actor or route.
|
|
|
201
218
|
Attach an image, diagram, or local file with:
|
|
202
219
|
|
|
203
220
|
```ts
|
|
204
|
-
dispatch_artifact({ issue
|
|
221
|
+
dispatch_artifact({ issue?, project?, name, path, summary? })
|
|
205
222
|
```
|
|
206
223
|
|
|
207
224
|
Or, when the text is already in the call, post a Markdown document directly:
|
|
208
225
|
|
|
209
226
|
```ts
|
|
210
|
-
dispatch_artifact({ issue
|
|
227
|
+
dispatch_artifact({ issue?, project?, name: "spec.md", content: "# Design\n..." })
|
|
211
228
|
```
|
|
212
229
|
|
|
213
|
-
Exactly one of `
|
|
214
|
-
`
|
|
215
|
-
|
|
216
|
-
the
|
|
230
|
+
Exactly one of `issue` and `project` is required. A project upload creates an unlinked project
|
|
231
|
+
document; it must not include `artifact`. Exactly one of `path` and `content` is required. The
|
|
232
|
+
inline form sends JSON with `Content-Type: application/json`. It returns issue or
|
|
233
|
+
project-document owner details plus `artifact`, `version`, and its write `topic`. Uploading the
|
|
234
|
+
same `name` creates its next version. Use `content` when the text is already in the call.
|
|
235
|
+
Address an existing artifact by the slug shown in the upload result or by its filename.
|
|
217
236
|
|
|
218
237
|
## Messages
|
|
219
238
|
|
|
@@ -228,23 +247,20 @@ message does not wake anyone. Do not use it for status, a decision, or document
|
|
|
228
247
|
|
|
229
248
|
## What comes back
|
|
230
249
|
|
|
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:
|
|
250
|
+
A write result's `details.topic` subscribes the host to its owner. Issue writes use
|
|
251
|
+
`notifications.dispatch.issue.<KEY>.>`; project-document writes use
|
|
252
|
+
`notifications.dispatch.document.<PROJECT>.<SLUG>.>`. The owner topic carries every Dispatch
|
|
253
|
+
event; `notify` only controls agent wake and routed delivery. After a restart, catch up with:
|
|
239
254
|
|
|
240
255
|
```ts
|
|
241
|
-
dispatch_read({ issue?, ref? })
|
|
256
|
+
dispatch_read({ issue?, project?, artifact?, ref? })
|
|
242
257
|
```
|
|
243
258
|
|
|
244
|
-
With an issue ref, it returns the issue summary, open asks, and recent events with
|
|
245
|
-
`{ issue }`. With
|
|
246
|
-
|
|
247
|
-
|
|
259
|
+
With an issue ref, it returns the issue summary, open asks, references, and recent events with
|
|
260
|
+
`details` `{ issue }`. With a project document owner or ref, it returns a document summary with
|
|
261
|
+
`details` `{ project, document }`. With an ask ref, it returns that ask's question, options,
|
|
262
|
+
state, answer, and its reply thread. With a comment ref, it returns that comment and its quoted
|
|
263
|
+
reply chain. Reads do not subscribe; use `dispatch_doc_read` for document contents.
|
|
248
264
|
|
|
249
265
|
## References
|
|
250
266
|
|
|
@@ -256,6 +272,9 @@ dispatch://KEY/spec
|
|
|
256
272
|
dispatch://KEY/artifact/<slug>[@vN]
|
|
257
273
|
dispatch://KEY/ask/<id>
|
|
258
274
|
dispatch://KEY/comment/<id>
|
|
275
|
+
dispatch://PROJECT/artifact/<slug>[@vN]
|
|
276
|
+
dispatch://PROJECT/artifact/<slug>/ask/<id>
|
|
277
|
+
dispatch://PROJECT/artifact/<slug>/comment/<id>
|
|
259
278
|
```
|
|
260
279
|
|
|
261
280
|
## Before / after
|
|
@@ -313,8 +332,8 @@ dispatch_doc_edit({
|
|
|
313
332
|
ops: [
|
|
314
333
|
{
|
|
315
334
|
op: "replace",
|
|
316
|
-
find: "
|
|
317
|
-
with: "
|
|
335
|
+
find: "Release pending.",
|
|
336
|
+
with: "Release merged and ready for deployment.",
|
|
318
337
|
},
|
|
319
338
|
],
|
|
320
339
|
summary: "Recorded merged release",
|