tines 0.0.289 → 0.0.291
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/dist/index.js +359 -31
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,6 +40,9 @@ tines issues create <project> --title "…" -d @description.md
|
|
|
40
40
|
tines issues create <project> --title "Linked" --blocked-by Other/12 --blocks Other/14 --duplicate-of Other/9
|
|
41
41
|
tines issues show <project>/<number>
|
|
42
42
|
tines issues move <project>/<number> <action> # a workflow transition
|
|
43
|
+
tines issues hold <project>/<number> # stop new admission, keep permission
|
|
44
|
+
tines issues release <project>/<number> # resume eligibility if permission remains on
|
|
45
|
+
tines issues cancel-run <project>/<number> <run-id> # request bounded cancellation
|
|
43
46
|
tines issues transfer <project>/<number> --project <dest> # move to another project (keeps ID, record and old refs)
|
|
44
47
|
tines issues transfer <ref> --project <dest> --dry-run # review only: no number allocated, nothing written
|
|
45
48
|
tines issues transfer <ref> --project <dest> --inspect 0 # print any reviewed guidance item, including retained, in full
|
|
@@ -77,6 +80,20 @@ or narrow the list's filters.
|
|
|
77
80
|
When a recurrence is also supplied, the relationships apply only to the first issue; later
|
|
78
81
|
scheduled instances start without copied relationships.
|
|
79
82
|
|
|
83
|
+
For a shared project's recurring schedule, `schedules list` and `schedules show` report
|
|
84
|
+
your saved future-instance permission and its epoch. They are read receipts: the CLI and
|
|
85
|
+
API keys cannot turn personal permission on or off. Open the schedule in the browser to
|
|
86
|
+
choose. The first issue's permission is independent of the future schedule choice; future
|
|
87
|
+
permission starts off.
|
|
88
|
+
|
|
89
|
+
On a shared issue, `issues show` displays the safe member view. An accepted
|
|
90
|
+
member can use `issues comment`, repair their own human or run comment with
|
|
91
|
+
`comment-edit`/`comment-delete`, and use `issues move` from an awaiting-human
|
|
92
|
+
state. `issues move` submits the current exact decision witness once and does
|
|
93
|
+
not retry after a conflict. Keys cannot turn issue or future-schedule personal
|
|
94
|
+
permission on or off; open the issue or shared project in the browser. A saved
|
|
95
|
+
member choice does not enable member execution in this release.
|
|
96
|
+
|
|
80
97
|
## Workflow package files
|
|
81
98
|
|
|
82
99
|
Export a workflow and its inheritance/context closure as canonical JSON, validate an edited
|
package/dist/index.js
CHANGED
|
@@ -3747,12 +3747,15 @@ function action(type) {
|
|
|
3747
3747
|
function otherRef(p) {
|
|
3748
3748
|
return {
|
|
3749
3749
|
kind: "other-ref",
|
|
3750
|
+
...typeof p.other_project_id === "string" ? { project_id: p.other_project_id } : {},
|
|
3750
3751
|
project_name: str(p.other_project_name),
|
|
3751
3752
|
number: Number(p.other_number)
|
|
3752
3753
|
};
|
|
3753
3754
|
}
|
|
3754
3755
|
function linkSegments(ev, p) {
|
|
3755
3756
|
const lead = ev.type === "issue.link_added" ? "marked" : "unmarked";
|
|
3757
|
+
if (!Number.isFinite(Number(p.other_number)))
|
|
3758
|
+
return [text("changed an issue link on"), selfRef()];
|
|
3756
3759
|
const duplicate = p.kind === "duplicate_of";
|
|
3757
3760
|
if (duplicate && p.role === "target") {
|
|
3758
3761
|
return [text(lead), otherRef(p), text("as a duplicate of"), selfRef()];
|
|
@@ -3795,6 +3798,14 @@ var DESCRIBERS = {
|
|
|
3795
3798
|
});
|
|
3796
3799
|
return segs;
|
|
3797
3800
|
},
|
|
3801
|
+
"issue.personal_permission_changed": (_ev, p) => [
|
|
3802
|
+
text(`${p.value === "on" ? "allowed" : "stopped"} their agents on`),
|
|
3803
|
+
selfRef()
|
|
3804
|
+
],
|
|
3805
|
+
"issue.agent_hold_changed": (_ev, p) => [
|
|
3806
|
+
text(p.held ? "held agents on" : "released the agent hold on"),
|
|
3807
|
+
selfRef()
|
|
3808
|
+
],
|
|
3798
3809
|
"issue.commented": () => [text("commented on"), selfRef()],
|
|
3799
3810
|
"issue.comment_edited": () => [text("edited a comment on"), selfRef()],
|
|
3800
3811
|
"issue.comment_deleted": () => [text("deleted a comment on"), selfRef()],
|
|
@@ -3816,6 +3827,10 @@ var DESCRIBERS = {
|
|
|
3816
3827
|
"project.deleted": (ev, p) => projectSegments(ev, p),
|
|
3817
3828
|
"project.archived": (ev, p) => projectSegments(ev, p),
|
|
3818
3829
|
"project.unarchived": (ev, p) => projectSegments(ev, p),
|
|
3830
|
+
"project.sharing_started": () => [text("started sharing this project")],
|
|
3831
|
+
"project.invitation_created": () => [text("created a project invitation")],
|
|
3832
|
+
"project.member_joined": () => [text("joined this project")],
|
|
3833
|
+
"project.member_removed": () => [text("changed membership in this project")],
|
|
3819
3834
|
"workflow.created": (ev, p) => [text(`${action(ev.type)} workflow`), name(p.name)],
|
|
3820
3835
|
"workflow.updated": (ev, p) => [text(`${action(ev.type)} workflow`), name(p.name)],
|
|
3821
3836
|
"workflow.deleted": (ev, p) => [text(`${action(ev.type)} workflow`), name(p.name)],
|
|
@@ -3826,6 +3841,11 @@ var DESCRIBERS = {
|
|
|
3826
3841
|
],
|
|
3827
3842
|
"api_key.revoked": (_ev, p) => [text("revoked API key"), name(p.name)],
|
|
3828
3843
|
"scheduled_task.created": (ev, p) => [text(`${action(ev.type)} schedule`), name(p.name)],
|
|
3844
|
+
"scheduled_task.personal_permission_changed": (_ev, p) => [
|
|
3845
|
+
text(
|
|
3846
|
+
`${p.value === "on" ? "allowed" : "stopped"} their agents on future issues from a schedule`
|
|
3847
|
+
)
|
|
3848
|
+
],
|
|
3829
3849
|
"scheduled_task.updated": (ev, p) => [text(`${action(ev.type)} schedule`), name(p.name)],
|
|
3830
3850
|
"scheduled_task.deleted": (ev, p) => [text(`${action(ev.type)} schedule`), name(p.name)],
|
|
3831
3851
|
"scheduled_task.skipped": (_ev, p) => {
|
|
@@ -4156,12 +4176,34 @@ function createApiClient(options) {
|
|
|
4156
4176
|
createProject: (body) => request("POST", "/api/v1/projects", body),
|
|
4157
4177
|
listStarters: () => get("/api/v1/projects/starters"),
|
|
4158
4178
|
getProject: (id2) => get(`/api/v1/projects/${id2}`),
|
|
4179
|
+
getProjectPeople: (id2) => get(`/api/v1/projects/${id2}/people`),
|
|
4180
|
+
listProjectInvitations: (id2) => get(`/api/v1/projects/${id2}/invitations`),
|
|
4181
|
+
inviteProjectPerson: (id2, body) => request("POST", `/api/v1/projects/${id2}/invitations`, body),
|
|
4182
|
+
resendProjectInvitation: (id2, inviteId, expectedGeneration) => request(
|
|
4183
|
+
"POST",
|
|
4184
|
+
`/api/v1/projects/${id2}/invitations/${inviteId}/resend`,
|
|
4185
|
+
{ expected_generation: expectedGeneration }
|
|
4186
|
+
),
|
|
4187
|
+
cancelProjectInvitation: (id2, inviteId, expectedGeneration) => request("DELETE", `/api/v1/projects/${id2}/invitations/${inviteId}`, {
|
|
4188
|
+
expected_generation: expectedGeneration
|
|
4189
|
+
}),
|
|
4190
|
+
removeProjectMember: (id2, userId, expectedRevision) => request(
|
|
4191
|
+
"DELETE",
|
|
4192
|
+
`/api/v1/projects/${id2}/members/${userId}`,
|
|
4193
|
+
{ expected_revision: expectedRevision }
|
|
4194
|
+
),
|
|
4195
|
+
leaveProject: (id2, expectedRevision) => request(
|
|
4196
|
+
"POST",
|
|
4197
|
+
`/api/v1/projects/${id2}/leave`,
|
|
4198
|
+
{ expected_revision: expectedRevision }
|
|
4199
|
+
),
|
|
4159
4200
|
updateProject: (id2, body) => request("PATCH", `/api/v1/projects/${id2}`, body),
|
|
4160
4201
|
deleteProject: (id2, body) => request("DELETE", `/api/v1/projects/${id2}`, body),
|
|
4161
4202
|
archiveProject: (id2) => request("POST", `/api/v1/projects/${id2}/archive`),
|
|
4162
4203
|
unarchiveProject: (id2) => request("POST", `/api/v1/projects/${id2}/unarchive`),
|
|
4163
4204
|
// Workflows
|
|
4164
|
-
|
|
4205
|
+
/** `project` narrows to the workflows an issue in that project can use. */
|
|
4206
|
+
listWorkflows: (params = {}) => get(`/api/v1/workflows${query(params)}`),
|
|
4165
4207
|
createWorkflow: (body) => request("POST", "/api/v1/workflows", body),
|
|
4166
4208
|
getWorkflow: (id2) => get(`/api/v1/workflows/${id2}`),
|
|
4167
4209
|
updateWorkflow: (id2, body) => request("PATCH", `/api/v1/workflows/${id2}`, body),
|
|
@@ -4197,6 +4239,10 @@ function createApiClient(options) {
|
|
|
4197
4239
|
},
|
|
4198
4240
|
getIssue: (id2) => get(`/api/v1/issues/${id2}`),
|
|
4199
4241
|
getIssueByNumber: (projectId, number) => get(`/api/v1/projects/${projectId}/issues/${number}`),
|
|
4242
|
+
getIssueConsent: (id2) => get(`/api/v1/issues/${id2}/my-consent`),
|
|
4243
|
+
setIssueConsent: (id2, body) => request("PUT", `/api/v1/issues/${id2}/my-consent`, body),
|
|
4244
|
+
setIssueHold: (id2, body) => request("PUT", `/api/v1/issues/${id2}/agent-hold`, body),
|
|
4245
|
+
cancelIssueRun: (issueId, runId) => request("POST", `/api/v1/issues/${issueId}/runs/${runId}/cancel`, {}),
|
|
4200
4246
|
updateIssue: (id2, body) => request("PATCH", `/api/v1/issues/${id2}`, body),
|
|
4201
4247
|
transitionIssue: (id2, body) => request("POST", `/api/v1/issues/${id2}/transition`, body),
|
|
4202
4248
|
/** Un-park: clears needs_attention, resets the attempt count. */
|
|
@@ -4215,6 +4261,8 @@ function createApiClient(options) {
|
|
|
4215
4261
|
listSchedules: (filters = {}) => get(`/api/v1/schedules${query(filters)}`),
|
|
4216
4262
|
listProjectSchedules: (projectId, page = {}) => get(`/api/v1/projects/${projectId}/schedules${query(page)}`),
|
|
4217
4263
|
getSchedule: (id2) => get(`/api/v1/schedules/${id2}`),
|
|
4264
|
+
getScheduleConsent: (id2) => get(`/api/v1/schedules/${id2}/my-consent`),
|
|
4265
|
+
setScheduleConsent: (id2, body) => request("PUT", `/api/v1/schedules/${id2}/my-consent`, body),
|
|
4218
4266
|
updateSchedule: (id2, body) => request("PATCH", `/api/v1/schedules/${id2}`, body),
|
|
4219
4267
|
deleteSchedule: (id2) => request("DELETE", `/api/v1/schedules/${id2}`),
|
|
4220
4268
|
/** Run now: creates an instance immediately (gate-respecting; 422 when blocked). */
|
|
@@ -4354,6 +4402,7 @@ function createApiClient(options) {
|
|
|
4354
4402
|
// Supervisor settings
|
|
4355
4403
|
getPreferences: () => get("/api/v1/preferences"),
|
|
4356
4404
|
updatePreferences: (body) => request("PATCH", "/api/v1/preferences", body),
|
|
4405
|
+
acknowledgePermissionNotice: (version) => request("PUT", "/api/v1/preferences/disclosure", { version }),
|
|
4357
4406
|
getSupervisorSettings: () => get("/api/v1/supervisor/settings"),
|
|
4358
4407
|
getSupervisorQueue: (q = {}) => get(`/api/v1/supervisor/queue${query(q)}`),
|
|
4359
4408
|
getSupervisorStats: (q = {}) => get(`/api/v1/supervisor/stats${query(q)}`),
|
|
@@ -5810,6 +5859,12 @@ function supportedEfforts(capabilities, model) {
|
|
|
5810
5859
|
return capabilities.models.find((entry) => entry.model === model)?.efforts ?? null;
|
|
5811
5860
|
}
|
|
5812
5861
|
|
|
5862
|
+
// ../shared/src/personal-permission.ts
|
|
5863
|
+
function personalPermissionLabel(role, value) {
|
|
5864
|
+
if (role === "owner" && value === "unset") return "on (owner default)";
|
|
5865
|
+
return value;
|
|
5866
|
+
}
|
|
5867
|
+
|
|
5813
5868
|
// src/format.ts
|
|
5814
5869
|
function timestamp(ms) {
|
|
5815
5870
|
return new Date(ms).toISOString().replace("T", " ").slice(0, 19);
|
|
@@ -6361,13 +6416,18 @@ async function resolveProject(api, ref) {
|
|
|
6361
6416
|
const byName = items.filter((p) => p.name === ref);
|
|
6362
6417
|
if (byName.length === 1) return byName[0];
|
|
6363
6418
|
if (byName.length > 1) {
|
|
6364
|
-
die(
|
|
6419
|
+
die(
|
|
6420
|
+
`project name "${ref}" is ambiguous; use an id: ${byName.map((p) => `${p.id}${p.owner ? ` (${p.owner.name})` : ""}`).join(", ")}`
|
|
6421
|
+
);
|
|
6365
6422
|
}
|
|
6366
6423
|
const have = items.map((p) => p.archived_at ? `${p.name} (archived)` : p.name).join(", ");
|
|
6367
6424
|
die(`no project named "${ref}" (have: ${have || "none"})`);
|
|
6368
6425
|
}
|
|
6369
|
-
async function resolveWorkflow(api, ref) {
|
|
6370
|
-
return pickWorkflow(
|
|
6426
|
+
async function resolveWorkflow(api, ref, projectId) {
|
|
6427
|
+
return pickWorkflow(
|
|
6428
|
+
(await api.listWorkflows(projectId ? { project: projectId } : {})).items,
|
|
6429
|
+
ref
|
|
6430
|
+
);
|
|
6371
6431
|
}
|
|
6372
6432
|
function pickWorkflow(items, ref) {
|
|
6373
6433
|
const found = items.find((w) => w.id === ref) ?? (items.filter((w) => w.name === ref).length === 1 ? items.find((w) => w.name === ref) : void 0);
|
|
@@ -7558,6 +7618,44 @@ function printIssueLinks(links) {
|
|
|
7558
7618
|
}
|
|
7559
7619
|
}
|
|
7560
7620
|
function printIssueDetail(issue) {
|
|
7621
|
+
const shared = issue;
|
|
7622
|
+
if (shared.mode === "member") {
|
|
7623
|
+
console.log(`${shared.project?.name}/${shared.number} ${shared.title}`);
|
|
7624
|
+
console.log(
|
|
7625
|
+
`state: ${shared.state?.name} (${shared.state?.category}) workflow: ${shared.workflow?.name}`
|
|
7626
|
+
);
|
|
7627
|
+
console.log(`owner: ${shared.project?.owner.name} id: ${shared.id}`);
|
|
7628
|
+
if (shared.description) console.log(`
|
|
7629
|
+
${shared.description}`);
|
|
7630
|
+
if (shared.artifacts?.length) {
|
|
7631
|
+
console.log("\nartifacts:");
|
|
7632
|
+
table(shared.artifacts.map((a) => [a.name, `v${a.current_version.version}`]));
|
|
7633
|
+
}
|
|
7634
|
+
if (shared.roster?.length) {
|
|
7635
|
+
console.log("\npeople and permission:");
|
|
7636
|
+
table(
|
|
7637
|
+
shared.roster.map((person) => [
|
|
7638
|
+
person.user.name,
|
|
7639
|
+
person.role,
|
|
7640
|
+
personalPermissionLabel(person.role, person.value)
|
|
7641
|
+
])
|
|
7642
|
+
);
|
|
7643
|
+
}
|
|
7644
|
+
console.log(`your issue permission: ${shared.my_choice?.value ?? "unset"} (browser control)`);
|
|
7645
|
+
console.log(`latest run: ${shared.latest_run?.status ?? "none"}`);
|
|
7646
|
+
if (shared.comments?.length) {
|
|
7647
|
+
console.log("\ncomments:");
|
|
7648
|
+
for (const comment of shared.comments)
|
|
7649
|
+
console.log(
|
|
7650
|
+
`${comment.id} \xB7 ${comment.author.name}${comment.author.run ? ` via ${comment.author.run.name}` : ""}${comment.editor ? ` \xB7 edited by ${comment.editor.name}` : ""}
|
|
7651
|
+
${comment.body}`
|
|
7652
|
+
);
|
|
7653
|
+
}
|
|
7654
|
+
console.log(
|
|
7655
|
+
"\nMembers work on shared issues as the owner does, except adding or removing people. Personal permission is chosen in the browser; member execution is unavailable."
|
|
7656
|
+
);
|
|
7657
|
+
return;
|
|
7658
|
+
}
|
|
7561
7659
|
console.log(`${issue.project_name}/#${issue.number} ${issue.title}`);
|
|
7562
7660
|
const dup = issue.duplicate_of;
|
|
7563
7661
|
console.log(
|
|
@@ -7577,6 +7675,10 @@ function printIssueDetail(issue) {
|
|
|
7577
7675
|
console.log(`labels: ${issue.labels.map((l) => l.name).join(", ")}`);
|
|
7578
7676
|
}
|
|
7579
7677
|
console.log(`id: ${issue.id}`);
|
|
7678
|
+
if (issue.schedule_origin)
|
|
7679
|
+
console.log(
|
|
7680
|
+
`created by schedule: ${issue.schedule_origin.schedule_name} [${issue.schedule_origin.schedule_id}] permission epoch: ${issue.schedule_origin.permission_epoch} (historical source; current issue permission is separate)`
|
|
7681
|
+
);
|
|
7580
7682
|
printIssueLinks(issue.links);
|
|
7581
7683
|
if (issue.description) {
|
|
7582
7684
|
console.log(`
|
|
@@ -7732,13 +7834,13 @@ function register3(program3) {
|
|
|
7732
7834
|
`${i.project_name}/${i.number}`,
|
|
7733
7835
|
i.title,
|
|
7734
7836
|
i.effective_state.name,
|
|
7735
|
-
ageLabel2(new Date(i.state_entered_at).toISOString()),
|
|
7837
|
+
ageLabel2(new Date(i.state_entered_at ?? i.created_at).toISOString()),
|
|
7736
7838
|
arrivedViaLabel(i.arrived_via),
|
|
7737
7839
|
roundSummaryLabel(i.round_summary ?? null),
|
|
7738
7840
|
[
|
|
7739
|
-
i.open_blockers
|
|
7841
|
+
(i.open_blockers?.length ?? 0) > 0 ? "blocked" : "",
|
|
7740
7842
|
i.duplicate_of ? "dup" : "",
|
|
7741
|
-
...i.labels.map((l) => `[${l.name}]`)
|
|
7843
|
+
...(i.labels ?? []).map((l) => `[${l.name}]`)
|
|
7742
7844
|
].filter(Boolean).join(" ")
|
|
7743
7845
|
])
|
|
7744
7846
|
]);
|
|
@@ -7753,11 +7855,11 @@ function register3(program3) {
|
|
|
7753
7855
|
// (and the filters above) go by the effective state.
|
|
7754
7856
|
i.effective_state.name,
|
|
7755
7857
|
i.effective_state.category,
|
|
7756
|
-
timestamp(i.last_activity_at),
|
|
7858
|
+
timestamp(i.last_activity_at ?? i.updated_at),
|
|
7757
7859
|
[
|
|
7758
|
-
i.open_blockers
|
|
7860
|
+
(i.open_blockers?.length ?? 0) > 0 ? "blocked" : "",
|
|
7759
7861
|
i.duplicate_of ? "dup" : "",
|
|
7760
|
-
...i.labels.map((l) => `[${l.name}]`)
|
|
7862
|
+
...(i.labels ?? []).map((l) => `[${l.name}]`)
|
|
7761
7863
|
].filter(Boolean).join(" ")
|
|
7762
7864
|
])
|
|
7763
7865
|
]);
|
|
@@ -7788,7 +7890,7 @@ function register3(program3) {
|
|
|
7788
7890
|
const description = opts.description !== void 0 ? readBodyValue(opts.description) : void 0;
|
|
7789
7891
|
const api = client(opts);
|
|
7790
7892
|
const project = await resolveProject(api, projectRef2);
|
|
7791
|
-
const workflowId = opts.workflow ? (await resolveWorkflow(api, opts.workflow)).id : void 0;
|
|
7893
|
+
const workflowId = opts.workflow ? (await resolveWorkflow(api, opts.workflow, project.id)).id : void 0;
|
|
7792
7894
|
const recurrence2 = buildRecurrence(opts);
|
|
7793
7895
|
if (!recurrence2 && (opts.ifClosed !== void 0 || opts.scheduleName !== void 0)) {
|
|
7794
7896
|
die('--if-closed/--schedule-name need a recurrence: add --every \u2026 or --cron "<expr>"');
|
|
@@ -7850,7 +7952,7 @@ function register3(program3) {
|
|
|
7850
7952
|
if (description !== void 0) body.description = description;
|
|
7851
7953
|
if (opts.state !== void 0) body.state = opts.state;
|
|
7852
7954
|
if (opts.workflow !== void 0)
|
|
7853
|
-
body.workflow_id = (await resolveWorkflow(api, opts.workflow)).id;
|
|
7955
|
+
body.workflow_id = (await resolveWorkflow(api, opts.workflow, issue.project_id)).id;
|
|
7854
7956
|
if (Object.keys(body).length === 0) {
|
|
7855
7957
|
die("nothing to update: pass --title, --description, --state, and/or --workflow");
|
|
7856
7958
|
}
|
|
@@ -7868,14 +7970,87 @@ function register3(program3) {
|
|
|
7868
7970
|
}
|
|
7869
7971
|
);
|
|
7870
7972
|
withCommon(
|
|
7871
|
-
issues.command("move <ref> <action>").description(
|
|
7973
|
+
issues.command("move <ref> <action>").description("Take a current transition (members: no permission change)")
|
|
7872
7974
|
).action(async (ref, action2, opts) => {
|
|
7873
7975
|
const api = client(opts);
|
|
7874
7976
|
const issue = await resolveIssue(api, ref);
|
|
7875
|
-
|
|
7977
|
+
let request = { action: action2 };
|
|
7978
|
+
const member = issue;
|
|
7979
|
+
if (member.mode === "member") {
|
|
7980
|
+
const transition = member.workflow.transitions?.find(
|
|
7981
|
+
(candidate) => candidate.name.toLowerCase() === action2.toLowerCase()
|
|
7982
|
+
);
|
|
7983
|
+
if (!transition || !member.my_choice || member.workflow_revision === void 0 || member.decision_revision === void 0)
|
|
7984
|
+
throw new Error(`No current transition named "${action2}"; refresh the issue`);
|
|
7985
|
+
request = {
|
|
7986
|
+
transition_id: transition.id,
|
|
7987
|
+
expected_state_id: issue.state.id,
|
|
7988
|
+
expected_decision_revision: member.decision_revision,
|
|
7989
|
+
expected_workflow_revision: member.workflow_revision,
|
|
7990
|
+
expected_consent_revision: member.my_choice.revision,
|
|
7991
|
+
expected_consent_epoch: member.my_choice.epoch
|
|
7992
|
+
};
|
|
7993
|
+
} else {
|
|
7994
|
+
const project = issue.project_id ? await api.getProject(issue.project_id) : null;
|
|
7995
|
+
if (project?.shared_at != null) {
|
|
7996
|
+
const transition = issue.allowed_transitions.find(
|
|
7997
|
+
(candidate) => candidate.name.toLowerCase() === action2.toLowerCase()
|
|
7998
|
+
);
|
|
7999
|
+
if (!transition)
|
|
8000
|
+
throw new Error(`No current transition named "${action2}"; refresh the issue`);
|
|
8001
|
+
const permission = await api.getIssueConsent(issue.id);
|
|
8002
|
+
request = {
|
|
8003
|
+
transition_id: transition.transition_id,
|
|
8004
|
+
expected_state_id: permission.issue_state.id,
|
|
8005
|
+
expected_decision_revision: permission.issue_state.decision_revision,
|
|
8006
|
+
expected_workflow_revision: permission.issue_state.workflow_revision,
|
|
8007
|
+
expected_consent_revision: permission.my_agents.revision,
|
|
8008
|
+
expected_consent_epoch: permission.my_agents.epoch
|
|
8009
|
+
};
|
|
8010
|
+
}
|
|
8011
|
+
}
|
|
8012
|
+
const moved = await api.transitionIssue(issue.id, request);
|
|
7876
8013
|
if (opts.json) return printJson(moved);
|
|
8014
|
+
const movedMember = moved;
|
|
7877
8015
|
console.log(
|
|
7878
|
-
`${moved.project_name}/#${moved.number}: ${issue.state.name} \u2192 ${moved.state.name} ("${action2}")`
|
|
8016
|
+
`${moved.project_name ?? movedMember.project?.name}/#${moved.number}: ${issue.state.name} \u2192 ${moved.state.name} ("${action2}")`
|
|
8017
|
+
);
|
|
8018
|
+
if (member.mode === "member")
|
|
8019
|
+
console.log(
|
|
8020
|
+
"Personal permission is managed in the browser. Member execution is unavailable in this release."
|
|
8021
|
+
);
|
|
8022
|
+
else if (moved.permission_receipt?.message) console.log(moved.permission_receipt.message);
|
|
8023
|
+
});
|
|
8024
|
+
for (const held of [true, false]) {
|
|
8025
|
+
const verb = held ? "hold" : "release";
|
|
8026
|
+
withCommon(
|
|
8027
|
+
issues.command(`${verb} <ref>`).description(
|
|
8028
|
+
held ? "Hold an issue so approved agents cannot be admitted" : "Release an issue hold; existing permission choices stay unchanged"
|
|
8029
|
+
)
|
|
8030
|
+
).action(async (ref, opts) => {
|
|
8031
|
+
const api = client(opts);
|
|
8032
|
+
const issue = await resolveIssue(api, ref);
|
|
8033
|
+
const consent = await api.getIssueConsent(issue.id);
|
|
8034
|
+
const receipt = await api.setIssueHold(issue.id, {
|
|
8035
|
+
held,
|
|
8036
|
+
expected_revision: consent.agent_hold.revision
|
|
8037
|
+
});
|
|
8038
|
+
if (opts.json) return printJson(receipt);
|
|
8039
|
+
console.log(`${issueRef(issue)}: ${receipt.message}`);
|
|
8040
|
+
if (receipt.released_assigned > 0) {
|
|
8041
|
+
console.log(`${receipt.released_assigned} assigned run(s) were released without a strike.`);
|
|
8042
|
+
}
|
|
8043
|
+
});
|
|
8044
|
+
}
|
|
8045
|
+
withCommon(
|
|
8046
|
+
issues.command("cancel-run <ref> <run-id>").description("Request cancellation of a run that belongs to this issue")
|
|
8047
|
+
).action(async (ref, runId, opts) => {
|
|
8048
|
+
const api = client(opts);
|
|
8049
|
+
const issue = await resolveIssue(api, ref);
|
|
8050
|
+
const run3 = await api.cancelIssueRun(issue.id, runId);
|
|
8051
|
+
if (opts.json) return printJson(run3);
|
|
8052
|
+
console.log(
|
|
8053
|
+
`${issueRef(issue)}: ${run3.status === "canceled" ? "run canceled" : "cancellation requested; the run keeps its slot until it stops"}`
|
|
7879
8054
|
);
|
|
7880
8055
|
});
|
|
7881
8056
|
withCommon(
|
|
@@ -7974,7 +8149,7 @@ function register3(program3) {
|
|
|
7974
8149
|
const comment = await api.createComment(issue.id, { body });
|
|
7975
8150
|
if (opts.json) return printJson(comment);
|
|
7976
8151
|
console.log(
|
|
7977
|
-
`commented on ${issue.project_name}/#${issue.number} as ${actorLabel(comment.actor)} (id ${comment.id})`
|
|
8152
|
+
`commented on ${issue.project_name ?? issue.project?.name}/#${issue.number} as ${actorLabel(comment.actor)} (id ${comment.id})`
|
|
7978
8153
|
);
|
|
7979
8154
|
});
|
|
7980
8155
|
withCommon(
|
|
@@ -7987,7 +8162,9 @@ function register3(program3) {
|
|
|
7987
8162
|
const issue = await resolveIssue(api, ref);
|
|
7988
8163
|
const comment = await api.updateComment(issue.id, commentId, { body });
|
|
7989
8164
|
if (opts.json) return printJson(comment);
|
|
7990
|
-
console.log(
|
|
8165
|
+
console.log(
|
|
8166
|
+
`edited comment ${comment.id} on ${issue.project_name ?? issue.project?.name}/#${issue.number}`
|
|
8167
|
+
);
|
|
7991
8168
|
}
|
|
7992
8169
|
);
|
|
7993
8170
|
withCommon(
|
|
@@ -7997,7 +8174,9 @@ function register3(program3) {
|
|
|
7997
8174
|
const issue = await resolveIssue(api, ref);
|
|
7998
8175
|
await api.deleteComment(issue.id, commentId);
|
|
7999
8176
|
if (opts.json) return printJson({ id: commentId, deleted: true });
|
|
8000
|
-
console.log(
|
|
8177
|
+
console.log(
|
|
8178
|
+
`deleted comment ${commentId} from ${issue.project_name ?? issue.project?.name}/#${issue.number}`
|
|
8179
|
+
);
|
|
8001
8180
|
});
|
|
8002
8181
|
withCommon(
|
|
8003
8182
|
issues.command("label <ref> <name...>").description("Add labels to an issue (a label that does not exist yet is created)")
|
|
@@ -8857,6 +9036,89 @@ function registerEvents(program3) {
|
|
|
8857
9036
|
// src/commands/projects.ts
|
|
8858
9037
|
function register7(program3) {
|
|
8859
9038
|
const projects = program3.command("projects").description("Manage projects");
|
|
9039
|
+
withCommon(
|
|
9040
|
+
projects.command("people <project>").description("List the owner and active members")
|
|
9041
|
+
).action(async (ref, opts) => {
|
|
9042
|
+
const api = client(opts), project = await resolveProject(api, ref), people = await api.getProjectPeople(project.id);
|
|
9043
|
+
if (opts.json) return printJson(people);
|
|
9044
|
+
console.log(`${people.owner.name} [${people.owner.id}] \u2014 owner`);
|
|
9045
|
+
for (const person of people.members)
|
|
9046
|
+
console.log(`${person.name} [${person.id}] \u2014 member (revision ${person.revision})`);
|
|
9047
|
+
});
|
|
9048
|
+
withCommon(
|
|
9049
|
+
projects.command("invite <project> <email>").description("Invite someone to the whole project").option("--issue <ref>", "issue to open after acceptance").option(
|
|
9050
|
+
"--confirm-sharing",
|
|
9051
|
+
"acknowledge that first sharing gives members the whole project and restarts assigned work"
|
|
9052
|
+
)
|
|
9053
|
+
).action(
|
|
9054
|
+
async (ref, email, opts) => {
|
|
9055
|
+
const api = client(opts), project = await resolveProject(api, ref);
|
|
9056
|
+
if (project.shared_at == null && !opts.confirmSharing)
|
|
9057
|
+
die(
|
|
9058
|
+
"First sharing gives access to the whole project. Your agents keep working on its issues and schedules unless you turn them off in the browser; members need their own permission. Assigned work is restarted and admitted work may finish. Rerun with --confirm-sharing to send the invite."
|
|
9059
|
+
);
|
|
9060
|
+
const issue = opts.issue ? await resolveIssue(api, opts.issue) : null;
|
|
9061
|
+
if (issue && issue.project_id !== project.id)
|
|
9062
|
+
die("--issue must name an issue in the invited project");
|
|
9063
|
+
const result = await api.inviteProjectPerson(project.id, {
|
|
9064
|
+
email,
|
|
9065
|
+
...issue ? { landing_issue_id: issue.id } : {},
|
|
9066
|
+
confirm_sharing: opts.confirmSharing,
|
|
9067
|
+
expected_sharing_revision: project.sharing_revision ?? 0
|
|
9068
|
+
});
|
|
9069
|
+
if (opts.json) return printJson(result);
|
|
9070
|
+
console.log(
|
|
9071
|
+
`${result.delivery_status === "sent" ? "Invited" : "Invitation saved; delivery failed for"} ${result.email} [${result.id}]`
|
|
9072
|
+
);
|
|
9073
|
+
}
|
|
9074
|
+
);
|
|
9075
|
+
withCommon(
|
|
9076
|
+
projects.command("invite-resend <project> <invite-id>").description("Rotate and resend an invitation")
|
|
9077
|
+
).action(async (ref, inviteId, opts) => {
|
|
9078
|
+
const api = client(opts), project = await resolveProject(api, ref);
|
|
9079
|
+
const invite = (await api.listProjectInvitations(project.id)).items.find(
|
|
9080
|
+
(item) => item.id === inviteId
|
|
9081
|
+
);
|
|
9082
|
+
if (!invite) die("No such invitation in this project");
|
|
9083
|
+
const result = await api.resendProjectInvitation(project.id, inviteId, invite.generation);
|
|
9084
|
+
if (opts.json) return printJson(result);
|
|
9085
|
+
console.log(`Invitation ${inviteId} rotated; delivery ${result.delivery_status}`);
|
|
9086
|
+
});
|
|
9087
|
+
withCommon(
|
|
9088
|
+
projects.command("invite-cancel <project> <invite-id>").description("Cancel an invitation")
|
|
9089
|
+
).action(async (ref, inviteId, opts) => {
|
|
9090
|
+
const api = client(opts), project = await resolveProject(api, ref);
|
|
9091
|
+
const invite = (await api.listProjectInvitations(project.id)).items.find(
|
|
9092
|
+
(item) => item.id === inviteId
|
|
9093
|
+
);
|
|
9094
|
+
if (!invite) die("No such invitation in this project");
|
|
9095
|
+
await api.cancelProjectInvitation(project.id, inviteId, invite.generation);
|
|
9096
|
+
if (opts.json) return printJson({ canceled: true, id: inviteId });
|
|
9097
|
+
console.log(`Canceled invitation ${inviteId}`);
|
|
9098
|
+
});
|
|
9099
|
+
withCommon(
|
|
9100
|
+
projects.command("remove-member <project> <user-id>").description("Remove a project member")
|
|
9101
|
+
).action(async (ref, userId, opts) => {
|
|
9102
|
+
const api = client(opts), project = await resolveProject(api, ref);
|
|
9103
|
+
const member = (await api.getProjectPeople(project.id)).members.find(
|
|
9104
|
+
(person) => person.id === userId
|
|
9105
|
+
);
|
|
9106
|
+
if (!member) die("No active member has that user ID");
|
|
9107
|
+
const result = await api.removeProjectMember(project.id, userId, member.revision);
|
|
9108
|
+
if (opts.json) return printJson(result);
|
|
9109
|
+
console.log(`Removed ${member.name} from ${project.name}`);
|
|
9110
|
+
});
|
|
9111
|
+
withCommon(projects.command("leave <project>").description("Leave a shared project")).action(
|
|
9112
|
+
async (ref, opts) => {
|
|
9113
|
+
const api = client(opts), project = await resolveProject(api, ref);
|
|
9114
|
+
const people = await api.getProjectPeople(project.id);
|
|
9115
|
+
const revision = people.members.find((person) => person.id === people.viewer_id)?.revision;
|
|
9116
|
+
if (!revision) die("No active membership to leave");
|
|
9117
|
+
const result = await api.leaveProject(project.id, revision);
|
|
9118
|
+
if (opts.json) return printJson(result);
|
|
9119
|
+
console.log(`Left ${project.name}`);
|
|
9120
|
+
}
|
|
9121
|
+
);
|
|
8860
9122
|
withCommon(
|
|
8861
9123
|
projects.command("starters").description("List built-in project starters and their inputs")
|
|
8862
9124
|
).action(async (opts) => {
|
|
@@ -9004,9 +9266,11 @@ function register7(program3) {
|
|
|
9004
9266
|
const project = await resolveProject(api, ref);
|
|
9005
9267
|
if (opts.json) return printJson(project);
|
|
9006
9268
|
console.log(`${project.name} [${project.id}]`);
|
|
9269
|
+
if (project.viewer_role === "member")
|
|
9270
|
+
console.log(`shared by ${project.owner?.name ?? "project owner"} \u2014 read only`);
|
|
9007
9271
|
if (project.description) console.log(project.description);
|
|
9008
|
-
const defaultWorkflow = project.default_workflow_id ? (await api.getWorkflow(project.default_workflow_id)).name : "(standard)";
|
|
9009
|
-
console.log(`
|
|
9272
|
+
const defaultWorkflow = project.viewer_role === "member" ? null : project.default_workflow_id ? (await api.getWorkflow(project.default_workflow_id)).name : "(standard)";
|
|
9273
|
+
if (defaultWorkflow) console.log(`
|
|
9010
9274
|
default workflow: ${defaultWorkflow}`);
|
|
9011
9275
|
console.log(
|
|
9012
9276
|
`issues: ${project.issue_count} created: ${timestamp(project.created_at)} updated: ${timestamp(project.updated_at)}`
|
|
@@ -9025,7 +9289,7 @@ default workflow: ${defaultWorkflow}`);
|
|
|
9025
9289
|
if (opts.description !== void 0) body.description = opts.description;
|
|
9026
9290
|
if (opts.defaultWorkflow === false) body.default_workflow_id = null;
|
|
9027
9291
|
else if (opts.defaultWorkflow !== void 0) {
|
|
9028
|
-
body.default_workflow_id = (await resolveWorkflow(api, opts.defaultWorkflow)).id;
|
|
9292
|
+
body.default_workflow_id = (await resolveWorkflow(api, opts.defaultWorkflow, project.id)).id;
|
|
9029
9293
|
}
|
|
9030
9294
|
if (Object.keys(body).length === 0) {
|
|
9031
9295
|
die("nothing to update: pass --name, --description, or --[no-]default-workflow");
|
|
@@ -10361,6 +10625,14 @@ var RunTable = class {
|
|
|
10361
10625
|
this.runs.delete(run3.runId);
|
|
10362
10626
|
this.effects.persist();
|
|
10363
10627
|
this.effects.release(run3, { keep: this.keep(outcome, run3), outcome });
|
|
10628
|
+
if (run3.cancellationToken) {
|
|
10629
|
+
const acknowledgement = this.effects.acknowledgeCancellation?.(run3);
|
|
10630
|
+
if (acknowledgement) {
|
|
10631
|
+
void acknowledgement.catch(
|
|
10632
|
+
(error) => this.effects.log(`cancellation acknowledgement for ${run3.runId} failed: ${String(error)}`)
|
|
10633
|
+
);
|
|
10634
|
+
}
|
|
10635
|
+
}
|
|
10364
10636
|
}
|
|
10365
10637
|
/**
|
|
10366
10638
|
* Ends a run: flush logs, finish-report, clean up. On a run someone
|
|
@@ -10410,6 +10682,10 @@ var RunTable = class {
|
|
|
10410
10682
|
run3.settled = true;
|
|
10411
10683
|
return run3;
|
|
10412
10684
|
}
|
|
10685
|
+
noteCancellationRequest(runId, token) {
|
|
10686
|
+
const run3 = this.runs.get(runId);
|
|
10687
|
+
if (run3) run3.cancellationToken = token;
|
|
10688
|
+
}
|
|
10413
10689
|
};
|
|
10414
10690
|
var LogBatcher = class {
|
|
10415
10691
|
constructor(send, opts = {}) {
|
|
@@ -11024,6 +11300,7 @@ async function runDaemon(opts) {
|
|
|
11024
11300
|
const ensureCli = () => opts.cliRefresh ? refresher.ensure().then(noteCli) : Promise.resolve(AMBIENT_CLI);
|
|
11025
11301
|
const cliLabel = (cli) => cli.source === "ambient" ? `ambient PATH (${opts.cliRefresh ? "refresh failed" : "refresh disabled"})` : `tines ${cli.version ?? "unknown"} (daemon-managed${cli.source === "stale" ? ", last-good copy" : ""})`;
|
|
11026
11302
|
log(`agent CLI: ${cliLabel(await ensureCli())}`);
|
|
11303
|
+
const pendingCancellationAcks = /* @__PURE__ */ new Map();
|
|
11027
11304
|
const table2 = new RunTable(
|
|
11028
11305
|
{
|
|
11029
11306
|
finish: async (run3, status, error, judgment) => {
|
|
@@ -11101,6 +11378,9 @@ async function runDaemon(opts) {
|
|
|
11101
11378
|
void uploadRawLog(run3);
|
|
11102
11379
|
sweepKeptWorkspaces();
|
|
11103
11380
|
},
|
|
11381
|
+
acknowledgeCancellation: async (run3) => {
|
|
11382
|
+
if (run3.cancellationToken) pendingCancellationAcks.set(run3.runId, run3.cancellationToken);
|
|
11383
|
+
},
|
|
11104
11384
|
noteKept: (run3) => run3.batcher.append(`workspace kept at ${run3.workspace}
|
|
11105
11385
|
`),
|
|
11106
11386
|
persist: () => {
|
|
@@ -11502,6 +11782,12 @@ async function runDaemon(opts) {
|
|
|
11502
11782
|
const res = await client2.pollRunner(creds.runner_id, {
|
|
11503
11783
|
instance_id: instanceId,
|
|
11504
11784
|
owned_runs: table2.ids(),
|
|
11785
|
+
...pendingCancellationAcks.size > 0 ? {
|
|
11786
|
+
cancellation_acks: [...pendingCancellationAcks].slice(0, 100).map(([run_id, token]) => ({
|
|
11787
|
+
run_id,
|
|
11788
|
+
token
|
|
11789
|
+
}))
|
|
11790
|
+
} : {},
|
|
11505
11791
|
// Kept for older servers. Opted-in daemons advertise only the last
|
|
11506
11792
|
// accepted scheduling cap, never the higher machine ceiling.
|
|
11507
11793
|
max_concurrent: effectiveConcurrency,
|
|
@@ -11521,6 +11807,14 @@ async function runDaemon(opts) {
|
|
|
11521
11807
|
...pendingUpdate ? { draining: true } : {}
|
|
11522
11808
|
});
|
|
11523
11809
|
failures = 0;
|
|
11810
|
+
for (const ack of res.cancellation_acks ?? []) {
|
|
11811
|
+
if (pendingCancellationAcks.get(ack.run_id) === ack.token) {
|
|
11812
|
+
pendingCancellationAcks.delete(ack.run_id);
|
|
11813
|
+
}
|
|
11814
|
+
}
|
|
11815
|
+
for (const request of res.cancel_requests ?? []) {
|
|
11816
|
+
table2.noteCancellationRequest(request.run_id, request.token);
|
|
11817
|
+
}
|
|
11524
11818
|
for (const runId of res.cancels) killWithoutFinish(runId);
|
|
11525
11819
|
let declinesChanged = false;
|
|
11526
11820
|
for (const runId of res.released_assignments ?? []) {
|
|
@@ -12728,6 +13022,24 @@ async function resolveSchedule(api, ref) {
|
|
|
12728
13022
|
return found;
|
|
12729
13023
|
}
|
|
12730
13024
|
function printScheduleDetail(s) {
|
|
13025
|
+
const shared = s;
|
|
13026
|
+
if (shared.project) {
|
|
13027
|
+
console.log(`${shared.project.name}/${shared.name} [${shared.id}]`);
|
|
13028
|
+
console.log(`owner: ${shared.project.owner.name} workflow: ${shared.workflow?.name}`);
|
|
13029
|
+
console.log(`cron: ${shared.recurrence?.cron} timezone: ${shared.recurrence?.timezone}`);
|
|
13030
|
+
console.log(
|
|
13031
|
+
`status: ${shared.recurrence?.enabled ? "enabled" : "paused"} start state: ${shared.workflow?.start_state_name}`
|
|
13032
|
+
);
|
|
13033
|
+
console.log(`
|
|
13034
|
+
title template: ${shared.title_template}`);
|
|
13035
|
+
if (shared.description_template)
|
|
13036
|
+
console.log(`description template:
|
|
13037
|
+
${shared.description_template}`);
|
|
13038
|
+
console.log(
|
|
13039
|
+
"\nMembers edit, pause, run and delete shared schedules with `tines schedules` as the owner does."
|
|
13040
|
+
);
|
|
13041
|
+
return;
|
|
13042
|
+
}
|
|
12731
13043
|
console.log(`${scheduleRef(s)} [${s.id}]${s.enabled ? "" : " (paused)"}`);
|
|
12732
13044
|
console.log(`${recurrenceLabel(s)} (cron "${s.cron}")`);
|
|
12733
13045
|
console.log(
|
|
@@ -12736,6 +13048,10 @@ function printScheduleDetail(s) {
|
|
|
12736
13048
|
console.log(
|
|
12737
13049
|
`next run: ${s.enabled ? timestamp(s.next_run_at) : "(paused)"} last run: ${s.last_run_at ? timestamp(s.last_run_at) : "never"} runs: ${s.run_count} open instances: ${s.open_instances}`
|
|
12738
13050
|
);
|
|
13051
|
+
if (s.my_future_permission)
|
|
13052
|
+
console.log(
|
|
13053
|
+
`my agents on future issues: ${personalPermissionLabel("owner", s.my_future_permission.value)} permission epoch: ${s.my_future_permission.epoch} (change in the browser)`
|
|
13054
|
+
);
|
|
12739
13055
|
console.log(`
|
|
12740
13056
|
title template: ${s.title_template}`);
|
|
12741
13057
|
if (s.description_template) {
|
|
@@ -12760,15 +13076,27 @@ function register10(program3) {
|
|
|
12760
13076
|
printList(res, opts, (items) => {
|
|
12761
13077
|
if (items.length === 0) return console.log("no schedules");
|
|
12762
13078
|
table([
|
|
12763
|
-
["NAME", "RECURRENCE", "NEXT RUN", "LAST RUN", "OPEN", ""],
|
|
12764
|
-
...items.map((s) =>
|
|
12765
|
-
|
|
12766
|
-
|
|
12767
|
-
|
|
12768
|
-
|
|
12769
|
-
|
|
12770
|
-
|
|
12771
|
-
|
|
13079
|
+
["NAME", "RECURRENCE", "NEXT RUN", "LAST RUN", "OPEN", "MY FUTURE", ""],
|
|
13080
|
+
...items.map((s) => {
|
|
13081
|
+
const shared = s;
|
|
13082
|
+
return shared.project ? [
|
|
13083
|
+
`${shared.project.name}/${shared.name}`,
|
|
13084
|
+
shared.recurrence?.cron ?? "\u2014",
|
|
13085
|
+
"\u2014",
|
|
13086
|
+
"\u2014",
|
|
13087
|
+
"\u2014",
|
|
13088
|
+
shared.my_future_permission?.value ?? "unset",
|
|
13089
|
+
shared.recurrence?.enabled ? "" : "(paused)"
|
|
13090
|
+
] : [
|
|
13091
|
+
scheduleRef(s),
|
|
13092
|
+
recurrenceLabel(s),
|
|
13093
|
+
s.enabled ? timestamp(s.next_run_at) : "\u2014",
|
|
13094
|
+
s.last_run_at ? timestamp(s.last_run_at) : "never",
|
|
13095
|
+
String(s.open_instances),
|
|
13096
|
+
s.my_future_permission ? personalPermissionLabel("owner", s.my_future_permission.value) : "\u2014",
|
|
13097
|
+
s.enabled ? "" : "(paused)"
|
|
13098
|
+
];
|
|
13099
|
+
})
|
|
12772
13100
|
]);
|
|
12773
13101
|
});
|
|
12774
13102
|
});
|
|
@@ -12821,7 +13149,7 @@ recent instances:`);
|
|
|
12821
13149
|
if (opts.title !== void 0) body.title_template = opts.title;
|
|
12822
13150
|
if (descriptionTemplate !== void 0) body.description_template = descriptionTemplate;
|
|
12823
13151
|
if (opts.workflow !== void 0)
|
|
12824
|
-
body.workflow_id = (await resolveWorkflow(api, opts.workflow)).id;
|
|
13152
|
+
body.workflow_id = (await resolveWorkflow(api, opts.workflow, schedule2.project_id)).id;
|
|
12825
13153
|
if (opts.state !== void 0) body.state = opts.state;
|
|
12826
13154
|
const recurrence2 = buildRecurrence(opts);
|
|
12827
13155
|
if (recurrence2?.preset) body.preset = recurrence2.preset;
|