tines 0.0.289 → 0.0.290
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 +346 -24
- 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,6 +4176,27 @@ 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`),
|
|
@@ -4197,6 +4238,10 @@ function createApiClient(options) {
|
|
|
4197
4238
|
},
|
|
4198
4239
|
getIssue: (id2) => get(`/api/v1/issues/${id2}`),
|
|
4199
4240
|
getIssueByNumber: (projectId, number) => get(`/api/v1/projects/${projectId}/issues/${number}`),
|
|
4241
|
+
getIssueConsent: (id2) => get(`/api/v1/issues/${id2}/my-consent`),
|
|
4242
|
+
setIssueConsent: (id2, body) => request("PUT", `/api/v1/issues/${id2}/my-consent`, body),
|
|
4243
|
+
setIssueHold: (id2, body) => request("PUT", `/api/v1/issues/${id2}/agent-hold`, body),
|
|
4244
|
+
cancelIssueRun: (issueId, runId) => request("POST", `/api/v1/issues/${issueId}/runs/${runId}/cancel`, {}),
|
|
4200
4245
|
updateIssue: (id2, body) => request("PATCH", `/api/v1/issues/${id2}`, body),
|
|
4201
4246
|
transitionIssue: (id2, body) => request("POST", `/api/v1/issues/${id2}/transition`, body),
|
|
4202
4247
|
/** Un-park: clears needs_attention, resets the attempt count. */
|
|
@@ -4215,6 +4260,8 @@ function createApiClient(options) {
|
|
|
4215
4260
|
listSchedules: (filters = {}) => get(`/api/v1/schedules${query(filters)}`),
|
|
4216
4261
|
listProjectSchedules: (projectId, page = {}) => get(`/api/v1/projects/${projectId}/schedules${query(page)}`),
|
|
4217
4262
|
getSchedule: (id2) => get(`/api/v1/schedules/${id2}`),
|
|
4263
|
+
getScheduleConsent: (id2) => get(`/api/v1/schedules/${id2}/my-consent`),
|
|
4264
|
+
setScheduleConsent: (id2, body) => request("PUT", `/api/v1/schedules/${id2}/my-consent`, body),
|
|
4218
4265
|
updateSchedule: (id2, body) => request("PATCH", `/api/v1/schedules/${id2}`, body),
|
|
4219
4266
|
deleteSchedule: (id2) => request("DELETE", `/api/v1/schedules/${id2}`),
|
|
4220
4267
|
/** Run now: creates an instance immediately (gate-respecting; 422 when blocked). */
|
|
@@ -4354,6 +4401,7 @@ function createApiClient(options) {
|
|
|
4354
4401
|
// Supervisor settings
|
|
4355
4402
|
getPreferences: () => get("/api/v1/preferences"),
|
|
4356
4403
|
updatePreferences: (body) => request("PATCH", "/api/v1/preferences", body),
|
|
4404
|
+
acknowledgePermissionNotice: (version) => request("PUT", "/api/v1/preferences/disclosure", { version }),
|
|
4357
4405
|
getSupervisorSettings: () => get("/api/v1/supervisor/settings"),
|
|
4358
4406
|
getSupervisorQueue: (q = {}) => get(`/api/v1/supervisor/queue${query(q)}`),
|
|
4359
4407
|
getSupervisorStats: (q = {}) => get(`/api/v1/supervisor/stats${query(q)}`),
|
|
@@ -5810,6 +5858,12 @@ function supportedEfforts(capabilities, model) {
|
|
|
5810
5858
|
return capabilities.models.find((entry) => entry.model === model)?.efforts ?? null;
|
|
5811
5859
|
}
|
|
5812
5860
|
|
|
5861
|
+
// ../shared/src/personal-permission.ts
|
|
5862
|
+
function personalPermissionLabel(role, value) {
|
|
5863
|
+
if (role === "owner" && value === "unset") return "on (owner default)";
|
|
5864
|
+
return value;
|
|
5865
|
+
}
|
|
5866
|
+
|
|
5813
5867
|
// src/format.ts
|
|
5814
5868
|
function timestamp(ms) {
|
|
5815
5869
|
return new Date(ms).toISOString().replace("T", " ").slice(0, 19);
|
|
@@ -6361,7 +6415,9 @@ async function resolveProject(api, ref) {
|
|
|
6361
6415
|
const byName = items.filter((p) => p.name === ref);
|
|
6362
6416
|
if (byName.length === 1) return byName[0];
|
|
6363
6417
|
if (byName.length > 1) {
|
|
6364
|
-
die(
|
|
6418
|
+
die(
|
|
6419
|
+
`project name "${ref}" is ambiguous; use an id: ${byName.map((p) => `${p.id}${p.owner ? ` (${p.owner.name})` : ""}`).join(", ")}`
|
|
6420
|
+
);
|
|
6365
6421
|
}
|
|
6366
6422
|
const have = items.map((p) => p.archived_at ? `${p.name} (archived)` : p.name).join(", ");
|
|
6367
6423
|
die(`no project named "${ref}" (have: ${have || "none"})`);
|
|
@@ -7558,6 +7614,44 @@ function printIssueLinks(links) {
|
|
|
7558
7614
|
}
|
|
7559
7615
|
}
|
|
7560
7616
|
function printIssueDetail(issue) {
|
|
7617
|
+
const shared = issue;
|
|
7618
|
+
if (shared.mode === "member") {
|
|
7619
|
+
console.log(`${shared.project?.name}/${shared.number} ${shared.title}`);
|
|
7620
|
+
console.log(
|
|
7621
|
+
`state: ${shared.state?.name} (${shared.state?.category}) workflow: ${shared.workflow?.name}`
|
|
7622
|
+
);
|
|
7623
|
+
console.log(`owner: ${shared.project?.owner.name} id: ${shared.id}`);
|
|
7624
|
+
if (shared.description) console.log(`
|
|
7625
|
+
${shared.description}`);
|
|
7626
|
+
if (shared.artifacts?.length) {
|
|
7627
|
+
console.log("\nartifacts:");
|
|
7628
|
+
table(shared.artifacts.map((a) => [a.name, `v${a.current_version.version}`]));
|
|
7629
|
+
}
|
|
7630
|
+
if (shared.roster?.length) {
|
|
7631
|
+
console.log("\npeople and permission:");
|
|
7632
|
+
table(
|
|
7633
|
+
shared.roster.map((person) => [
|
|
7634
|
+
person.user.name,
|
|
7635
|
+
person.role,
|
|
7636
|
+
personalPermissionLabel(person.role, person.value)
|
|
7637
|
+
])
|
|
7638
|
+
);
|
|
7639
|
+
}
|
|
7640
|
+
console.log(`your issue permission: ${shared.my_choice?.value ?? "unset"} (browser control)`);
|
|
7641
|
+
console.log(`latest run: ${shared.latest_run?.status ?? "none"}`);
|
|
7642
|
+
if (shared.comments?.length) {
|
|
7643
|
+
console.log("\ncomments:");
|
|
7644
|
+
for (const comment of shared.comments)
|
|
7645
|
+
console.log(
|
|
7646
|
+
`${comment.id} \xB7 ${comment.author.name}${comment.author.run ? ` via ${comment.author.run.name}` : ""}${comment.editor ? ` \xB7 edited by ${comment.editor.name}` : ""}
|
|
7647
|
+
${comment.body}`
|
|
7648
|
+
);
|
|
7649
|
+
}
|
|
7650
|
+
console.log(
|
|
7651
|
+
"\nMembers may comment and take a current awaiting-human transition. Personal permission is chosen in the browser; member execution is unavailable."
|
|
7652
|
+
);
|
|
7653
|
+
return;
|
|
7654
|
+
}
|
|
7561
7655
|
console.log(`${issue.project_name}/#${issue.number} ${issue.title}`);
|
|
7562
7656
|
const dup = issue.duplicate_of;
|
|
7563
7657
|
console.log(
|
|
@@ -7577,6 +7671,10 @@ function printIssueDetail(issue) {
|
|
|
7577
7671
|
console.log(`labels: ${issue.labels.map((l) => l.name).join(", ")}`);
|
|
7578
7672
|
}
|
|
7579
7673
|
console.log(`id: ${issue.id}`);
|
|
7674
|
+
if (issue.schedule_origin)
|
|
7675
|
+
console.log(
|
|
7676
|
+
`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)`
|
|
7677
|
+
);
|
|
7580
7678
|
printIssueLinks(issue.links);
|
|
7581
7679
|
if (issue.description) {
|
|
7582
7680
|
console.log(`
|
|
@@ -7732,13 +7830,13 @@ function register3(program3) {
|
|
|
7732
7830
|
`${i.project_name}/${i.number}`,
|
|
7733
7831
|
i.title,
|
|
7734
7832
|
i.effective_state.name,
|
|
7735
|
-
ageLabel2(new Date(i.state_entered_at).toISOString()),
|
|
7833
|
+
ageLabel2(new Date(i.state_entered_at ?? i.created_at).toISOString()),
|
|
7736
7834
|
arrivedViaLabel(i.arrived_via),
|
|
7737
7835
|
roundSummaryLabel(i.round_summary ?? null),
|
|
7738
7836
|
[
|
|
7739
|
-
i.open_blockers
|
|
7837
|
+
(i.open_blockers?.length ?? 0) > 0 ? "blocked" : "",
|
|
7740
7838
|
i.duplicate_of ? "dup" : "",
|
|
7741
|
-
...i.labels.map((l) => `[${l.name}]`)
|
|
7839
|
+
...(i.labels ?? []).map((l) => `[${l.name}]`)
|
|
7742
7840
|
].filter(Boolean).join(" ")
|
|
7743
7841
|
])
|
|
7744
7842
|
]);
|
|
@@ -7753,11 +7851,11 @@ function register3(program3) {
|
|
|
7753
7851
|
// (and the filters above) go by the effective state.
|
|
7754
7852
|
i.effective_state.name,
|
|
7755
7853
|
i.effective_state.category,
|
|
7756
|
-
timestamp(i.last_activity_at),
|
|
7854
|
+
timestamp(i.last_activity_at ?? i.updated_at),
|
|
7757
7855
|
[
|
|
7758
|
-
i.open_blockers
|
|
7856
|
+
(i.open_blockers?.length ?? 0) > 0 ? "blocked" : "",
|
|
7759
7857
|
i.duplicate_of ? "dup" : "",
|
|
7760
|
-
...i.labels.map((l) => `[${l.name}]`)
|
|
7858
|
+
...(i.labels ?? []).map((l) => `[${l.name}]`)
|
|
7761
7859
|
].filter(Boolean).join(" ")
|
|
7762
7860
|
])
|
|
7763
7861
|
]);
|
|
@@ -7868,14 +7966,87 @@ function register3(program3) {
|
|
|
7868
7966
|
}
|
|
7869
7967
|
);
|
|
7870
7968
|
withCommon(
|
|
7871
|
-
issues.command("move <ref> <action>").description(
|
|
7969
|
+
issues.command("move <ref> <action>").description("Take a current transition (members: awaiting-human only; no permission change)")
|
|
7872
7970
|
).action(async (ref, action2, opts) => {
|
|
7873
7971
|
const api = client(opts);
|
|
7874
7972
|
const issue = await resolveIssue(api, ref);
|
|
7875
|
-
|
|
7973
|
+
let request = { action: action2 };
|
|
7974
|
+
const member = issue;
|
|
7975
|
+
if (member.mode === "member") {
|
|
7976
|
+
const transition = member.workflow.transitions?.find(
|
|
7977
|
+
(candidate) => candidate.name.toLowerCase() === action2.toLowerCase()
|
|
7978
|
+
);
|
|
7979
|
+
if (!transition || !member.my_choice || member.workflow_revision === void 0 || member.decision_revision === void 0)
|
|
7980
|
+
throw new Error(`No current transition named "${action2}"; refresh the issue`);
|
|
7981
|
+
request = {
|
|
7982
|
+
transition_id: transition.id,
|
|
7983
|
+
expected_state_id: issue.state.id,
|
|
7984
|
+
expected_decision_revision: member.decision_revision,
|
|
7985
|
+
expected_workflow_revision: member.workflow_revision,
|
|
7986
|
+
expected_consent_revision: member.my_choice.revision,
|
|
7987
|
+
expected_consent_epoch: member.my_choice.epoch
|
|
7988
|
+
};
|
|
7989
|
+
} else {
|
|
7990
|
+
const project = issue.project_id ? await api.getProject(issue.project_id) : null;
|
|
7991
|
+
if (project?.shared_at != null) {
|
|
7992
|
+
const transition = issue.allowed_transitions.find(
|
|
7993
|
+
(candidate) => candidate.name.toLowerCase() === action2.toLowerCase()
|
|
7994
|
+
);
|
|
7995
|
+
if (!transition)
|
|
7996
|
+
throw new Error(`No current transition named "${action2}"; refresh the issue`);
|
|
7997
|
+
const permission = await api.getIssueConsent(issue.id);
|
|
7998
|
+
request = {
|
|
7999
|
+
transition_id: transition.transition_id,
|
|
8000
|
+
expected_state_id: permission.issue_state.id,
|
|
8001
|
+
expected_decision_revision: permission.issue_state.decision_revision,
|
|
8002
|
+
expected_workflow_revision: permission.issue_state.workflow_revision,
|
|
8003
|
+
expected_consent_revision: permission.my_agents.revision,
|
|
8004
|
+
expected_consent_epoch: permission.my_agents.epoch
|
|
8005
|
+
};
|
|
8006
|
+
}
|
|
8007
|
+
}
|
|
8008
|
+
const moved = await api.transitionIssue(issue.id, request);
|
|
7876
8009
|
if (opts.json) return printJson(moved);
|
|
8010
|
+
const movedMember = moved;
|
|
8011
|
+
console.log(
|
|
8012
|
+
`${moved.project_name ?? movedMember.project?.name}/#${moved.number}: ${issue.state.name} \u2192 ${moved.state.name} ("${action2}")`
|
|
8013
|
+
);
|
|
8014
|
+
if (member.mode === "member")
|
|
8015
|
+
console.log(
|
|
8016
|
+
"Personal permission is managed in the browser. Member execution is unavailable in this release."
|
|
8017
|
+
);
|
|
8018
|
+
else if (moved.permission_receipt?.message) console.log(moved.permission_receipt.message);
|
|
8019
|
+
});
|
|
8020
|
+
for (const held of [true, false]) {
|
|
8021
|
+
const verb = held ? "hold" : "release";
|
|
8022
|
+
withCommon(
|
|
8023
|
+
issues.command(`${verb} <ref>`).description(
|
|
8024
|
+
held ? "Hold an issue so approved agents cannot be admitted" : "Release an issue hold; existing permission choices stay unchanged"
|
|
8025
|
+
)
|
|
8026
|
+
).action(async (ref, opts) => {
|
|
8027
|
+
const api = client(opts);
|
|
8028
|
+
const issue = await resolveIssue(api, ref);
|
|
8029
|
+
const consent = await api.getIssueConsent(issue.id);
|
|
8030
|
+
const receipt = await api.setIssueHold(issue.id, {
|
|
8031
|
+
held,
|
|
8032
|
+
expected_revision: consent.agent_hold.revision
|
|
8033
|
+
});
|
|
8034
|
+
if (opts.json) return printJson(receipt);
|
|
8035
|
+
console.log(`${issueRef(issue)}: ${receipt.message}`);
|
|
8036
|
+
if (receipt.released_assigned > 0) {
|
|
8037
|
+
console.log(`${receipt.released_assigned} assigned run(s) were released without a strike.`);
|
|
8038
|
+
}
|
|
8039
|
+
});
|
|
8040
|
+
}
|
|
8041
|
+
withCommon(
|
|
8042
|
+
issues.command("cancel-run <ref> <run-id>").description("Request cancellation of a run that belongs to this issue")
|
|
8043
|
+
).action(async (ref, runId, opts) => {
|
|
8044
|
+
const api = client(opts);
|
|
8045
|
+
const issue = await resolveIssue(api, ref);
|
|
8046
|
+
const run3 = await api.cancelIssueRun(issue.id, runId);
|
|
8047
|
+
if (opts.json) return printJson(run3);
|
|
7877
8048
|
console.log(
|
|
7878
|
-
`${
|
|
8049
|
+
`${issueRef(issue)}: ${run3.status === "canceled" ? "run canceled" : "cancellation requested; the run keeps its slot until it stops"}`
|
|
7879
8050
|
);
|
|
7880
8051
|
});
|
|
7881
8052
|
withCommon(
|
|
@@ -7974,7 +8145,7 @@ function register3(program3) {
|
|
|
7974
8145
|
const comment = await api.createComment(issue.id, { body });
|
|
7975
8146
|
if (opts.json) return printJson(comment);
|
|
7976
8147
|
console.log(
|
|
7977
|
-
`commented on ${issue.project_name}/#${issue.number} as ${actorLabel(comment.actor)} (id ${comment.id})`
|
|
8148
|
+
`commented on ${issue.project_name ?? issue.project?.name}/#${issue.number} as ${actorLabel(comment.actor)} (id ${comment.id})`
|
|
7978
8149
|
);
|
|
7979
8150
|
});
|
|
7980
8151
|
withCommon(
|
|
@@ -7987,7 +8158,9 @@ function register3(program3) {
|
|
|
7987
8158
|
const issue = await resolveIssue(api, ref);
|
|
7988
8159
|
const comment = await api.updateComment(issue.id, commentId, { body });
|
|
7989
8160
|
if (opts.json) return printJson(comment);
|
|
7990
|
-
console.log(
|
|
8161
|
+
console.log(
|
|
8162
|
+
`edited comment ${comment.id} on ${issue.project_name ?? issue.project?.name}/#${issue.number}`
|
|
8163
|
+
);
|
|
7991
8164
|
}
|
|
7992
8165
|
);
|
|
7993
8166
|
withCommon(
|
|
@@ -7997,7 +8170,9 @@ function register3(program3) {
|
|
|
7997
8170
|
const issue = await resolveIssue(api, ref);
|
|
7998
8171
|
await api.deleteComment(issue.id, commentId);
|
|
7999
8172
|
if (opts.json) return printJson({ id: commentId, deleted: true });
|
|
8000
|
-
console.log(
|
|
8173
|
+
console.log(
|
|
8174
|
+
`deleted comment ${commentId} from ${issue.project_name ?? issue.project?.name}/#${issue.number}`
|
|
8175
|
+
);
|
|
8001
8176
|
});
|
|
8002
8177
|
withCommon(
|
|
8003
8178
|
issues.command("label <ref> <name...>").description("Add labels to an issue (a label that does not exist yet is created)")
|
|
@@ -8857,6 +9032,89 @@ function registerEvents(program3) {
|
|
|
8857
9032
|
// src/commands/projects.ts
|
|
8858
9033
|
function register7(program3) {
|
|
8859
9034
|
const projects = program3.command("projects").description("Manage projects");
|
|
9035
|
+
withCommon(
|
|
9036
|
+
projects.command("people <project>").description("List the owner and active members")
|
|
9037
|
+
).action(async (ref, opts) => {
|
|
9038
|
+
const api = client(opts), project = await resolveProject(api, ref), people = await api.getProjectPeople(project.id);
|
|
9039
|
+
if (opts.json) return printJson(people);
|
|
9040
|
+
console.log(`${people.owner.name} [${people.owner.id}] \u2014 owner`);
|
|
9041
|
+
for (const person of people.members)
|
|
9042
|
+
console.log(`${person.name} [${person.id}] \u2014 member (revision ${person.revision})`);
|
|
9043
|
+
});
|
|
9044
|
+
withCommon(
|
|
9045
|
+
projects.command("invite <project> <email>").description("Invite someone to the whole project").option("--issue <ref>", "issue to open after acceptance").option(
|
|
9046
|
+
"--confirm-sharing",
|
|
9047
|
+
"acknowledge that first sharing gives members the whole project and restarts assigned work"
|
|
9048
|
+
)
|
|
9049
|
+
).action(
|
|
9050
|
+
async (ref, email, opts) => {
|
|
9051
|
+
const api = client(opts), project = await resolveProject(api, ref);
|
|
9052
|
+
if (project.shared_at == null && !opts.confirmSharing)
|
|
9053
|
+
die(
|
|
9054
|
+
"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."
|
|
9055
|
+
);
|
|
9056
|
+
const issue = opts.issue ? await resolveIssue(api, opts.issue) : null;
|
|
9057
|
+
if (issue && issue.project_id !== project.id)
|
|
9058
|
+
die("--issue must name an issue in the invited project");
|
|
9059
|
+
const result = await api.inviteProjectPerson(project.id, {
|
|
9060
|
+
email,
|
|
9061
|
+
...issue ? { landing_issue_id: issue.id } : {},
|
|
9062
|
+
confirm_sharing: opts.confirmSharing,
|
|
9063
|
+
expected_sharing_revision: project.sharing_revision ?? 0
|
|
9064
|
+
});
|
|
9065
|
+
if (opts.json) return printJson(result);
|
|
9066
|
+
console.log(
|
|
9067
|
+
`${result.delivery_status === "sent" ? "Invited" : "Invitation saved; delivery failed for"} ${result.email} [${result.id}]`
|
|
9068
|
+
);
|
|
9069
|
+
}
|
|
9070
|
+
);
|
|
9071
|
+
withCommon(
|
|
9072
|
+
projects.command("invite-resend <project> <invite-id>").description("Rotate and resend an invitation")
|
|
9073
|
+
).action(async (ref, inviteId, opts) => {
|
|
9074
|
+
const api = client(opts), project = await resolveProject(api, ref);
|
|
9075
|
+
const invite = (await api.listProjectInvitations(project.id)).items.find(
|
|
9076
|
+
(item) => item.id === inviteId
|
|
9077
|
+
);
|
|
9078
|
+
if (!invite) die("No such invitation in this project");
|
|
9079
|
+
const result = await api.resendProjectInvitation(project.id, inviteId, invite.generation);
|
|
9080
|
+
if (opts.json) return printJson(result);
|
|
9081
|
+
console.log(`Invitation ${inviteId} rotated; delivery ${result.delivery_status}`);
|
|
9082
|
+
});
|
|
9083
|
+
withCommon(
|
|
9084
|
+
projects.command("invite-cancel <project> <invite-id>").description("Cancel an invitation")
|
|
9085
|
+
).action(async (ref, inviteId, opts) => {
|
|
9086
|
+
const api = client(opts), project = await resolveProject(api, ref);
|
|
9087
|
+
const invite = (await api.listProjectInvitations(project.id)).items.find(
|
|
9088
|
+
(item) => item.id === inviteId
|
|
9089
|
+
);
|
|
9090
|
+
if (!invite) die("No such invitation in this project");
|
|
9091
|
+
await api.cancelProjectInvitation(project.id, inviteId, invite.generation);
|
|
9092
|
+
if (opts.json) return printJson({ canceled: true, id: inviteId });
|
|
9093
|
+
console.log(`Canceled invitation ${inviteId}`);
|
|
9094
|
+
});
|
|
9095
|
+
withCommon(
|
|
9096
|
+
projects.command("remove-member <project> <user-id>").description("Remove a project member")
|
|
9097
|
+
).action(async (ref, userId, opts) => {
|
|
9098
|
+
const api = client(opts), project = await resolveProject(api, ref);
|
|
9099
|
+
const member = (await api.getProjectPeople(project.id)).members.find(
|
|
9100
|
+
(person) => person.id === userId
|
|
9101
|
+
);
|
|
9102
|
+
if (!member) die("No active member has that user ID");
|
|
9103
|
+
const result = await api.removeProjectMember(project.id, userId, member.revision);
|
|
9104
|
+
if (opts.json) return printJson(result);
|
|
9105
|
+
console.log(`Removed ${member.name} from ${project.name}`);
|
|
9106
|
+
});
|
|
9107
|
+
withCommon(projects.command("leave <project>").description("Leave a shared project")).action(
|
|
9108
|
+
async (ref, opts) => {
|
|
9109
|
+
const api = client(opts), project = await resolveProject(api, ref);
|
|
9110
|
+
const people = await api.getProjectPeople(project.id);
|
|
9111
|
+
const revision = people.members.find((person) => person.id === people.viewer_id)?.revision;
|
|
9112
|
+
if (!revision) die("No active membership to leave");
|
|
9113
|
+
const result = await api.leaveProject(project.id, revision);
|
|
9114
|
+
if (opts.json) return printJson(result);
|
|
9115
|
+
console.log(`Left ${project.name}`);
|
|
9116
|
+
}
|
|
9117
|
+
);
|
|
8860
9118
|
withCommon(
|
|
8861
9119
|
projects.command("starters").description("List built-in project starters and their inputs")
|
|
8862
9120
|
).action(async (opts) => {
|
|
@@ -9004,9 +9262,11 @@ function register7(program3) {
|
|
|
9004
9262
|
const project = await resolveProject(api, ref);
|
|
9005
9263
|
if (opts.json) return printJson(project);
|
|
9006
9264
|
console.log(`${project.name} [${project.id}]`);
|
|
9265
|
+
if (project.viewer_role === "member")
|
|
9266
|
+
console.log(`shared by ${project.owner?.name ?? "project owner"} \u2014 read only`);
|
|
9007
9267
|
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(`
|
|
9268
|
+
const defaultWorkflow = project.viewer_role === "member" ? null : project.default_workflow_id ? (await api.getWorkflow(project.default_workflow_id)).name : "(standard)";
|
|
9269
|
+
if (defaultWorkflow) console.log(`
|
|
9010
9270
|
default workflow: ${defaultWorkflow}`);
|
|
9011
9271
|
console.log(
|
|
9012
9272
|
`issues: ${project.issue_count} created: ${timestamp(project.created_at)} updated: ${timestamp(project.updated_at)}`
|
|
@@ -10361,6 +10621,14 @@ var RunTable = class {
|
|
|
10361
10621
|
this.runs.delete(run3.runId);
|
|
10362
10622
|
this.effects.persist();
|
|
10363
10623
|
this.effects.release(run3, { keep: this.keep(outcome, run3), outcome });
|
|
10624
|
+
if (run3.cancellationToken) {
|
|
10625
|
+
const acknowledgement = this.effects.acknowledgeCancellation?.(run3);
|
|
10626
|
+
if (acknowledgement) {
|
|
10627
|
+
void acknowledgement.catch(
|
|
10628
|
+
(error) => this.effects.log(`cancellation acknowledgement for ${run3.runId} failed: ${String(error)}`)
|
|
10629
|
+
);
|
|
10630
|
+
}
|
|
10631
|
+
}
|
|
10364
10632
|
}
|
|
10365
10633
|
/**
|
|
10366
10634
|
* Ends a run: flush logs, finish-report, clean up. On a run someone
|
|
@@ -10410,6 +10678,10 @@ var RunTable = class {
|
|
|
10410
10678
|
run3.settled = true;
|
|
10411
10679
|
return run3;
|
|
10412
10680
|
}
|
|
10681
|
+
noteCancellationRequest(runId, token) {
|
|
10682
|
+
const run3 = this.runs.get(runId);
|
|
10683
|
+
if (run3) run3.cancellationToken = token;
|
|
10684
|
+
}
|
|
10413
10685
|
};
|
|
10414
10686
|
var LogBatcher = class {
|
|
10415
10687
|
constructor(send, opts = {}) {
|
|
@@ -11024,6 +11296,7 @@ async function runDaemon(opts) {
|
|
|
11024
11296
|
const ensureCli = () => opts.cliRefresh ? refresher.ensure().then(noteCli) : Promise.resolve(AMBIENT_CLI);
|
|
11025
11297
|
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
11298
|
log(`agent CLI: ${cliLabel(await ensureCli())}`);
|
|
11299
|
+
const pendingCancellationAcks = /* @__PURE__ */ new Map();
|
|
11027
11300
|
const table2 = new RunTable(
|
|
11028
11301
|
{
|
|
11029
11302
|
finish: async (run3, status, error, judgment) => {
|
|
@@ -11101,6 +11374,9 @@ async function runDaemon(opts) {
|
|
|
11101
11374
|
void uploadRawLog(run3);
|
|
11102
11375
|
sweepKeptWorkspaces();
|
|
11103
11376
|
},
|
|
11377
|
+
acknowledgeCancellation: async (run3) => {
|
|
11378
|
+
if (run3.cancellationToken) pendingCancellationAcks.set(run3.runId, run3.cancellationToken);
|
|
11379
|
+
},
|
|
11104
11380
|
noteKept: (run3) => run3.batcher.append(`workspace kept at ${run3.workspace}
|
|
11105
11381
|
`),
|
|
11106
11382
|
persist: () => {
|
|
@@ -11502,6 +11778,12 @@ async function runDaemon(opts) {
|
|
|
11502
11778
|
const res = await client2.pollRunner(creds.runner_id, {
|
|
11503
11779
|
instance_id: instanceId,
|
|
11504
11780
|
owned_runs: table2.ids(),
|
|
11781
|
+
...pendingCancellationAcks.size > 0 ? {
|
|
11782
|
+
cancellation_acks: [...pendingCancellationAcks].slice(0, 100).map(([run_id, token]) => ({
|
|
11783
|
+
run_id,
|
|
11784
|
+
token
|
|
11785
|
+
}))
|
|
11786
|
+
} : {},
|
|
11505
11787
|
// Kept for older servers. Opted-in daemons advertise only the last
|
|
11506
11788
|
// accepted scheduling cap, never the higher machine ceiling.
|
|
11507
11789
|
max_concurrent: effectiveConcurrency,
|
|
@@ -11521,6 +11803,14 @@ async function runDaemon(opts) {
|
|
|
11521
11803
|
...pendingUpdate ? { draining: true } : {}
|
|
11522
11804
|
});
|
|
11523
11805
|
failures = 0;
|
|
11806
|
+
for (const ack of res.cancellation_acks ?? []) {
|
|
11807
|
+
if (pendingCancellationAcks.get(ack.run_id) === ack.token) {
|
|
11808
|
+
pendingCancellationAcks.delete(ack.run_id);
|
|
11809
|
+
}
|
|
11810
|
+
}
|
|
11811
|
+
for (const request of res.cancel_requests ?? []) {
|
|
11812
|
+
table2.noteCancellationRequest(request.run_id, request.token);
|
|
11813
|
+
}
|
|
11524
11814
|
for (const runId of res.cancels) killWithoutFinish(runId);
|
|
11525
11815
|
let declinesChanged = false;
|
|
11526
11816
|
for (const runId of res.released_assignments ?? []) {
|
|
@@ -12728,6 +13018,22 @@ async function resolveSchedule(api, ref) {
|
|
|
12728
13018
|
return found;
|
|
12729
13019
|
}
|
|
12730
13020
|
function printScheduleDetail(s) {
|
|
13021
|
+
const shared = s;
|
|
13022
|
+
if (shared.project) {
|
|
13023
|
+
console.log(`${shared.project.name}/${shared.name} [${shared.id}]`);
|
|
13024
|
+
console.log(`owner: ${shared.project.owner.name} workflow: ${shared.workflow?.name}`);
|
|
13025
|
+
console.log(`cron: ${shared.recurrence?.cron} timezone: ${shared.recurrence?.timezone}`);
|
|
13026
|
+
console.log(
|
|
13027
|
+
`status: ${shared.recurrence?.enabled ? "enabled" : "paused"} start state: ${shared.workflow?.start_state_name}`
|
|
13028
|
+
);
|
|
13029
|
+
console.log(`
|
|
13030
|
+
title template: ${shared.title_template}`);
|
|
13031
|
+
if (shared.description_template)
|
|
13032
|
+
console.log(`description template:
|
|
13033
|
+
${shared.description_template}`);
|
|
13034
|
+
console.log("\nThis shared schedule is read only in the current release.");
|
|
13035
|
+
return;
|
|
13036
|
+
}
|
|
12731
13037
|
console.log(`${scheduleRef(s)} [${s.id}]${s.enabled ? "" : " (paused)"}`);
|
|
12732
13038
|
console.log(`${recurrenceLabel(s)} (cron "${s.cron}")`);
|
|
12733
13039
|
console.log(
|
|
@@ -12736,6 +13042,10 @@ function printScheduleDetail(s) {
|
|
|
12736
13042
|
console.log(
|
|
12737
13043
|
`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
13044
|
);
|
|
13045
|
+
if (s.my_future_permission)
|
|
13046
|
+
console.log(
|
|
13047
|
+
`my agents on future issues: ${personalPermissionLabel("owner", s.my_future_permission.value)} permission epoch: ${s.my_future_permission.epoch} (change in the browser)`
|
|
13048
|
+
);
|
|
12739
13049
|
console.log(`
|
|
12740
13050
|
title template: ${s.title_template}`);
|
|
12741
13051
|
if (s.description_template) {
|
|
@@ -12760,15 +13070,27 @@ function register10(program3) {
|
|
|
12760
13070
|
printList(res, opts, (items) => {
|
|
12761
13071
|
if (items.length === 0) return console.log("no schedules");
|
|
12762
13072
|
table([
|
|
12763
|
-
["NAME", "RECURRENCE", "NEXT RUN", "LAST RUN", "OPEN", ""],
|
|
12764
|
-
...items.map((s) =>
|
|
12765
|
-
|
|
12766
|
-
|
|
12767
|
-
|
|
12768
|
-
|
|
12769
|
-
|
|
12770
|
-
|
|
12771
|
-
|
|
13073
|
+
["NAME", "RECURRENCE", "NEXT RUN", "LAST RUN", "OPEN", "MY FUTURE", ""],
|
|
13074
|
+
...items.map((s) => {
|
|
13075
|
+
const shared = s;
|
|
13076
|
+
return shared.project ? [
|
|
13077
|
+
`${shared.project.name}/${shared.name}`,
|
|
13078
|
+
shared.recurrence?.cron ?? "\u2014",
|
|
13079
|
+
"\u2014",
|
|
13080
|
+
"\u2014",
|
|
13081
|
+
"\u2014",
|
|
13082
|
+
shared.my_future_permission?.value ?? "unset",
|
|
13083
|
+
shared.recurrence?.enabled ? "" : "(paused)"
|
|
13084
|
+
] : [
|
|
13085
|
+
scheduleRef(s),
|
|
13086
|
+
recurrenceLabel(s),
|
|
13087
|
+
s.enabled ? timestamp(s.next_run_at) : "\u2014",
|
|
13088
|
+
s.last_run_at ? timestamp(s.last_run_at) : "never",
|
|
13089
|
+
String(s.open_instances),
|
|
13090
|
+
s.my_future_permission ? personalPermissionLabel("owner", s.my_future_permission.value) : "\u2014",
|
|
13091
|
+
s.enabled ? "" : "(paused)"
|
|
13092
|
+
];
|
|
13093
|
+
})
|
|
12772
13094
|
]);
|
|
12773
13095
|
});
|
|
12774
13096
|
});
|