runline 0.22.0 → 0.23.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/plugins/shiftOcr/src/index.js +13 -0
- package/dist/plugins/shiftPages/src/pages.js +72 -15
- package/dist/plugins/shiftPages/src/shared.js +5 -0
- package/dist/plugins/shiftTranscription/src/transcription.js +9 -5
- package/dist/plugins/shiftWork/src/issues.js +119 -33
- package/package.json +1 -1
|
@@ -114,6 +114,17 @@ export default function shiftOcr(rl) {
|
|
|
114
114
|
description: "Override for URLs whose extension does not reveal the type. " +
|
|
115
115
|
"Inferred from the extension otherwise.",
|
|
116
116
|
})),
|
|
117
|
+
provider: t.Optional(t.String({
|
|
118
|
+
minLength: 1,
|
|
119
|
+
pattern: "\\S",
|
|
120
|
+
description: "Provider ID from ocr.providers. Defaults to the first " +
|
|
121
|
+
"catalog provider.",
|
|
122
|
+
})),
|
|
123
|
+
model: t.Optional(t.String({
|
|
124
|
+
minLength: 1,
|
|
125
|
+
pattern: "\\S",
|
|
126
|
+
description: "Model override. Defaults to the provider's defaultModel.",
|
|
127
|
+
})),
|
|
117
128
|
pages: t.Optional(t.String({
|
|
118
129
|
minLength: 1,
|
|
119
130
|
pattern: "^\\d+(-\\d+)?(,\\d+(-\\d+)?)*$",
|
|
@@ -151,6 +162,8 @@ export default function shiftOcr(rl) {
|
|
|
151
162
|
method: "POST",
|
|
152
163
|
body: JSON.stringify({
|
|
153
164
|
document,
|
|
165
|
+
...(fields.provider ? { provider: fields.provider } : {}),
|
|
166
|
+
...(fields.model ? { model: fields.model } : {}),
|
|
154
167
|
...(fields.pages ? { pages: fields.pages } : {}),
|
|
155
168
|
...(fields.schema
|
|
156
169
|
? {
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import * as t from "typebox";
|
|
2
|
-
import { enumSchema, idSchema, listParams, PAGE_STATUS, PAGE_VISIBILITY, pageRenderUrl, pathSegment, request, STRICT_OBJECT, timestampSchema, withQuery, } from "./shared.js";
|
|
2
|
+
import { enumSchema, idSchema, listParams, PAGE_STATUS, PAGE_TYPE, PAGE_VISIBILITY, pageRenderUrl, pathSegment, request, STRICT_OBJECT, timestampSchema, withQuery, } from "./shared.js";
|
|
3
3
|
const PageSlug = t.String({
|
|
4
4
|
minLength: 1,
|
|
5
5
|
maxLength: 120,
|
|
6
6
|
pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$",
|
|
7
7
|
description: "Lowercase kebab-case page slug",
|
|
8
8
|
});
|
|
9
|
+
const OriginUrl = t.String({
|
|
10
|
+
minLength: 1,
|
|
11
|
+
format: "uri",
|
|
12
|
+
pattern: "^https://",
|
|
13
|
+
description: "HTTPS origin to proxy behind SSO (protected_origin pages only). " +
|
|
14
|
+
"Localhost and private IP hosts are rejected.",
|
|
15
|
+
});
|
|
9
16
|
export function registerPageActions(rl) {
|
|
10
17
|
rl.registerAction("page.list", {
|
|
11
18
|
access: "read",
|
|
@@ -33,9 +40,23 @@ export function registerPageActions(rl) {
|
|
|
33
40
|
return body.page;
|
|
34
41
|
},
|
|
35
42
|
});
|
|
43
|
+
rl.registerAction("page.vexArtifacts", {
|
|
44
|
+
access: "read",
|
|
45
|
+
description: "List the shareable Vex artifacts on a managed deployment, sorted by " +
|
|
46
|
+
"title, each carrying the workspace it belongs to. Pass an artifact's " +
|
|
47
|
+
"ID to page.create with type vex_artifact to publish it. The " +
|
|
48
|
+
"deployment must belong to the API key's organization.",
|
|
49
|
+
inputSchema: t.Object({ deploymentId: idSchema("Managed Vex deployment ID") }, STRICT_OBJECT),
|
|
50
|
+
async execute(input, ctx) {
|
|
51
|
+
const body = await request(ctx, withQuery("/v1/pages/vex-artifacts", listParams(input)));
|
|
52
|
+
return body.artifacts;
|
|
53
|
+
},
|
|
54
|
+
});
|
|
36
55
|
rl.registerAction("page.create", {
|
|
37
56
|
access: "write",
|
|
38
|
-
description: "Create a draft
|
|
57
|
+
description: "Create a draft page and publish it with page.publish. hosted_html " +
|
|
58
|
+
"pages carry their own HTML; vex_artifact pages render a live Vex " +
|
|
59
|
+
"artifact (pass deploymentId and artifactId from page.vexArtifacts).",
|
|
39
60
|
inputSchema: t.Object({
|
|
40
61
|
slug: PageSlug,
|
|
41
62
|
title: t.String({
|
|
@@ -44,14 +65,19 @@ export function registerPageActions(rl) {
|
|
|
44
65
|
pattern: "\\S",
|
|
45
66
|
description: "Page title",
|
|
46
67
|
}),
|
|
68
|
+
type: t.Optional(enumSchema("Page type", PAGE_TYPE)),
|
|
47
69
|
html: t.Optional(t.String({
|
|
48
70
|
maxLength: 1_000_000,
|
|
49
|
-
description: "Hosted HTML content",
|
|
71
|
+
description: "Hosted HTML content (hosted_html pages only)",
|
|
50
72
|
})),
|
|
73
|
+
originUrl: t.Optional(OriginUrl),
|
|
74
|
+
deploymentId: t.Optional(idSchema("Managed Vex deployment ID (vex_artifact pages only)")),
|
|
75
|
+
artifactId: t.Optional(idSchema("Vex artifact ID (vex_artifact pages only)")),
|
|
51
76
|
visibility: t.Optional(enumSchema("Page visibility", PAGE_VISIBILITY)),
|
|
52
77
|
}, STRICT_OBJECT),
|
|
53
78
|
async execute(input, ctx) {
|
|
54
79
|
const fields = input;
|
|
80
|
+
assertPageContent(fields);
|
|
55
81
|
const body = await request(ctx, "/v1/pages", {
|
|
56
82
|
method: "POST",
|
|
57
83
|
body: JSON.stringify({
|
|
@@ -65,7 +91,9 @@ export function registerPageActions(rl) {
|
|
|
65
91
|
});
|
|
66
92
|
rl.registerAction("page.update", {
|
|
67
93
|
access: "write",
|
|
68
|
-
description: "Update a
|
|
94
|
+
description: "Update a page's slug, title, visibility, HTML, or — for vex_artifact " +
|
|
95
|
+
"pages — which deployment and artifact it renders. A page's type is " +
|
|
96
|
+
"fixed at creation.",
|
|
69
97
|
inputSchema: t.Object({
|
|
70
98
|
id: idSchema("Page ID"),
|
|
71
99
|
slug: t.Optional(PageSlug),
|
|
@@ -78,8 +106,11 @@ export function registerPageActions(rl) {
|
|
|
78
106
|
visibility: t.Optional(enumSchema("Page visibility", PAGE_VISIBILITY)),
|
|
79
107
|
html: t.Optional(t.String({
|
|
80
108
|
maxLength: 1_000_000,
|
|
81
|
-
description: "Hosted HTML content",
|
|
109
|
+
description: "Hosted HTML content (hosted_html pages only)",
|
|
82
110
|
})),
|
|
111
|
+
originUrl: t.Optional(OriginUrl),
|
|
112
|
+
deploymentId: t.Optional(idSchema("Managed Vex deployment ID (vex_artifact pages only)")),
|
|
113
|
+
artifactId: t.Optional(idSchema("Vex artifact ID (vex_artifact pages only)")),
|
|
83
114
|
}, STRICT_OBJECT),
|
|
84
115
|
async execute(input, ctx) {
|
|
85
116
|
const { id, ...fields } = input;
|
|
@@ -92,7 +123,8 @@ export function registerPageActions(rl) {
|
|
|
92
123
|
});
|
|
93
124
|
rl.registerAction("page.publish", {
|
|
94
125
|
access: "write",
|
|
95
|
-
description: "Publish a
|
|
126
|
+
description: "Publish a draft page, making it reachable at its render URL by the " +
|
|
127
|
+
"organization (or by invited viewers when visibility is invited).",
|
|
96
128
|
inputSchema: t.Object({ id: idSchema("Page ID") }, STRICT_OBJECT),
|
|
97
129
|
async execute(input, ctx) {
|
|
98
130
|
const { id } = input;
|
|
@@ -113,10 +145,10 @@ export function registerPageActions(rl) {
|
|
|
113
145
|
rl.registerAction("page.shares", {
|
|
114
146
|
access: "read",
|
|
115
147
|
description: "List shares for a Shift Labs page.",
|
|
116
|
-
inputSchema: t.Object({
|
|
148
|
+
inputSchema: t.Object({ id: idSchema("Page ID") }, STRICT_OBJECT),
|
|
117
149
|
async execute(input, ctx) {
|
|
118
|
-
const {
|
|
119
|
-
const body = await request(ctx, `/v1/pages/${pathSegment(
|
|
150
|
+
const { id } = input;
|
|
151
|
+
const body = await request(ctx, `/v1/pages/${pathSegment(id)}/shares`);
|
|
120
152
|
return body.shares;
|
|
121
153
|
},
|
|
122
154
|
});
|
|
@@ -124,7 +156,7 @@ export function registerPageActions(rl) {
|
|
|
124
156
|
access: "write",
|
|
125
157
|
description: "Create a viewer share for a Shift Labs page.",
|
|
126
158
|
inputSchema: t.Object({
|
|
127
|
-
|
|
159
|
+
id: idSchema("Page ID"),
|
|
128
160
|
email: t.String({
|
|
129
161
|
minLength: 1,
|
|
130
162
|
format: "email",
|
|
@@ -133,8 +165,8 @@ export function registerPageActions(rl) {
|
|
|
133
165
|
expiresAt: t.Optional(timestampSchema("Optional ISO-8601 expiration")),
|
|
134
166
|
}, STRICT_OBJECT),
|
|
135
167
|
async execute(input, ctx) {
|
|
136
|
-
const {
|
|
137
|
-
const body = await request(ctx, `/v1/pages/${pathSegment(String(
|
|
168
|
+
const { id, ...fields } = input;
|
|
169
|
+
const body = await request(ctx, `/v1/pages/${pathSegment(String(id))}/shares`, {
|
|
138
170
|
method: "POST",
|
|
139
171
|
body: JSON.stringify(fields),
|
|
140
172
|
});
|
|
@@ -156,13 +188,38 @@ export function registerPageActions(rl) {
|
|
|
156
188
|
rl.registerAction("page.renderUrl", {
|
|
157
189
|
access: "read",
|
|
158
190
|
description: "Return the authenticated render URL for a page.",
|
|
159
|
-
inputSchema: t.Object({
|
|
191
|
+
inputSchema: t.Object({ id: idSchema("Page ID") }, STRICT_OBJECT),
|
|
160
192
|
async execute(input, ctx) {
|
|
161
|
-
const {
|
|
162
|
-
const body = await request(ctx, `/v1/pages/${pathSegment(
|
|
193
|
+
const { id } = input;
|
|
194
|
+
const body = await request(ctx, `/v1/pages/${pathSegment(id)}`);
|
|
163
195
|
return {
|
|
164
196
|
url: pageRenderUrl(body.page.organizationId, body.page.slug),
|
|
165
197
|
};
|
|
166
198
|
},
|
|
167
199
|
});
|
|
168
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* The content rules the API enforces per page type, checked before the
|
|
203
|
+
* round-trip so a caller gets the reason rather than a bare 400. Each
|
|
204
|
+
* type owns exactly one content field.
|
|
205
|
+
*/
|
|
206
|
+
function assertPageContent(fields) {
|
|
207
|
+
const type = fields.type ?? "hosted_html";
|
|
208
|
+
const referencesArtifact = fields.deploymentId !== undefined || fields.artifactId !== undefined;
|
|
209
|
+
if (type !== "vex_artifact" && referencesArtifact) {
|
|
210
|
+
throw new Error("Only vex_artifact pages may reference a deployment or artifact");
|
|
211
|
+
}
|
|
212
|
+
if (type !== "protected_origin" && fields.originUrl !== undefined) {
|
|
213
|
+
throw new Error("Only protected_origin pages may set an originUrl");
|
|
214
|
+
}
|
|
215
|
+
if (type !== "hosted_html" && fields.html !== undefined) {
|
|
216
|
+
throw new Error("Only hosted_html pages may carry html");
|
|
217
|
+
}
|
|
218
|
+
if (type === "vex_artifact" &&
|
|
219
|
+
(fields.deploymentId === undefined || fields.artifactId === undefined)) {
|
|
220
|
+
throw new Error("vex_artifact pages require both deploymentId and artifactId");
|
|
221
|
+
}
|
|
222
|
+
if (type === "protected_origin" && fields.originUrl === undefined) {
|
|
223
|
+
throw new Error("protected_origin pages require an originUrl");
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { baseUrl, pathSegment } from "../../_shared/shiftCloud.js";
|
|
2
2
|
export { enumSchema, idSchema, listParams, pathSegment, request, STRICT_OBJECT, timestampSchema, withQuery, } from "../../_shared/shiftCloud.js";
|
|
3
3
|
export const PAGE_STATUS = ["draft", "published", "archived"];
|
|
4
|
+
export const PAGE_TYPE = [
|
|
5
|
+
"hosted_html",
|
|
6
|
+
"protected_origin",
|
|
7
|
+
"vex_artifact",
|
|
8
|
+
];
|
|
4
9
|
export const PAGE_VISIBILITY = ["org", "invited"];
|
|
5
10
|
export function pageRenderUrl(organizationId, slug) {
|
|
6
11
|
return new URL(`/pages/${pathSegment(organizationId)}/${pathSegment(slug)}`, baseUrl()).toString();
|
|
@@ -28,6 +28,9 @@ export function registerTranscriptionActions(rl) {
|
|
|
28
28
|
pattern: "\\S",
|
|
29
29
|
description: "Label shown in Shift Cloud",
|
|
30
30
|
})),
|
|
31
|
+
metadata: t.Optional(t.Record(t.String(), t.Unknown(), {
|
|
32
|
+
description: "Small caller-defined metadata object stored on the job",
|
|
33
|
+
})),
|
|
31
34
|
waitSeconds: t.Optional(t.Number({
|
|
32
35
|
minimum: 0,
|
|
33
36
|
maximum: 120,
|
|
@@ -55,6 +58,7 @@ export function registerTranscriptionActions(rl) {
|
|
|
55
58
|
language: fields.language,
|
|
56
59
|
diarize: fields.diarize,
|
|
57
60
|
name: fields.name,
|
|
61
|
+
metadata: fields.metadata,
|
|
58
62
|
}),
|
|
59
63
|
});
|
|
60
64
|
if (!fields.waitSeconds)
|
|
@@ -93,10 +97,10 @@ export function registerTranscriptionActions(rl) {
|
|
|
93
97
|
rl.registerAction("transcription.artifact.list", {
|
|
94
98
|
access: "read",
|
|
95
99
|
description: "List the available transcript artifacts for a job.",
|
|
96
|
-
inputSchema: t.Object({
|
|
100
|
+
inputSchema: t.Object({ id: Id }, STRICT_OBJECT),
|
|
97
101
|
async execute(input, ctx) {
|
|
98
|
-
const {
|
|
99
|
-
const body = await request(ctx, `/v1/services/transcription/jobs/${pathSegment(
|
|
102
|
+
const { id } = input;
|
|
103
|
+
const body = await request(ctx, `/v1/services/transcription/jobs/${pathSegment(id)}/artifacts`);
|
|
100
104
|
return body.artifacts;
|
|
101
105
|
},
|
|
102
106
|
});
|
|
@@ -106,14 +110,14 @@ export function registerTranscriptionActions(rl) {
|
|
|
106
110
|
"return subtitles; json returns the full timestamped document. Set " +
|
|
107
111
|
"downloadOnly=true to return the short-lived signed download grant.",
|
|
108
112
|
inputSchema: t.Object({
|
|
109
|
-
|
|
113
|
+
id: Id,
|
|
110
114
|
format: t.Optional(enumSchema("Transcript format", [...TRANSCRIPT_FORMAT])),
|
|
111
115
|
downloadOnly: t.Optional(t.Boolean({ description: "Return a signed download grant only" })),
|
|
112
116
|
}, STRICT_OBJECT),
|
|
113
117
|
async execute(input, ctx) {
|
|
114
118
|
const fields = input;
|
|
115
119
|
const format = fields.format ?? "txt";
|
|
116
|
-
const grant = await request(ctx, `/v1/services/transcription/jobs/${pathSegment(fields.
|
|
120
|
+
const grant = await request(ctx, `/v1/services/transcription/jobs/${pathSegment(fields.id)}/artifacts/${pathSegment(format)}/download`, { method: "POST" });
|
|
117
121
|
if (fields.downloadOnly ||
|
|
118
122
|
grant.artifact.sizeBytes > MAX_TRANSCRIPT_BYTES) {
|
|
119
123
|
return { format, ...grant };
|
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
import * as t from "typebox";
|
|
2
2
|
import { cursorSchema, enumSchema, ISSUE_PRIORITY, ISSUE_SOURCE, ISSUE_STATUS, idSchema, listParams, pathSegment, request, STRICT_OBJECT, STRICT_UPDATE_OBJECT, timestampSchema, withQuery, } from "./shared.js";
|
|
3
|
+
const issueFields = {
|
|
4
|
+
title: t.String({
|
|
5
|
+
minLength: 1,
|
|
6
|
+
maxLength: 160,
|
|
7
|
+
pattern: "\\S",
|
|
8
|
+
description: "Issue title",
|
|
9
|
+
}),
|
|
10
|
+
description: t.Optional(t.String({ maxLength: 20_000, description: "Issue description" })),
|
|
11
|
+
projectId: t.Optional(idSchema("Project ID")),
|
|
12
|
+
parentIssueId: t.Optional(idSchema("Parent Issue ID")),
|
|
13
|
+
sortOrder: t.Optional(t.Integer({ minimum: 0 })),
|
|
14
|
+
status: t.Optional(enumSchema("Issue status", ISSUE_STATUS)),
|
|
15
|
+
priority: t.Optional(enumSchema("Issue priority", ISSUE_PRIORITY)),
|
|
16
|
+
assigneeUserId: t.Optional(idSchema("Assignee user ID")),
|
|
17
|
+
startAt: t.Optional(timestampSchema("ISO-8601 start timestamp")),
|
|
18
|
+
dueAt: t.Optional(timestampSchema("ISO-8601 due timestamp")),
|
|
19
|
+
deploymentId: t.Optional(idSchema("Deployment ID")),
|
|
20
|
+
workspaceId: t.Optional(idSchema("Workspace ID")),
|
|
21
|
+
sessionId: t.Optional(idSchema("Session ID")),
|
|
22
|
+
traceId: t.Optional(idSchema("Trace ID")),
|
|
23
|
+
fingerprint: t.Optional(t.String({ minLength: 1, maxLength: 512, pattern: "\\S" })),
|
|
24
|
+
labels: t.Optional(t.Array(t.String({ minLength: 1, maxLength: 64, pattern: "\\S" }))),
|
|
25
|
+
metadata: t.Optional(t.Record(t.String(), t.Unknown(), { description: "Issue metadata" })),
|
|
26
|
+
source: t.Optional(enumSchema("Issue source", ISSUE_SOURCE)),
|
|
27
|
+
};
|
|
3
28
|
const issueListFields = {
|
|
4
29
|
status: t.Optional(enumSchema("Issue status", ISSUE_STATUS)),
|
|
5
30
|
projectId: t.Optional(idSchema("Project ID")),
|
|
@@ -14,6 +39,15 @@ const issueListFields = {
|
|
|
14
39
|
description: "Max results, default 50",
|
|
15
40
|
})),
|
|
16
41
|
};
|
|
42
|
+
const eventListFields = {
|
|
43
|
+
id: idSchema("Issue ID"),
|
|
44
|
+
cursor: t.Optional(cursorSchema()),
|
|
45
|
+
limit: t.Optional(t.Integer({
|
|
46
|
+
minimum: 1,
|
|
47
|
+
maximum: 100,
|
|
48
|
+
description: "Max results, default 50",
|
|
49
|
+
})),
|
|
50
|
+
};
|
|
17
51
|
export function registerIssueActions(rl) {
|
|
18
52
|
rl.registerAction("issue.list", {
|
|
19
53
|
access: "read",
|
|
@@ -45,38 +79,10 @@ export function registerIssueActions(rl) {
|
|
|
45
79
|
rl.registerAction("issue.create", {
|
|
46
80
|
access: "write",
|
|
47
81
|
description: "Create an Issue. Leave projectId empty for inbox work; parentIssueId requires a Project.",
|
|
48
|
-
inputSchema: t.Object(
|
|
49
|
-
title: t.String({
|
|
50
|
-
minLength: 1,
|
|
51
|
-
maxLength: 160,
|
|
52
|
-
pattern: "\\S",
|
|
53
|
-
description: "Issue title",
|
|
54
|
-
}),
|
|
55
|
-
description: t.Optional(t.String({ maxLength: 20_000, description: "Issue description" })),
|
|
56
|
-
projectId: t.Optional(idSchema("Project ID")),
|
|
57
|
-
parentIssueId: t.Optional(idSchema("Parent Issue ID")),
|
|
58
|
-
sortOrder: t.Optional(t.Integer({ minimum: 0 })),
|
|
59
|
-
status: t.Optional(enumSchema("Issue status", ISSUE_STATUS)),
|
|
60
|
-
priority: t.Optional(enumSchema("Issue priority", ISSUE_PRIORITY)),
|
|
61
|
-
source: t.Optional(enumSchema("Issue source", ISSUE_SOURCE)),
|
|
62
|
-
assigneeUserId: t.Optional(idSchema("Assignee user ID")),
|
|
63
|
-
startAt: t.Optional(timestampSchema("ISO-8601 start timestamp")),
|
|
64
|
-
dueAt: t.Optional(timestampSchema("ISO-8601 due timestamp")),
|
|
65
|
-
deploymentId: t.Optional(idSchema("Deployment ID")),
|
|
66
|
-
workspaceId: t.Optional(idSchema("Workspace ID")),
|
|
67
|
-
sessionId: t.Optional(idSchema("Session ID")),
|
|
68
|
-
traceId: t.Optional(idSchema("Trace ID")),
|
|
69
|
-
fingerprint: t.Optional(t.String({ minLength: 1, maxLength: 512, pattern: "\\S" })),
|
|
70
|
-
labels: t.Optional(t.Array(t.String({ minLength: 1, maxLength: 64, pattern: "\\S" }))),
|
|
71
|
-
metadata: t.Optional(t.Record(t.String(), t.Unknown(), { description: "Issue metadata" })),
|
|
72
|
-
}, STRICT_OBJECT),
|
|
82
|
+
inputSchema: t.Object(issueFields, STRICT_OBJECT),
|
|
73
83
|
async execute(input, ctx) {
|
|
74
84
|
const fields = input;
|
|
75
|
-
|
|
76
|
-
fields.projectId === undefined) {
|
|
77
|
-
throw new Error("projectId is required when parentIssueId is set");
|
|
78
|
-
}
|
|
79
|
-
assertDateOrder(fields.startAt, fields.dueAt, "Issue dueAt must not precede startAt");
|
|
85
|
+
assertIssueShape(fields);
|
|
80
86
|
const body = await request(ctx, "/v1/issues", {
|
|
81
87
|
method: "POST",
|
|
82
88
|
body: JSON.stringify(fields),
|
|
@@ -84,6 +90,19 @@ export function registerIssueActions(rl) {
|
|
|
84
90
|
return body.issue;
|
|
85
91
|
},
|
|
86
92
|
});
|
|
93
|
+
rl.registerAction("issue.report", {
|
|
94
|
+
access: "write",
|
|
95
|
+
description: "Report an Issue the way an agent should: source defaults to agent, " +
|
|
96
|
+
"and a repeated fingerprint folds into the existing Issue as a " +
|
|
97
|
+
"reported_again event instead of creating a duplicate.",
|
|
98
|
+
inputSchema: t.Object(issueFields, STRICT_OBJECT),
|
|
99
|
+
async execute(input, ctx) {
|
|
100
|
+
const fields = input;
|
|
101
|
+
assertIssueShape(fields);
|
|
102
|
+
const body = await request(ctx, "/v1/issues/report", { method: "POST", body: JSON.stringify(fields) });
|
|
103
|
+
return body.issue;
|
|
104
|
+
},
|
|
105
|
+
});
|
|
87
106
|
rl.registerAction("issue.update", {
|
|
88
107
|
access: "write",
|
|
89
108
|
description: "Update an Issue, including Project assignment, one-level parentage, ordering, status, dates, or assignee.",
|
|
@@ -127,6 +146,66 @@ export function registerIssueActions(rl) {
|
|
|
127
146
|
return response.event;
|
|
128
147
|
},
|
|
129
148
|
});
|
|
149
|
+
rl.registerAction("issue.resolve", {
|
|
150
|
+
access: "write",
|
|
151
|
+
description: "Resolve an Issue, optionally with a closing comment recorded on the " +
|
|
152
|
+
"event timeline.",
|
|
153
|
+
inputSchema: t.Object({
|
|
154
|
+
id: idSchema("Issue ID"),
|
|
155
|
+
comment: t.Optional(t.String({
|
|
156
|
+
minLength: 1,
|
|
157
|
+
maxLength: 20_000,
|
|
158
|
+
pattern: "\\S",
|
|
159
|
+
description: "Resolution comment",
|
|
160
|
+
})),
|
|
161
|
+
}, STRICT_OBJECT),
|
|
162
|
+
async execute(input, ctx) {
|
|
163
|
+
const { id, ...fields } = input;
|
|
164
|
+
const body = await request(ctx, `/v1/issues/${pathSegment(id)}/resolve`, { method: "POST", body: JSON.stringify(fields) });
|
|
165
|
+
return body.issue;
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
rl.registerAction("issue.reopen", {
|
|
169
|
+
access: "write",
|
|
170
|
+
description: "Reopen a resolved or closed Issue.",
|
|
171
|
+
inputSchema: t.Object({ id: idSchema("Issue ID") }, STRICT_OBJECT),
|
|
172
|
+
async execute(input, ctx) {
|
|
173
|
+
const { id } = input;
|
|
174
|
+
const body = await request(ctx, `/v1/issues/${pathSegment(id)}/reopen`, { method: "POST" });
|
|
175
|
+
return body.issue;
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
rl.registerAction("issue.close", {
|
|
179
|
+
access: "write",
|
|
180
|
+
description: "Close an Issue without resolving it — the outcome for work that is " +
|
|
181
|
+
"abandoned rather than finished.",
|
|
182
|
+
inputSchema: t.Object({ id: idSchema("Issue ID") }, STRICT_OBJECT),
|
|
183
|
+
async execute(input, ctx) {
|
|
184
|
+
const { id } = input;
|
|
185
|
+
const body = await request(ctx, `/v1/issues/${pathSegment(id)}/close`, { method: "POST" });
|
|
186
|
+
return body.issue;
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
rl.registerAction("issue.event.list", {
|
|
190
|
+
access: "read",
|
|
191
|
+
description: "List the first page of an Issue's event timeline: status changes, " +
|
|
192
|
+
"comments, assignment and dependency history, newest first.",
|
|
193
|
+
inputSchema: t.Object(eventListFields, STRICT_OBJECT),
|
|
194
|
+
async execute(input, ctx) {
|
|
195
|
+
const { id, ...pagination } = input;
|
|
196
|
+
const body = await request(ctx, withQuery(`/v1/issues/${pathSegment(id)}/events`, listParams(pagination)));
|
|
197
|
+
return body.events;
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
rl.registerAction("issue.event.listPage", {
|
|
201
|
+
access: "read",
|
|
202
|
+
description: "List a cursor-paginated page of an Issue's event timeline.",
|
|
203
|
+
inputSchema: t.Object(eventListFields, STRICT_OBJECT),
|
|
204
|
+
async execute(input, ctx) {
|
|
205
|
+
const { id, ...pagination } = input;
|
|
206
|
+
return request(ctx, withQuery(`/v1/issues/${pathSegment(id)}/events`, listParams(pagination)));
|
|
207
|
+
},
|
|
208
|
+
});
|
|
130
209
|
rl.registerAction("issue.dependency.list", {
|
|
131
210
|
access: "read",
|
|
132
211
|
description: "List the first page of blocking and blocked-by links.",
|
|
@@ -173,16 +252,23 @@ export function registerIssueActions(rl) {
|
|
|
173
252
|
access: "write",
|
|
174
253
|
description: "Remove an Issue dependency link.",
|
|
175
254
|
inputSchema: t.Object({
|
|
176
|
-
|
|
255
|
+
id: idSchema("Issue ID"),
|
|
177
256
|
dependencyId: idSchema("Dependency ID"),
|
|
178
257
|
}, STRICT_OBJECT),
|
|
179
258
|
async execute(input, ctx) {
|
|
180
|
-
const {
|
|
181
|
-
await request(ctx, `/v1/issues/${pathSegment(
|
|
259
|
+
const { id, dependencyId } = input;
|
|
260
|
+
await request(ctx, `/v1/issues/${pathSegment(id)}/dependencies/${pathSegment(dependencyId)}`, { method: "DELETE" });
|
|
182
261
|
return { removed: true };
|
|
183
262
|
},
|
|
184
263
|
});
|
|
185
264
|
}
|
|
265
|
+
/** Shape rules the API enforces server-side, caught before the round-trip. */
|
|
266
|
+
function assertIssueShape(fields) {
|
|
267
|
+
if (fields.parentIssueId !== undefined && fields.projectId === undefined) {
|
|
268
|
+
throw new Error("projectId is required when parentIssueId is set");
|
|
269
|
+
}
|
|
270
|
+
assertDateOrder(fields.startAt, fields.dueAt, "Issue dueAt must not precede startAt");
|
|
271
|
+
}
|
|
186
272
|
function assertDateOrder(start, end, message) {
|
|
187
273
|
if (typeof start === "string" && typeof end === "string" && start > end) {
|
|
188
274
|
throw new Error(message);
|