@skrr-ai/cli 0.1.22 → 0.1.23
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 +83 -73
- package/bin/dev-fallback.js +221 -0
- package/bin/run.js +43 -0
- package/dist/base-command.d.ts +15 -0
- package/dist/base-command.js +71 -10
- package/dist/commands/agents/attach-harness.d.ts +37 -0
- package/dist/commands/agents/attach-harness.js +102 -3
- package/dist/commands/agents/chat.d.ts +69 -1
- package/dist/commands/agents/chat.js +202 -22
- package/dist/commands/agents/create.d.ts +38 -0
- package/dist/commands/agents/create.js +95 -2
- package/dist/commands/agents/schedule-trace.js +11 -0
- package/dist/commands/agents/show.js +29 -1
- package/dist/commands/api-keys/create.d.ts +24 -0
- package/dist/commands/api-keys/create.js +80 -0
- package/dist/commands/api-keys/list.d.ts +11 -0
- package/dist/commands/api-keys/list.js +42 -0
- package/dist/commands/api-keys/revoke.d.ts +14 -0
- package/dist/commands/api-keys/revoke.js +33 -0
- package/dist/commands/api-keys/rotate.d.ts +19 -0
- package/dist/commands/api-keys/rotate.js +42 -0
- package/dist/commands/balance/index.d.ts +18 -0
- package/dist/commands/balance/index.js +29 -0
- package/dist/commands/balance/show.d.ts +25 -0
- package/dist/commands/balance/show.js +60 -0
- package/dist/commands/balance/statement.d.ts +19 -0
- package/dist/commands/balance/statement.js +52 -0
- package/dist/commands/browser/doctor.d.ts +11 -0
- package/dist/commands/browser/doctor.js +11 -0
- package/dist/commands/code/jobs/approve.d.ts +25 -0
- package/dist/commands/code/jobs/approve.js +52 -0
- package/dist/commands/code/jobs/cancel.d.ts +12 -0
- package/dist/commands/code/jobs/cancel.js +39 -0
- package/dist/commands/code/jobs/index.d.ts +19 -0
- package/dist/commands/code/jobs/index.js +38 -0
- package/dist/commands/code/jobs/list.d.ts +12 -0
- package/dist/commands/code/jobs/list.js +87 -0
- package/dist/commands/code/jobs/run.d.ts +32 -0
- package/dist/commands/code/jobs/run.js +151 -0
- package/dist/commands/code/jobs/show.d.ts +12 -0
- package/dist/commands/code/jobs/show.js +86 -0
- package/dist/commands/commitments/preflight.js +18 -4
- package/dist/commands/commitments/receipt-metrics.d.ts +39 -0
- package/dist/commands/commitments/receipt-metrics.js +78 -0
- package/dist/commands/convos/search.js +5 -5
- package/dist/commands/harnesses/leases/list.d.ts +3 -0
- package/dist/commands/harnesses/leases/list.js +59 -12
- package/dist/commands/harnesses/leases/show.js +36 -0
- package/dist/commands/harnesses/list.d.ts +18 -0
- package/dist/commands/harnesses/list.js +30 -0
- package/dist/commands/harnesses/products.js +48 -3
- package/dist/commands/harnesses/quota.d.ts +16 -0
- package/dist/commands/harnesses/quota.js +26 -1
- package/dist/commands/harnesses/usage.js +17 -0
- package/dist/commands/inbox/index.d.ts +2 -1
- package/dist/commands/inbox/index.js +27 -4
- package/dist/commands/login.js +4 -1
- package/dist/commands/machines/list.d.ts +1 -0
- package/dist/commands/machines/list.js +33 -5
- package/dist/commands/memory/approvals/issue.d.ts +18 -0
- package/dist/commands/memory/approvals/issue.js +63 -0
- package/dist/commands/memory/commit.js +5 -2
- package/dist/commands/memory/context.d.ts +3 -2
- package/dist/commands/memory/context.js +9 -11
- package/dist/commands/memory/delete.d.ts +1 -1
- package/dist/commands/memory/list.d.ts +1 -1
- package/dist/commands/memory/list.js +1 -1
- package/dist/commands/memory/mounts/resolve.d.ts +10 -0
- package/dist/commands/memory/mounts/resolve.js +27 -0
- package/dist/commands/memory/proposals/commit.d.ts +18 -0
- package/dist/commands/memory/proposals/commit.js +32 -0
- package/dist/commands/memory/proposals/create.d.ts +18 -0
- package/dist/commands/memory/proposals/create.js +56 -0
- package/dist/commands/memory/proposals/list.d.ts +13 -0
- package/dist/commands/memory/proposals/list.js +31 -0
- package/dist/commands/memory/proposals/promote.d.ts +22 -0
- package/dist/commands/memory/proposals/promote.js +33 -0
- package/dist/commands/memory/proposals/reject.d.ts +16 -0
- package/dist/commands/memory/proposals/reject.js +28 -0
- package/dist/commands/memory/rebuild-index.d.ts +20 -0
- package/dist/commands/memory/rebuild-index.js +25 -0
- package/dist/commands/memory/save.d.ts +1 -1
- package/dist/commands/memory/save.js +1 -1
- package/dist/commands/memory/search.d.ts +6 -3
- package/dist/commands/memory/search.js +27 -3
- package/dist/commands/memory/show.d.ts +1 -1
- package/dist/commands/memory/stores/archive.d.ts +14 -0
- package/dist/commands/memory/stores/archive.js +31 -0
- package/dist/commands/memory/stores/create.d.ts +12 -0
- package/dist/commands/memory/stores/create.js +30 -0
- package/dist/commands/memory/stores/delete.d.ts +15 -0
- package/dist/commands/memory/stores/delete.js +38 -0
- package/dist/commands/memory/stores/list.d.ts +10 -0
- package/dist/commands/memory/stores/list.js +29 -0
- package/dist/commands/memory/stores/show.d.ts +12 -0
- package/dist/commands/memory/stores/show.js +14 -0
- package/dist/commands/memory/stores/update.d.ts +15 -0
- package/dist/commands/memory/stores/update.js +39 -0
- package/dist/commands/memory/sync/delete.d.ts +13 -0
- package/dist/commands/memory/sync/delete.js +35 -0
- package/dist/commands/memory/sync/pull.d.ts +19 -0
- package/dist/commands/memory/sync/pull.js +52 -0
- package/dist/commands/memory/sync/push.d.ts +18 -0
- package/dist/commands/memory/sync/push.js +115 -0
- package/dist/commands/memory/sync/resolve.d.ts +23 -0
- package/dist/commands/memory/sync/resolve.js +27 -0
- package/dist/commands/memory/sync/status.d.ts +9 -0
- package/dist/commands/memory/sync/status.js +22 -0
- package/dist/commands/memory/topics/archive.d.ts +20 -0
- package/dist/commands/memory/topics/archive.js +29 -0
- package/dist/commands/memory/topics/create.d.ts +23 -0
- package/dist/commands/memory/topics/create.js +46 -0
- package/dist/commands/memory/topics/delete.d.ts +22 -0
- package/dist/commands/memory/topics/delete.js +30 -0
- package/dist/commands/memory/topics/forget.d.ts +22 -0
- package/dist/commands/memory/topics/forget.js +29 -0
- package/dist/commands/memory/topics/list.d.ts +13 -0
- package/dist/commands/memory/topics/list.js +22 -0
- package/dist/commands/memory/topics/move.d.ts +22 -0
- package/dist/commands/memory/topics/move.js +26 -0
- package/dist/commands/memory/topics/show.d.ts +17 -0
- package/dist/commands/memory/topics/show.js +21 -0
- package/dist/commands/memory/topics/update.d.ts +23 -0
- package/dist/commands/memory/topics/update.js +27 -0
- package/dist/commands/memory/tree.d.ts +5 -2
- package/dist/commands/memory/tree.js +20 -2
- package/dist/commands/memory/update.d.ts +1 -1
- package/dist/commands/memory/versions/list.d.ts +16 -0
- package/dist/commands/memory/versions/list.js +22 -0
- package/dist/commands/memory/versions/redact.d.ts +21 -0
- package/dist/commands/memory/versions/redact.js +22 -0
- package/dist/commands/memory/versions/restore.d.ts +20 -0
- package/dist/commands/memory/versions/restore.js +16 -0
- package/dist/commands/memory/versions/show.d.ts +15 -0
- package/dist/commands/memory/versions/show.js +17 -0
- package/dist/commands/messages/list.js +25 -8
- package/dist/commands/messages/show.js +10 -0
- package/dist/commands/store/install.js +5 -1
- package/dist/commands/store/releases.js +1 -1
- package/dist/commands/store/update.js +5 -1
- package/dist/commands/subscriptions/cancel.js +5 -1
- package/dist/commands/subscriptions/health.js +5 -1
- package/dist/commands/subscriptions/status.js +5 -1
- package/dist/commands/subscriptions/subscribe.js +5 -1
- package/dist/commands/tasks/activity.d.ts +2 -3
- package/dist/commands/tasks/activity.js +15 -5
- package/dist/commands/tasks/complete.d.ts +18 -150
- package/dist/commands/tasks/complete.js +161 -600
- package/dist/commands/tasks/create.d.ts +5 -0
- package/dist/commands/tasks/create.js +107 -4
- package/dist/commands/tasks/deliverable/add.d.ts +8 -0
- package/dist/commands/tasks/deliverable/add.js +41 -8
- package/dist/commands/tasks/deliverable/confirm.d.ts +5 -5
- package/dist/commands/tasks/deliverable/confirm.js +9 -18
- package/dist/commands/tasks/deliverable/list.d.ts +12 -0
- package/dist/commands/tasks/deliverable/list.js +37 -0
- package/dist/commands/tasks/events/append.d.ts +2 -0
- package/dist/commands/tasks/events/append.js +11 -4
- package/dist/commands/tasks/events/list.d.ts +1 -0
- package/dist/commands/tasks/events/list.js +1 -0
- package/dist/commands/tasks/events/tail.d.ts +1 -0
- package/dist/commands/tasks/events/tail.js +1 -0
- package/dist/commands/tasks/expectations/assess.d.ts +21 -0
- package/dist/commands/tasks/expectations/assess.js +77 -0
- package/dist/commands/tasks/expectations.js +14 -18
- package/dist/commands/tasks/judge.d.ts +1 -0
- package/dist/commands/tasks/judge.js +2 -1
- package/dist/commands/tasks/output.d.ts +2 -11
- package/dist/commands/tasks/output.js +64 -145
- package/dist/commands/tasks/report.d.ts +3 -3
- package/dist/commands/tasks/report.js +64 -41
- package/dist/commands/tasks/resource/add.d.ts +23 -0
- package/dist/commands/tasks/resource/add.js +76 -0
- package/dist/commands/tasks/resource/list.d.ts +13 -0
- package/dist/commands/tasks/resource/list.js +41 -0
- package/dist/commands/tasks/result/show.d.ts +13 -0
- package/dist/commands/tasks/result/show.js +46 -0
- package/dist/commands/tasks/result/submit.d.ts +26 -0
- package/dist/commands/tasks/result/submit.js +134 -0
- package/dist/commands/tasks/resume/save.d.ts +16 -0
- package/dist/commands/tasks/resume/save.js +59 -0
- package/dist/commands/tasks/resume/show.d.ts +13 -0
- package/dist/commands/tasks/resume/show.js +35 -0
- package/dist/commands/tasks/review.d.ts +17 -0
- package/dist/commands/tasks/review.js +54 -0
- package/dist/commands/tasks/runs.js +5 -0
- package/dist/commands/tasks/timeline.d.ts +2 -2
- package/dist/commands/tasks/timeline.js +10 -23
- package/dist/commands/tasks/updates/add.d.ts +22 -0
- package/dist/commands/tasks/updates/add.js +101 -0
- package/dist/commands/tasks/updates/list.d.ts +15 -0
- package/dist/commands/tasks/updates/list.js +57 -0
- package/dist/commands/tasks/waive.d.ts +1 -0
- package/dist/commands/tasks/waive.js +2 -1
- package/dist/lib/agentic-stream.d.ts +257 -0
- package/dist/lib/agentic-stream.js +490 -33
- package/dist/lib/balance.d.ts +8 -0
- package/dist/lib/commitment-product.d.ts +10 -1
- package/dist/lib/commitment-product.js +66 -2
- package/dist/lib/commitments.d.ts +2 -1
- package/dist/lib/commitments.js +1 -0
- package/dist/lib/conversation-search.d.ts +3 -0
- package/dist/lib/conversation-search.js +75 -0
- package/dist/lib/execution-target.d.ts +21 -0
- package/dist/lib/execution-target.js +22 -6
- package/dist/lib/harnesses.d.ts +82 -6
- package/dist/lib/harnesses.js +30 -2
- package/dist/lib/hosted-machines.js +9 -0
- package/dist/lib/inbox.d.ts +2 -1
- package/dist/lib/machines.d.ts +17 -0
- package/dist/lib/machines.js +29 -0
- package/dist/lib/managed-agent-keys.d.ts +104 -0
- package/dist/lib/managed-agent-keys.js +50 -0
- package/dist/lib/memory-scope.d.ts +4 -3
- package/dist/lib/memory-scope.js +9 -9
- package/dist/lib/memory-sync.d.ts +114 -0
- package/dist/lib/memory-sync.js +730 -0
- package/dist/lib/message-provenance.d.ts +42 -0
- package/dist/lib/message-provenance.js +89 -0
- package/dist/lib/scoped-memory-command.d.ts +68 -0
- package/dist/lib/scoped-memory-command.js +183 -0
- package/dist/lib/session-task-endpoints.d.ts +1 -1
- package/dist/lib/session-task-endpoints.js +7 -0
- package/dist/lib/sky-code-broker.d.ts +47 -0
- package/dist/lib/sky-code-broker.js +50 -0
- package/dist/lib/sky-code-doctor.js +10 -1
- package/dist/lib/task-artifact-flags.d.ts +2 -0
- package/dist/lib/task-artifact-flags.js +7 -0
- package/dist/lib/task-execution.d.ts +4 -5
- package/dist/lib/task-execution.js +4 -5
- package/dist/lib/task-extras.d.ts +1 -5
- package/dist/lib/task-extras.js +1 -5
- package/dist/lib/task-transcript.d.ts +5 -7
- package/dist/lib/task-transcript.js +8 -16
- package/dist/lib/tasks.d.ts +7 -21
- package/dist/lib/tasks.js +17 -20
- package/dist/lib/triggers.d.ts +39 -1
- package/dist/lib/triggers.js +70 -2
- package/dist/lib/usage-discovery.d.ts +17 -1
- package/dist/lib/usage-discovery.js +17 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +3733 -3618
- package/oclif.manifest.json +16201 -10377
- package/package.json +26 -5
|
@@ -1,294 +1,97 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.resolveCanonicalCompletionTaskId = resolveCanonicalCompletionTaskId;
|
|
5
|
-
exports.resolveCompletionTarget = resolveCompletionTarget;
|
|
6
|
-
exports.decideRecompletion = decideRecompletion;
|
|
7
|
-
exports.isCompleteEndpointMissing = isCompleteEndpointMissing;
|
|
8
|
-
exports.completeTaskDurably = completeTaskDurably;
|
|
3
|
+
exports.describePendingReview = describePendingReview;
|
|
9
4
|
const core_1 = require("@oclif/core");
|
|
10
|
-
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
11
5
|
const base_command_1 = require("../../base-command");
|
|
12
|
-
const task_extras_1 = require("../../lib/task-extras");
|
|
13
6
|
const api_fetch_1 = require("../../lib/api-fetch");
|
|
14
7
|
const outbox_1 = require("../../lib/outbox");
|
|
15
|
-
const credential_resolver_1 = require("../../lib/credential-resolver");
|
|
16
|
-
const delegated_cli_1 = require("../../lib/delegated-cli");
|
|
17
8
|
const prompt_1 = require("../../lib/prompt");
|
|
18
|
-
const
|
|
9
|
+
const session_task_endpoints_1 = require("../../lib/session-task-endpoints");
|
|
19
10
|
const task_resolver_1 = require("../../lib/task-resolver");
|
|
20
|
-
const
|
|
21
|
-
const task_transcript_1 = require("../../lib/task-transcript");
|
|
11
|
+
const task_extras_1 = require("../../lib/task-extras");
|
|
22
12
|
const web_url_1 = require("../../lib/web-url");
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
* not get it meets the gate's refusal, which names the command that writes it.
|
|
32
|
-
*
|
|
33
|
-
* `local`, never anything stronger — this is the executor talking about its own
|
|
34
|
-
* work, which is exactly what the authority ladder is built to weigh.
|
|
35
|
-
*/
|
|
36
|
-
async function recordCoordinateDeliverables({ taskId, values, warn, }) {
|
|
37
|
-
const seen = new Set();
|
|
38
|
-
for (const value of values) {
|
|
39
|
-
const raw = (value ?? '').trim();
|
|
40
|
-
if (!raw || raw === 'none' || seen.has(raw)) {
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
seen.add(raw);
|
|
44
|
-
const parsed = (0, tasks_1.parseDeliverableCoordinate)(raw);
|
|
45
|
-
if (!parsed.ok) {
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
try {
|
|
49
|
-
await data_provider_1.dataService.addTaskDeliverable(taskId, parsed.value);
|
|
50
|
-
}
|
|
51
|
-
catch {
|
|
52
|
-
warn(`Recorded "${raw}" in the report but not in the deliverables ledger. ` +
|
|
53
|
-
`If the task refuses to close, run \`tasks deliverable add\` for it.`);
|
|
54
|
-
}
|
|
13
|
+
function parseJson(value, label) {
|
|
14
|
+
if (!value)
|
|
15
|
+
return undefined;
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(value);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
throw new Error(`${label} must be valid JSON`);
|
|
55
21
|
}
|
|
56
22
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
* old server and fall through to the legacy comment-first path.
|
|
62
|
-
*/
|
|
63
|
-
async function resolveCanonicalCompletionTaskId(taskRef, lookup) {
|
|
64
|
-
return (await resolveCompletionTarget(taskRef, lookup)).id;
|
|
65
|
-
}
|
|
66
|
-
/** Terminal workflow states — a task in one of these has already been closed out. */
|
|
67
|
-
const TERMINAL_STATUSES = new Set(['done', 'failed']);
|
|
68
|
-
/**
|
|
69
|
-
* The canonical id AND the status it is in.
|
|
70
|
-
*
|
|
71
|
-
* The status comes free: this lookup already happens on the user path, so the
|
|
72
|
-
* re-completion guard below costs no extra round trip on the common case. It is
|
|
73
|
-
* also the only way to make the success line honest — `tasks complete` printed
|
|
74
|
-
* "Moved task X to done" unconditionally, so a task that was ALREADY done got a
|
|
75
|
-
* report of a state transition that did not happen.
|
|
76
|
-
*/
|
|
77
|
-
async function resolveCompletionTarget(taskRef, lookup) {
|
|
78
|
-
const task = (await lookup(taskRef));
|
|
79
|
-
const id = typeof task?.id === 'string' ? task.id.trim() : '';
|
|
80
|
-
if (!id) {
|
|
81
|
-
throw new Error(`Task ${taskRef} did not resolve to a canonical id.`);
|
|
23
|
+
function parseReference(raw) {
|
|
24
|
+
const split = raw.indexOf(':');
|
|
25
|
+
if (split < 1 || split === raw.length - 1) {
|
|
26
|
+
throw new Error('Evidence must use <kind>:<id-or-uri>.');
|
|
82
27
|
}
|
|
83
|
-
|
|
28
|
+
const kind = raw.slice(0, split);
|
|
29
|
+
const value = raw.slice(split + 1);
|
|
30
|
+
return /^[a-z][a-z0-9+.-]*:/i.test(value) || value.startsWith('/')
|
|
31
|
+
? { kind, uri: value }
|
|
32
|
+
: { kind, id: value };
|
|
84
33
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
* TASK_OUTPUT block, and leave `tasks output` rendering the throwaway one as
|
|
91
|
-
* authoritative. The real summary and its deliverables link were still on the task
|
|
92
|
-
* but no longer canonical, which is the worst of the available failures: no
|
|
93
|
-
* error, no warning, exit 0, and a record that now says something different.
|
|
94
|
-
*
|
|
95
|
-
* Refusing outright would break the case that makes retries safe. A completion
|
|
96
|
-
* whose response is lost — a flaky connection, a re-run script — SHOULD be able
|
|
97
|
-
* to run again, and today it collapses server-side on the shared
|
|
98
|
-
* `X-Idempotency-Key`. Turning that into a hard failure would force every
|
|
99
|
-
* caller to write its own is-it-already-done check.
|
|
100
|
-
*
|
|
101
|
-
* So the axis is not "again or not", it is "the SAME again, or something
|
|
102
|
-
* different":
|
|
103
|
-
*
|
|
104
|
-
* - identical report → `noop`, exit 0. Nothing to do, and saying so is more
|
|
105
|
-
* honest than re-posting an identical block.
|
|
106
|
-
* - differing report → `refuse`. Superseding a canonical outcome is a real
|
|
107
|
-
* decision, so it takes `--amend` and says so.
|
|
108
|
-
* - not yet terminal → `proceed`, unchanged.
|
|
109
|
-
*
|
|
110
|
-
* Pure, so the rule is testable without a server.
|
|
111
|
-
*/
|
|
112
|
-
function decideRecompletion(opts) {
|
|
113
|
-
const { priorStatus, canonicalBody, incomingBody, amend } = opts;
|
|
114
|
-
if (!priorStatus || !TERMINAL_STATUSES.has(priorStatus))
|
|
115
|
-
return 'proceed';
|
|
116
|
-
if (amend)
|
|
117
|
-
return 'proceed';
|
|
118
|
-
// No canonical block to disagree with — a task closed by a path that skipped
|
|
119
|
-
// it, so writing one is an addition rather than a replacement.
|
|
120
|
-
if (canonicalBody === null)
|
|
121
|
-
return 'proceed';
|
|
122
|
-
return canonicalBody.trim() === incomingBody.trim() ? 'noop' : 'refuse';
|
|
34
|
+
function stringValue(value, field) {
|
|
35
|
+
const text = typeof value === 'string' ? value.trim() : '';
|
|
36
|
+
if (!text)
|
|
37
|
+
throw new Error(`${field} is required.`);
|
|
38
|
+
return text;
|
|
123
39
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
return err instanceof api_fetch_1.ApiFetchError && err.status === 404;
|
|
40
|
+
function optionalString(value) {
|
|
41
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
127
42
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
*
|
|
131
|
-
* The session-scoped completion endpoint derives its task from the run claim
|
|
132
|
-
* and deliberately ignores any client-supplied id — that is what stops a
|
|
133
|
-
* compromised agent process finishing a sibling task. The corollary is that the
|
|
134
|
-
* id resolved on this side is not necessarily the one that moved, so reporting
|
|
135
|
-
* ours would print a success line (and a `--json` `taskId`) for a task that is
|
|
136
|
-
* still open.
|
|
137
|
-
*/
|
|
138
|
-
class TaskCompletionMismatchError extends Error {
|
|
139
|
-
requestedTaskId;
|
|
140
|
-
completedTaskId;
|
|
141
|
-
constructor(requestedTaskId, completedTaskId) {
|
|
142
|
-
super(`Asked to complete ${requestedTaskId}, but the server finalized ${completedTaskId}. ` +
|
|
143
|
-
'In an autonomous session the server derives the task from the run claim and ignores ' +
|
|
144
|
-
`the id passed here, so ${requestedTaskId} has NOT been completed. Re-run without a ` +
|
|
145
|
-
"task id to finalize this session's own task.");
|
|
146
|
-
this.name = 'TaskCompletionMismatchError';
|
|
147
|
-
this.requestedTaskId = requestedTaskId;
|
|
148
|
-
this.completedTaskId = completedTaskId;
|
|
149
|
-
}
|
|
43
|
+
function stringArray(value) {
|
|
44
|
+
return Array.isArray(value) ? value.map((item) => String(item).trim()).filter(Boolean) : [];
|
|
150
45
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
* the TASK_OUTPUT comment — no half-finished state if this process dies).
|
|
157
|
-
* Falls back to the legacy client-side two-step (comment, then move) when the
|
|
158
|
-
* server predates the endpoint (HTTP 404). `noMove` keeps the historical
|
|
159
|
-
* comment-only behavior and never touches the endpoint. All transport errors
|
|
160
|
-
* other than the endpoint-missing 404 are rethrown for the caller's uniform
|
|
161
|
-
* error handling.
|
|
162
|
-
*/
|
|
163
|
-
async function completeTaskDurably(opts) {
|
|
164
|
-
const { taskId, status, body, summary, deliverables, verdict, report, deliveryReceipt, noMove, transport, } = opts;
|
|
165
|
-
if (noMove) {
|
|
166
|
-
const comment = await transport.addComment(taskId, body);
|
|
167
|
-
return {
|
|
168
|
-
comment,
|
|
169
|
-
movedTask: null,
|
|
170
|
-
moved: false,
|
|
171
|
-
commentFailed: false,
|
|
172
|
-
usedAtomicEndpoint: false,
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
try {
|
|
176
|
-
const response = await transport.postComplete(taskId, {
|
|
177
|
-
status,
|
|
178
|
-
summary,
|
|
179
|
-
deliverables,
|
|
180
|
-
verdict,
|
|
181
|
-
...(report ? { report } : {}),
|
|
182
|
-
...(deliveryReceipt ? { deliveryReceipt } : {}),
|
|
183
|
-
});
|
|
184
|
-
// Reconcile before reporting. On the session-scoped path the server owns
|
|
185
|
-
// the task selection, so a divergence here means the task we were asked to
|
|
186
|
-
// finish is still open — a silent success would hide that from the agent
|
|
187
|
-
// and from anything parsing `--json`.
|
|
188
|
-
const finalizedTaskId = response.task?.id;
|
|
189
|
-
if (finalizedTaskId && finalizedTaskId !== taskId) {
|
|
190
|
-
throw new TaskCompletionMismatchError(taskId, finalizedTaskId);
|
|
191
|
-
}
|
|
192
|
-
return {
|
|
193
|
-
comment: response.comment ?? null,
|
|
194
|
-
movedTask: response.task ?? null,
|
|
195
|
-
moved: true,
|
|
196
|
-
commentFailed: response.commentFailed === true,
|
|
197
|
-
...(response.advisories?.length ? { advisories: response.advisories } : {}),
|
|
198
|
-
usedAtomicEndpoint: true,
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
catch (err) {
|
|
202
|
-
if (err instanceof TaskCompletionMismatchError)
|
|
203
|
-
throw err;
|
|
204
|
-
if (!isCompleteEndpointMissing(err) || transport.allowLegacyFallback === false) {
|
|
205
|
-
throw err;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
// Old server — legacy non-atomic two-step, byte-identical to the historical
|
|
209
|
-
// behavior (comment first, then move).
|
|
210
|
-
const comment = await transport.addComment(taskId, body);
|
|
211
|
-
const movedTask = await transport.moveTask(taskId, status === 'completed' ? 'done' : 'failed');
|
|
212
|
-
return {
|
|
213
|
-
comment,
|
|
214
|
-
movedTask,
|
|
215
|
-
moved: true,
|
|
216
|
-
commentFailed: false,
|
|
217
|
-
usedAtomicEndpoint: false,
|
|
218
|
-
};
|
|
46
|
+
function describePendingReview({ statusType, taskId, resultId, bin, }) {
|
|
47
|
+
if (statusType !== 'review')
|
|
48
|
+
return null;
|
|
49
|
+
return ('The Result was submitted, but the Task is not done yet. An independent reviewer must ' +
|
|
50
|
+
`accept this exact Result with: ${bin} tasks review ${taskId} --result-id ${resultId || '<result-id>'} --decision accepted`);
|
|
219
51
|
}
|
|
220
52
|
class TasksComplete extends base_command_1.BaseCommand {
|
|
221
|
-
static description = '
|
|
53
|
+
static description = 'Submit one versioned Result and advance the Task workflow';
|
|
222
54
|
static examples = [
|
|
223
|
-
'<%= config.bin %> tasks complete <task
|
|
224
|
-
'<%= config.bin %> tasks complete --summary "
|
|
225
|
-
'<%= config.bin %> tasks complete
|
|
226
|
-
'<%= config.bin %> tasks complete <task-id> --status failed --summary "Could not deploy." --verdict blocked',
|
|
227
|
-
'<%= config.bin %> tasks complete <task-id> --from-json task-output.json --json',
|
|
228
|
-
'<%= config.bin %> tasks complete <task-id> --summary "Comment only" --no-move',
|
|
55
|
+
'<%= config.bin %> tasks complete <task> --summary "Implemented and verified the fix" --deliverable <resource-link-id>',
|
|
56
|
+
'<%= config.bin %> tasks complete <task> --summary "Do not ship" --conclusion-type release_recommendation --conclusion-json \'{"posture":"no_ship"}\'',
|
|
57
|
+
'<%= config.bin %> tasks complete <task> --status blocked --summary "Waiting for production credentials" --remaining "A workspace owner must grant access"',
|
|
229
58
|
];
|
|
230
59
|
static args = {
|
|
231
60
|
id: core_1.Args.string({
|
|
232
|
-
description: 'Task
|
|
61
|
+
description: 'Task id or ref; defaults to the bound runtime Task',
|
|
233
62
|
required: false,
|
|
234
63
|
ignoreStdin: true,
|
|
235
64
|
}),
|
|
236
65
|
};
|
|
237
66
|
static flags = {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}),
|
|
242
|
-
'
|
|
243
|
-
description: '
|
|
244
|
-
}),
|
|
245
|
-
amend: core_1.Flags.boolean({
|
|
246
|
-
description: 'Supersede the canonical outcome of an already-completed task. Without it, a differing report on a closed task is refused',
|
|
247
|
-
}),
|
|
248
|
-
status: core_1.Flags.string({
|
|
249
|
-
description: 'TASK_OUTPUT status',
|
|
250
|
-
options: ['completed', 'failed'],
|
|
251
|
-
default: 'completed',
|
|
67
|
+
task: core_1.Flags.string({ description: 'Task id; overrides runtime context' }),
|
|
68
|
+
status: core_1.Flags.string({ options: ['completed', 'failed', 'blocked'] }),
|
|
69
|
+
summary: core_1.Flags.string({ description: 'The Result in one readable paragraph' }),
|
|
70
|
+
body: core_1.Flags.string({ description: 'Long-form Result body' }),
|
|
71
|
+
'conclusion-type': core_1.Flags.string({
|
|
72
|
+
description: 'Domain conclusion type, such as release_recommendation',
|
|
252
73
|
}),
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
'characters — link to commits, PRs, or file paths rather than pasting content.',
|
|
258
|
-
// Repeatable, even though `--deliverable` already is. The two spellings sit
|
|
259
|
-
// one letter apart and both read as "the deliverables go here", so the wrong
|
|
260
|
-
// one gets typed — and oclif answered that with "Flag --deliverables can only
|
|
261
|
-
// be specified once", which names the flag you used and not the sibling
|
|
262
|
-
// that does what you wanted. Dogfooded (OSK-266) by losing a completion
|
|
263
|
-
// call to exactly that, then re-running with the two references pre-joined
|
|
264
|
-
// into one string, which is the workaround rather than the shape.
|
|
265
|
-
multiple: true,
|
|
266
|
-
default: ['none'],
|
|
74
|
+
'conclusion-json': core_1.Flags.string({ description: 'JSON value for the domain conclusion' }),
|
|
75
|
+
'structured-schema': core_1.Flags.string({ description: 'Schema name for --structured-json' }),
|
|
76
|
+
'structured-json': core_1.Flags.string({
|
|
77
|
+
description: 'Task-type-specific structured Result JSON object',
|
|
267
78
|
}),
|
|
268
79
|
deliverable: core_1.Flags.string({
|
|
269
|
-
description: 'A single artifact reference (repeatable); joined with commas. Prefer this ' +
|
|
270
|
-
'over pre-joining into --deliverables.',
|
|
271
|
-
multiple: true,
|
|
272
|
-
}),
|
|
273
|
-
verdict: core_1.Flags.string({ description: 'Final verdict', default: 'approved' }),
|
|
274
|
-
outcome: core_1.Flags.string({
|
|
275
|
-
description: 'Reader-facing conclusion for the durable task report',
|
|
276
|
-
}),
|
|
277
|
-
finding: core_1.Flags.string({
|
|
278
|
-
description: 'Concrete finding for the durable task report (repeatable)',
|
|
279
|
-
multiple: true,
|
|
280
|
-
}),
|
|
281
|
-
evidence: core_1.Flags.string({
|
|
282
|
-
description: 'Supporting rationale or evidence for the durable task report (repeatable)',
|
|
283
80
|
multiple: true,
|
|
81
|
+
description: 'Exact Task Resource link id produced by `tasks deliverable add`',
|
|
284
82
|
}),
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
83
|
+
evidence: core_1.Flags.string({ multiple: true, description: 'Evidence as <kind>:<id-or-uri>' }),
|
|
84
|
+
remaining: core_1.Flags.string({ multiple: true, description: 'Remaining work item' }),
|
|
85
|
+
'source-run': core_1.Flags.string({ multiple: true, description: 'Contributing Task Run id' }),
|
|
86
|
+
'run-id': core_1.Flags.string({ description: 'Expected current Task Run id' }),
|
|
87
|
+
'spec-version': core_1.Flags.integer({
|
|
88
|
+
description: 'Task specification version this Result answers',
|
|
288
89
|
}),
|
|
289
|
-
'
|
|
290
|
-
|
|
90
|
+
'idempotency-key': core_1.Flags.string({ description: 'Stable retry key for this exact closeout' }),
|
|
91
|
+
'from-json': core_1.Flags.string({
|
|
92
|
+
description: 'JSON file containing the same canonical Result fields; "-" for stdin',
|
|
291
93
|
}),
|
|
94
|
+
json: core_1.Flags.boolean({ description: 'Output JSON' }),
|
|
292
95
|
};
|
|
293
96
|
async run() {
|
|
294
97
|
this.requireAuth();
|
|
@@ -298,51 +101,26 @@ class TasksComplete extends base_command_1.BaseCommand {
|
|
|
298
101
|
try {
|
|
299
102
|
jsonBody = await (0, prompt_1.readJsonBody)(flags['from-json']);
|
|
300
103
|
}
|
|
301
|
-
catch (
|
|
104
|
+
catch (error) {
|
|
302
105
|
this.failWithCliError({
|
|
303
|
-
message:
|
|
106
|
+
message: error.message,
|
|
304
107
|
code: 'INVALID_JSON_INPUT',
|
|
305
108
|
exit: 1,
|
|
306
109
|
retryable: false,
|
|
307
110
|
});
|
|
308
111
|
}
|
|
309
112
|
}
|
|
310
|
-
|
|
311
|
-
status: flags.status,
|
|
312
|
-
summary: flags.summary,
|
|
313
|
-
// Both spellings, pooled, then normalized to the one-line form the server
|
|
314
|
-
// stores. Pooling rather than letting one win: someone who passes both has
|
|
315
|
-
// named deliverables in each, and silently dropping half of them is worse
|
|
316
|
-
// than either flag being unavailable.
|
|
317
|
-
deliverables: (0, data_provider_1.normalizeTaskOutputDeliverables)([
|
|
318
|
-
...(flags.deliverable ?? []),
|
|
319
|
-
// The literal default is dropped when it is only the default; a user
|
|
320
|
-
// who typed `--deliverables none` alongside a real one meant the real one.
|
|
321
|
-
...(flags.deliverables ?? []).filter((value) => value !== 'none'),
|
|
322
|
-
]) || 'none',
|
|
323
|
-
verdict: flags.verdict,
|
|
324
|
-
});
|
|
325
|
-
// `--verdict` is deliberately not enumerated — a reviewer task passes a
|
|
326
|
-
// JSON posture object, and `normalizeReviewPosture` accepts synonyms. But
|
|
327
|
-
// when it recognises nothing it derives the posture from the criteria
|
|
328
|
-
// rather than failing, so a typo is recorded and silently means something
|
|
329
|
-
// else. Warn over the set the server actually matches; never refuse.
|
|
330
|
-
if (!(0, tasks_1.verdictIsRecognized)(input.verdict)) {
|
|
331
|
-
this.warn(`Verdict "${input.verdict}" is not one the server recognises ` +
|
|
332
|
-
`(${tasks_1.RECOGNIZED_TASK_VERDICTS.join(', ')}, or a JSON review posture). ` +
|
|
333
|
-
'It will be recorded as written, and a review will fall back to what the criteria imply.');
|
|
334
|
-
}
|
|
335
|
-
let taskIdentity;
|
|
113
|
+
let identity;
|
|
336
114
|
try {
|
|
337
|
-
|
|
115
|
+
identity = (0, task_resolver_1.resolveTaskIdentity)({
|
|
338
116
|
positionalId: args.id,
|
|
339
117
|
flagTask: flags.task,
|
|
340
118
|
jsonBody,
|
|
341
119
|
jsonKeys: ['taskId', 'task', 'id'],
|
|
342
120
|
});
|
|
343
121
|
}
|
|
344
|
-
catch (
|
|
345
|
-
const failure = (0, task_resolver_1.taskResolutionFailure)(
|
|
122
|
+
catch (error) {
|
|
123
|
+
const failure = (0, task_resolver_1.taskResolutionFailure)(error);
|
|
346
124
|
this.failWithCliError({
|
|
347
125
|
message: failure.message,
|
|
348
126
|
code: failure.code,
|
|
@@ -351,348 +129,131 @@ class TasksComplete extends base_command_1.BaseCommand {
|
|
|
351
129
|
details: { suggestion: failure.suggestion, context: failure.details },
|
|
352
130
|
});
|
|
353
131
|
}
|
|
354
|
-
let
|
|
355
|
-
let
|
|
356
|
-
let deliverables;
|
|
357
|
-
let verdict;
|
|
358
|
-
let report;
|
|
132
|
+
let conclusionValue;
|
|
133
|
+
let structuredValue;
|
|
359
134
|
try {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
deliverables =
|
|
363
|
-
optionalOutputField((0, data_provider_1.normalizeTaskOutputDeliverables)(input.deliverables)) || 'none';
|
|
364
|
-
verdict =
|
|
365
|
-
optionalOutputField(input.verdict) || (status === 'completed' ? 'approved' : 'failed');
|
|
366
|
-
const jsonReport = input.report && typeof input.report === 'object'
|
|
367
|
-
? input.report
|
|
368
|
-
: undefined;
|
|
369
|
-
const reportInput = {
|
|
370
|
-
outcome: flags.outcome ?? jsonReport?.outcome,
|
|
371
|
-
findings: flags.finding?.length ? flags.finding : jsonReport?.findings,
|
|
372
|
-
evidence: flags.evidence?.length ? flags.evidence : jsonReport?.evidence,
|
|
373
|
-
nextActions: flags.next?.length
|
|
374
|
-
? flags.next
|
|
375
|
-
: (jsonReport?.nextActions ?? jsonReport?.next),
|
|
376
|
-
};
|
|
377
|
-
if ((0, task_report_1.hasTaskReportInput)(reportInput)) {
|
|
378
|
-
// Summary remains the compact completion acknowledgement. A report's
|
|
379
|
-
// outcome is the readable conclusion in the task detail, so default it
|
|
380
|
-
// to summary when a caller supplied findings but no duplicate outcome.
|
|
381
|
-
report = (0, task_report_1.buildTaskReport)({ ...reportInput, outcome: reportInput.outcome ?? summary });
|
|
382
|
-
}
|
|
135
|
+
conclusionValue = parseJson(flags['conclusion-json'], '--conclusion-json');
|
|
136
|
+
structuredValue = parseJson(flags['structured-json'], '--structured-json');
|
|
383
137
|
}
|
|
384
|
-
catch (
|
|
138
|
+
catch (error) {
|
|
385
139
|
this.failWithCliError({
|
|
386
|
-
message:
|
|
387
|
-
code: '
|
|
140
|
+
message: error.message,
|
|
141
|
+
code: 'INVALID_RESULT_JSON',
|
|
388
142
|
exit: 1,
|
|
389
143
|
retryable: false,
|
|
390
144
|
});
|
|
391
145
|
}
|
|
392
|
-
if (
|
|
393
|
-
this.
|
|
394
|
-
|
|
395
|
-
|
|
146
|
+
if (Boolean(flags['conclusion-type']) !== Boolean(flags['conclusion-json'])) {
|
|
147
|
+
this.error('--conclusion-type and --conclusion-json must be supplied together', { exit: 1 });
|
|
148
|
+
}
|
|
149
|
+
if (Boolean(flags['structured-schema']) !== Boolean(flags['structured-json'])) {
|
|
150
|
+
this.error('--structured-schema and --structured-json must be supplied together', {
|
|
396
151
|
exit: 1,
|
|
397
|
-
retryable: false,
|
|
398
152
|
});
|
|
399
153
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
const
|
|
405
|
-
|
|
154
|
+
if (structuredValue &&
|
|
155
|
+
(typeof structuredValue !== 'object' || Array.isArray(structuredValue))) {
|
|
156
|
+
this.error('--structured-json must contain a JSON object', { exit: 1 });
|
|
157
|
+
}
|
|
158
|
+
const input = (0, prompt_1.mergeDefined)(jsonBody, {
|
|
159
|
+
status: flags.status,
|
|
160
|
+
summary: flags.summary,
|
|
161
|
+
body: flags.body,
|
|
162
|
+
conclusion: flags['conclusion-type']
|
|
163
|
+
? { type: flags['conclusion-type'], value: conclusionValue }
|
|
164
|
+
: undefined,
|
|
165
|
+
structuredContent: flags['structured-schema']
|
|
166
|
+
? {
|
|
167
|
+
schema: flags['structured-schema'],
|
|
168
|
+
value: structuredValue,
|
|
169
|
+
}
|
|
170
|
+
: undefined,
|
|
171
|
+
resourceLinkIds: flags.deliverable,
|
|
172
|
+
evidenceRefs: flags.evidence?.map(parseReference),
|
|
173
|
+
remainingWork: flags.remaining?.map((summary) => ({ summary })),
|
|
174
|
+
sourceRunIds: flags['source-run'],
|
|
175
|
+
expectedRunId: flags['run-id'],
|
|
176
|
+
expectedSpecVersion: flags['spec-version'],
|
|
177
|
+
idempotencyKey: flags['idempotency-key'],
|
|
178
|
+
});
|
|
179
|
+
let status;
|
|
180
|
+
let summary;
|
|
181
|
+
try {
|
|
182
|
+
const candidate = optionalString(input.status) || 'completed';
|
|
183
|
+
if (!['completed', 'failed', 'blocked'].includes(candidate)) {
|
|
184
|
+
throw new Error('status must be completed, failed, or blocked.');
|
|
185
|
+
}
|
|
186
|
+
status = candidate;
|
|
187
|
+
summary = stringValue(input.summary, 'summary');
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
406
190
|
this.failWithCliError({
|
|
407
|
-
message:
|
|
408
|
-
code: '
|
|
191
|
+
message: error.message,
|
|
192
|
+
code: 'INVALID_TASK_RESULT',
|
|
409
193
|
exit: 1,
|
|
410
194
|
retryable: false,
|
|
411
|
-
details: {
|
|
412
|
-
limit: budget.limit,
|
|
413
|
-
length: budget.length,
|
|
414
|
-
overBy: budget.overBy,
|
|
415
|
-
fieldLengths: budget.fieldLengths,
|
|
416
|
-
},
|
|
417
195
|
});
|
|
418
196
|
}
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
// both verifies remote-default ancestry and anchors a commit that has no
|
|
422
|
-
// local branch before the completion request can release this run.
|
|
423
|
-
const deliveryReceipt = status === 'completed' && !flags['no-move']
|
|
424
|
-
? ((0, git_task_1.inspectTaskDeliveryArtifacts)({ deliverables, taskRef: taskIdentity.id }) ?? undefined)
|
|
425
|
-
: undefined;
|
|
426
|
-
// Computed once, OUTSIDE the try, so the original attempt and the queued
|
|
427
|
-
// replay carry the same key. Must match the payload the outbox stores.
|
|
428
|
-
const completionPayload = {
|
|
197
|
+
const payload = {
|
|
198
|
+
status,
|
|
429
199
|
summary,
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
200
|
+
body: optionalString(input.body),
|
|
201
|
+
conclusion: input.conclusion,
|
|
202
|
+
structuredContent: input.structuredContent,
|
|
203
|
+
resourceLinkIds: Array.isArray(input.resourceLinkIds)
|
|
204
|
+
? stringArray(input.resourceLinkIds)
|
|
205
|
+
: [],
|
|
206
|
+
evidenceRefs: Array.isArray(input.evidenceRefs) ? input.evidenceRefs : [],
|
|
207
|
+
remainingWork: Array.isArray(input.remainingWork) ? input.remainingWork : [],
|
|
208
|
+
contributors: Array.isArray(input.contributors)
|
|
209
|
+
? input.contributors
|
|
210
|
+
: [],
|
|
211
|
+
sourceRunIds: stringArray(input.sourceRunIds),
|
|
212
|
+
expectedRunId: optionalString(input.expectedRunId),
|
|
213
|
+
expectedSpecVersion: typeof input.expectedSpecVersion === 'number' ? input.expectedSpecVersion : undefined,
|
|
435
214
|
};
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
// replaying later.
|
|
442
|
-
//
|
|
443
|
-
// The session endpoint derives its task from the trusted run claim and does
|
|
444
|
-
// not need (or permit) a broad task lookup. The normal user and
|
|
445
|
-
// delegated-daemon path does: task routes read OSK refs, while mutation
|
|
446
|
-
// services key on UUIDs. Passing the ref straight through caused the atomic
|
|
447
|
-
// endpoint's task-level 404 to look like "endpoint missing"; legacy fallback
|
|
448
|
-
// then posted TASK_OUTPUT before its move 404'd.
|
|
449
|
-
let completionTaskId = taskIdentity.id;
|
|
450
|
-
// Captured at resolution so the guard and the success line can both tell
|
|
451
|
-
// "already closed" from "just closed".
|
|
452
|
-
let priorStatus = null;
|
|
453
|
-
if (!sessionCompletionPath) {
|
|
454
|
-
try {
|
|
455
|
-
const target = await resolveCompletionTarget(await this.resolveTaskReference(taskIdentity.id), (taskRef) => data_provider_1.dataService.getTask(taskRef, { enrich: false }));
|
|
456
|
-
completionTaskId = target.id;
|
|
457
|
-
priorStatus = target.status;
|
|
458
|
-
}
|
|
459
|
-
catch (err) {
|
|
460
|
-
this.handleApiError(err);
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
// A task that is already closed gets one extra read, and only then — the
|
|
464
|
-
// first completion of a task never pays for this.
|
|
465
|
-
if (decideRecompletion({
|
|
466
|
-
priorStatus,
|
|
467
|
-
canonicalBody: '',
|
|
468
|
-
incomingBody: body,
|
|
469
|
-
amend: flags.amend,
|
|
470
|
-
}) !== 'proceed') {
|
|
471
|
-
let canonicalBody = null;
|
|
215
|
+
const idempotencyKey = optionalString(input.idempotencyKey) ||
|
|
216
|
+
(0, outbox_1.idempotencyKeyFor)('task-complete', identity.id, JSON.stringify(payload));
|
|
217
|
+
const sessionPath = await (0, session_task_endpoints_1.sessionScopedTaskPath)('completion');
|
|
218
|
+
let taskId = identity.id;
|
|
219
|
+
if (!sessionPath) {
|
|
472
220
|
try {
|
|
473
|
-
|
|
221
|
+
const resolved = await this.resolveTaskReference(identity.id);
|
|
222
|
+
taskId = String(resolved);
|
|
474
223
|
}
|
|
475
|
-
catch {
|
|
476
|
-
|
|
477
|
-
// `proceed` is exactly today's behaviour, which is the safe direction
|
|
478
|
-
// for a guard whose whole job is to protect a record it cannot see.
|
|
479
|
-
canonicalBody = null;
|
|
480
|
-
}
|
|
481
|
-
const decision = decideRecompletion({
|
|
482
|
-
priorStatus,
|
|
483
|
-
canonicalBody,
|
|
484
|
-
incomingBody: body,
|
|
485
|
-
amend: flags.amend,
|
|
486
|
-
});
|
|
487
|
-
if (decision === 'noop') {
|
|
488
|
-
const url = (0, web_url_1.taskUrlFrom)(this.cliConfig.baseURL, { id: completionTaskId });
|
|
489
|
-
if (flags.json) {
|
|
490
|
-
this.log(JSON.stringify({
|
|
491
|
-
taskId: completionTaskId,
|
|
492
|
-
status: priorStatus,
|
|
493
|
-
alreadyCompleted: true,
|
|
494
|
-
changed: false,
|
|
495
|
-
url: url ? `${url}#task-outcome` : url,
|
|
496
|
-
}, null, 2));
|
|
497
|
-
return;
|
|
498
|
-
}
|
|
499
|
-
this.log(`Already ${priorStatus}; this report is identical to the canonical one — nothing to do.`);
|
|
500
|
-
if (url)
|
|
501
|
-
this.log(`Outcome: ${url}#task-outcome`);
|
|
502
|
-
return;
|
|
503
|
-
}
|
|
504
|
-
if (decision === 'refuse') {
|
|
505
|
-
this.failWithCliError({
|
|
506
|
-
message: `Task ${completionTaskId} is already ${priorStatus}, and this report differs from its canonical ` +
|
|
507
|
-
`outcome — completing again would silently replace it (deliverables included).\n` +
|
|
508
|
-
` → re-run with \`--amend\` to supersede it deliberately, or read what is there now with ` +
|
|
509
|
-
`\`${this.config.bin} tasks output ${completionTaskId}\`.`,
|
|
510
|
-
code: 'TASK_ALREADY_COMPLETED',
|
|
511
|
-
exit: 1,
|
|
512
|
-
retryable: false,
|
|
513
|
-
details: { taskId: completionTaskId, status: priorStatus },
|
|
514
|
-
});
|
|
224
|
+
catch (error) {
|
|
225
|
+
this.handleApiError(error);
|
|
515
226
|
}
|
|
516
227
|
}
|
|
517
|
-
// THE THIRD SPELLING, BRIDGED.
|
|
518
|
-
//
|
|
519
|
-
// `--deliverable` here is PROSE for the TASK_OUTPUT block. The ledger the
|
|
520
|
-
// completion gate reads is written by `tasks deliverable add`, and nothing
|
|
521
|
-
// connected the two — so an agent that ran
|
|
522
|
-
// `tasks complete --deliverable code:abc@origin/main` had, from its own
|
|
523
|
-
// point of view, recorded its deliverable, and was then refused by a gate
|
|
524
|
-
// whose message told it to record its deliverable.
|
|
525
|
-
//
|
|
526
|
-
// Three surfaces in one CLI answer to the word (`tasks deliverable add`,
|
|
527
|
-
// this flag, and `tasks report --deliverable`), and the project's own
|
|
528
|
-
// operating instructions teach the two that do NOT write the ledger. That
|
|
529
|
-
// is a documentation problem only if you believe people read the docs
|
|
530
|
-
// before the error; the cheaper fix is to make the wrong spelling do the
|
|
531
|
-
// right thing when what was typed is unambiguously a coordinate.
|
|
532
|
-
//
|
|
533
|
-
// Deliberately narrow: only values that PARSE as `<kind>:<identity>` are
|
|
534
|
-
// forwarded. A prose reference like `report.pdf` is left exactly as it was —
|
|
535
|
-
// guessing a coordinate out of prose is how the ledger fills with rows
|
|
536
|
-
// nobody can look up again.
|
|
537
|
-
await recordCoordinateDeliverables({
|
|
538
|
-
taskId: completionTaskId,
|
|
539
|
-
values: [...(flags.deliverable ?? []), ...(flags.deliverables ?? [])],
|
|
540
|
-
warn: (message) => this.warn(message),
|
|
541
|
-
});
|
|
542
228
|
let result;
|
|
543
229
|
try {
|
|
544
|
-
result = await
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
summary,
|
|
549
|
-
deliverables,
|
|
550
|
-
verdict,
|
|
551
|
-
report,
|
|
552
|
-
deliveryReceipt,
|
|
553
|
-
noMove: !!flags['no-move'],
|
|
554
|
-
transport: {
|
|
555
|
-
postComplete: (taskId, payload) => (0, api_fetch_1.apiFetch)(
|
|
556
|
-
// A managed session supplies its own completion path; otherwise the
|
|
557
|
-
// lib owns the URL so the endpoint stays visible to the parity check.
|
|
558
|
-
sessionCompletionPath ?? (0, task_extras_1.taskCompletePath)(taskId), {
|
|
559
|
-
method: 'POST',
|
|
560
|
-
body: payload,
|
|
561
|
-
// Same key the outbox will replay with — see `spaces create`.
|
|
562
|
-
// Without it on THIS attempt, a completion that lands and loses
|
|
563
|
-
// its response would be applied twice on replay.
|
|
564
|
-
headers: { 'X-Idempotency-Key': completionIdempotencyKey },
|
|
565
|
-
}),
|
|
566
|
-
addComment: (taskId, commentBody) => data_provider_1.dataService.addTaskComment({ taskId, data: { body: commentBody } }),
|
|
567
|
-
moveTask: (taskId, taskStatus) => data_provider_1.dataService.moveTask({ taskId, status: taskStatus }),
|
|
568
|
-
allowLegacyFallback: sessionCompletionPath ? false : undefined,
|
|
569
|
-
},
|
|
230
|
+
result = await (0, api_fetch_1.apiFetch)(sessionPath || (0, task_extras_1.taskCompletePath)(taskId), {
|
|
231
|
+
method: 'POST',
|
|
232
|
+
body: { ...payload, idempotencyKey },
|
|
233
|
+
headers: { 'X-Idempotency-Key': idempotencyKey },
|
|
570
234
|
});
|
|
571
235
|
}
|
|
572
|
-
catch (
|
|
573
|
-
|
|
574
|
-
// record of what the agent actually did. Losing that to a 503 costs the
|
|
575
|
-
// reasoning, not just a retry, so an OUTAGE queues it durably (OSK-2806)
|
|
576
|
-
// while every 4xx still fails immediately through handleApiError.
|
|
577
|
-
//
|
|
578
|
-
// The key is derived from (taskId, payload) so replaying THIS completion
|
|
579
|
-
// collapses onto the original server-side rather than double-completing;
|
|
580
|
-
// `requestIdempotency` on POST /api/tasks/:taskId/complete is what makes
|
|
581
|
-
// that hold.
|
|
582
|
-
this.queueWriteOrFail(err, {
|
|
583
|
-
command: 'tasks complete',
|
|
584
|
-
summary: `completion of task ${taskIdentity.id}`,
|
|
585
|
-
method: 'post',
|
|
586
|
-
url: `/api/tasks/${encodeURIComponent(taskIdentity.id)}/complete`,
|
|
587
|
-
body: completionPayload,
|
|
588
|
-
idempotencyKey: completionIdempotencyKey,
|
|
589
|
-
});
|
|
236
|
+
catch (error) {
|
|
237
|
+
this.handleApiError(error);
|
|
590
238
|
}
|
|
591
|
-
const
|
|
592
|
-
// Report the task the SERVER finalized, not the one resolved locally.
|
|
593
|
-
// `completeTaskDurably` already refuses a divergence, so these agree by the
|
|
594
|
-
// time we get here — preferring the server's id keeps that true even if a
|
|
595
|
-
// future path stops throwing.
|
|
596
|
-
const completedTaskId = movedTask?.id ?? taskIdentity.id;
|
|
597
|
-
const payload = {
|
|
598
|
-
success: true,
|
|
599
|
-
taskId: completedTaskId,
|
|
600
|
-
task: taskIdentity,
|
|
601
|
-
moved: !flags['no-move'],
|
|
602
|
-
moveStatus: movedTask?.status ?? null,
|
|
603
|
-
comment,
|
|
604
|
-
movedTask,
|
|
605
|
-
taskOutput: {
|
|
606
|
-
status,
|
|
607
|
-
summary,
|
|
608
|
-
deliverables,
|
|
609
|
-
verdict,
|
|
610
|
-
...(report ? { report } : {}),
|
|
611
|
-
body,
|
|
612
|
-
},
|
|
613
|
-
...(result.advisories?.length ? { advisories: result.advisories } : {}),
|
|
614
|
-
};
|
|
239
|
+
const url = (0, web_url_1.taskUrlFrom)(this.cliConfig.baseURL, { id: taskId });
|
|
615
240
|
if (flags.json) {
|
|
616
|
-
|
|
617
|
-
? (0, web_url_1.taskUrlFrom)(this.cliConfig.baseURL, movedTask)
|
|
618
|
-
: (0, web_url_1.taskUrlFrom)(this.cliConfig.baseURL, { id: completedTaskId });
|
|
619
|
-
this.log(JSON.stringify({ ...payload, url: report && url ? `${url}#task-outcome` : url }, null, 2));
|
|
241
|
+
this.log(JSON.stringify({ ...result, url }, null, 2));
|
|
620
242
|
return;
|
|
621
243
|
}
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
:
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
this.log(priorStatus && priorStatus === newStatus
|
|
633
|
-
? `Task ${completedTaskId} was already ${newStatus}; the outcome was amended.`
|
|
634
|
-
: `Moved task ${completedTaskId} to ${newStatus}`);
|
|
635
|
-
}
|
|
636
|
-
for (const advisory of result.advisories ?? []) {
|
|
637
|
-
// One warn per unmet condition. Each already reads as an observation —
|
|
638
|
-
// the completion happened — so the CLI relays them rather than
|
|
639
|
-
// re-wording them into something that sounds like a failure.
|
|
244
|
+
this.log(`Submitted Result v${result.result.version}; Task is ${result.task.statusType || result.task.status || 'updated'}.`);
|
|
245
|
+
const pending = describePendingReview({
|
|
246
|
+
statusType: result.task.statusType,
|
|
247
|
+
taskId,
|
|
248
|
+
resultId: result.result.id,
|
|
249
|
+
bin: this.config.bin,
|
|
250
|
+
});
|
|
251
|
+
if (pending)
|
|
252
|
+
this.log(pending);
|
|
253
|
+
for (const advisory of result.advisories || [])
|
|
640
254
|
this.warn(advisory.message);
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
const url = (0, web_url_1.taskUrlFrom)(this.cliConfig.baseURL, movedTask ?? { id: completedTaskId });
|
|
644
|
-
if (url)
|
|
645
|
-
this.log(`Outcome: ${url}#task-outcome`);
|
|
646
|
-
}
|
|
647
|
-
await (0, task_instruction_offer_1.maybeOfferTaskManagementInstructions)((message) => this.log(message));
|
|
255
|
+
if (url)
|
|
256
|
+
this.log(`Task: ${url}#task-result`);
|
|
648
257
|
}
|
|
649
258
|
}
|
|
650
259
|
exports.default = TasksComplete;
|
|
651
|
-
function autonomousSessionCompletionPath() {
|
|
652
|
-
// A daemon-delegated CLI credential has the regular user command surface;
|
|
653
|
-
// use the normal atomic endpoint so completion, comments, and moves all
|
|
654
|
-
// share the same full-permission principal.
|
|
655
|
-
if ((0, delegated_cli_1.isDelegatedCliSession)())
|
|
656
|
-
return null;
|
|
657
|
-
if (!(0, credential_resolver_1.resolveTrustedAutonomousCredential)())
|
|
658
|
-
return null;
|
|
659
|
-
const sessionId = (process.env.OVERSKY_SESSION_ID || '').trim();
|
|
660
|
-
if (!sessionId) {
|
|
661
|
-
throw new Error('Autonomous task completion requires OVERSKY_SESSION_ID.');
|
|
662
|
-
}
|
|
663
|
-
return `/api/agentic/sessions/${encodeURIComponent(sessionId)}/task-completion`;
|
|
664
|
-
}
|
|
665
|
-
function normalizeStatus(value) {
|
|
666
|
-
const normalized = optionalString(value) || 'completed';
|
|
667
|
-
if (normalized === 'completed' || normalized === 'failed') {
|
|
668
|
-
return normalized;
|
|
669
|
-
}
|
|
670
|
-
throw new Error('status must be "completed" or "failed".');
|
|
671
|
-
}
|
|
672
|
-
function requireOutputField(value, field) {
|
|
673
|
-
const normalized = optionalOutputField(value);
|
|
674
|
-
if (!normalized) {
|
|
675
|
-
throw new Error(`${field} is required.`);
|
|
676
|
-
}
|
|
677
|
-
return normalized;
|
|
678
|
-
}
|
|
679
|
-
function optionalOutputField(value) {
|
|
680
|
-
if (value === undefined || value === null)
|
|
681
|
-
return undefined;
|
|
682
|
-
const normalized = String(value)
|
|
683
|
-
.replace(/---TASK_OUTPUT---/gi, 'TASK_OUTPUT')
|
|
684
|
-
.replace(/---END_OUTPUT---/gi, 'END_OUTPUT')
|
|
685
|
-
.replace(/[\r\n]+/g, ' ')
|
|
686
|
-
.replace(/\s+/g, ' ')
|
|
687
|
-
.trim();
|
|
688
|
-
return normalized || undefined;
|
|
689
|
-
}
|
|
690
|
-
function optionalString(value) {
|
|
691
|
-
if (value === undefined || value === null)
|
|
692
|
-
return undefined;
|
|
693
|
-
const normalized = String(value).trim();
|
|
694
|
-
return normalized || undefined;
|
|
695
|
-
}
|
|
696
|
-
// Rendering is shared with the API (`@skrr-ai/data-provider`), so the string
|
|
697
|
-
// measured by the local preflight is byte-identical to the one the server stores.
|
|
698
|
-
const formatTaskOutput = data_provider_1.renderTaskOutputBody;
|