@skrr-ai/cli 0.1.118 → 0.1.120
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/base-command.d.ts +32 -0
- package/dist/base-command.js +113 -5
- package/dist/commands/commands.js +11 -0
- package/dist/commands/commitments/action-proposal.d.ts +13 -0
- package/dist/commands/commitments/action-proposal.js +39 -0
- package/dist/commands/commitments/action-proposals/decide.js +8 -1
- package/dist/commands/commitments/action-proposals/execute.js +11 -1
- package/dist/commands/commitments/create.js +2 -2
- package/dist/commands/commitments/doctor.d.ts +8 -0
- package/dist/commands/commitments/doctor.js +22 -0
- package/dist/commands/commitments/execute-remediation.js +12 -1
- package/dist/commands/commitments/observations.js +2 -2
- package/dist/commands/commitments/pack/bind.d.ts +18 -1
- package/dist/commands/commitments/pack/bind.js +66 -8
- package/dist/commands/commitments/pack/plan.d.ts +4 -1
- package/dist/commands/commitments/pack/plan.js +30 -2
- package/dist/commands/commitments/update.js +8 -2
- package/dist/commands/completion/index.js +1 -1
- package/dist/commands/completion/values.js +1 -1
- package/dist/commands/computer-use/audio.d.ts +1 -0
- package/dist/commands/computer-use/audio.js +1 -0
- package/dist/commands/computer-use/browser.d.ts +1 -0
- package/dist/commands/computer-use/browser.js +1 -0
- package/dist/commands/computer-use/disable.d.ts +1 -0
- package/dist/commands/computer-use/disable.js +1 -0
- package/dist/commands/computer-use/enable.d.ts +1 -0
- package/dist/commands/computer-use/enable.js +1 -0
- package/dist/commands/computer-use/index.d.ts +1 -0
- package/dist/commands/computer-use/index.js +1 -0
- package/dist/commands/computer-use/install.d.ts +1 -0
- package/dist/commands/computer-use/install.js +1 -0
- package/dist/commands/computer-use/recover.d.ts +1 -0
- package/dist/commands/computer-use/recover.js +1 -0
- package/dist/commands/computer-use/status.d.ts +1 -0
- package/dist/commands/computer-use/status.js +1 -0
- package/dist/commands/computer-use/stop.d.ts +1 -0
- package/dist/commands/computer-use/stop.js +1 -0
- package/dist/commands/context.js +16 -11
- package/dist/commands/convos/recovery.js +1 -1
- package/dist/commands/daemon/diagnostics.d.ts +9 -0
- package/dist/commands/daemon/diagnostics.js +53 -0
- package/dist/commands/daemon/index.js +2 -0
- package/dist/commands/daemon/maintenance/attest.d.ts +15 -0
- package/dist/commands/daemon/maintenance/attest.js +52 -0
- package/dist/commands/daemon/maintenance/hold.d.ts +17 -0
- package/dist/commands/daemon/maintenance/hold.js +70 -0
- package/dist/commands/daemon/maintenance/index.d.ts +12 -0
- package/dist/commands/daemon/maintenance/index.js +30 -0
- package/dist/commands/daemon/maintenance/resume.d.ts +13 -0
- package/dist/commands/daemon/maintenance/resume.js +36 -0
- package/dist/commands/daemon/maintenance/status.d.ts +13 -0
- package/dist/commands/daemon/maintenance/status.js +36 -0
- package/dist/commands/doctor.d.ts +4 -1
- package/dist/commands/doctor.js +50 -6
- package/dist/commands/followups/plans/cancel-batch.js +6 -3
- package/dist/commands/followups/plans/capacity.js +7 -3
- package/dist/commands/followups/plans/observability.js +11 -3
- package/dist/commands/followups/plans/preferences/set.js +26 -8
- package/dist/commands/followups/plans/preferences.js +1 -1
- package/dist/commands/followups/plans/show.js +1 -1
- package/dist/commands/goals/key-results/assess.d.ts +36 -0
- package/dist/commands/goals/key-results/assess.js +110 -0
- package/dist/commands/goals/key-results/history.d.ts +30 -0
- package/dist/commands/goals/key-results/history.js +104 -0
- package/dist/commands/goals/key-results/list.d.ts +5 -4
- package/dist/commands/goals/key-results/list.js +12 -6
- package/dist/commands/goals/key-results/show.d.ts +4 -2
- package/dist/commands/goals/key-results/show.js +25 -4
- package/dist/commands/goals/key-results/update.d.ts +4 -0
- package/dist/commands/goals/key-results/update.js +18 -24
- package/dist/commands/goals/plan-now.js +12 -1
- package/dist/commands/goals/planner-compare.d.ts +30 -0
- package/dist/commands/goals/planner-compare.js +136 -0
- package/dist/commands/machines/dedicated/service/exec.d.ts +3 -0
- package/dist/commands/machines/dedicated/service/exec.js +70 -11
- package/dist/commands/machines/hosted/destroy.d.ts +1 -0
- package/dist/commands/machines/hosted/destroy.js +11 -1
- package/dist/commands/machines/hosted/exec.d.ts +1 -0
- package/dist/commands/machines/hosted/exec.js +12 -1
- package/dist/commands/machines/hosted/index.js +1 -0
- package/dist/commands/machines/hosted/operation.d.ts +14 -0
- package/dist/commands/machines/hosted/operation.js +51 -0
- package/dist/commands/machines/hosted/pull.js +1 -1
- package/dist/commands/machines/hosted/start.js +6 -2
- package/dist/commands/memory/versions/list.d.ts +2 -0
- package/dist/commands/memory/versions/list.js +3 -2
- package/dist/commands/memory/versions/show.d.ts +2 -0
- package/dist/commands/memory/versions/show.js +3 -2
- package/dist/commands/spaces/auto-executor/compare.d.ts +18 -0
- package/dist/commands/spaces/auto-executor/compare.js +139 -0
- package/dist/commands/spaces/auto-executor/runs.d.ts +3 -0
- package/dist/commands/spaces/auto-executor/runs.js +13 -1
- package/dist/commands/spaces/charter/create-from-description.d.ts +21 -0
- package/dist/commands/spaces/charter/create-from-description.js +77 -0
- package/dist/commands/spaces/charter/show.d.ts +22 -0
- package/dist/commands/spaces/charter/show.js +61 -0
- package/dist/commands/spaces/heartbeats/list.d.ts +1 -16
- package/dist/commands/spaces/heartbeats/list.js +13 -0
- package/dist/commands/spaces/install-template.js +3 -0
- package/dist/commands/spaces/loops/migrate.d.ts +32 -0
- package/dist/commands/spaces/loops/migrate.js +103 -0
- package/dist/commands/spaces/loops/release.d.ts +14 -0
- package/dist/commands/spaces/loops/release.js +59 -0
- package/dist/commands/spaces/notebook.d.ts +25 -0
- package/dist/commands/spaces/notebook.js +75 -0
- package/dist/commands/spaces/proposals/discard.d.ts +17 -0
- package/dist/commands/spaces/proposals/discard.js +17 -0
- package/dist/commands/spaces/proposals/keep.d.ts +17 -0
- package/dist/commands/spaces/proposals/keep.js +17 -0
- package/dist/commands/spaces/proposals/list.d.ts +24 -0
- package/dist/commands/spaces/proposals/list.js +89 -0
- package/dist/commands/spaces/skills/propose.d.ts +29 -0
- package/dist/commands/spaces/skills/propose.js +82 -0
- package/dist/commands/spaces/templates/fork.js +3 -0
- package/dist/commands/spaces/triggers/show.js +6 -0
- package/dist/commands/spaces/workflows/retry.d.ts +1 -0
- package/dist/commands/spaces/workflows/retry.js +8 -1
- package/dist/commands/spaces/workflows/triggers/create.js +3 -2
- package/dist/commands/spaces/workflows/triggers/fire.js +4 -0
- package/dist/commands/spaces/workflows/triggers/update.js +2 -1
- package/dist/commands/spaces/write-policy/set.d.ts +22 -0
- package/dist/commands/spaces/write-policy/set.js +82 -0
- package/dist/commands/spaces/write-policy/show.d.ts +19 -0
- package/dist/commands/spaces/write-policy/show.js +49 -0
- package/dist/commands/store/artifacts/receipt.d.ts +14 -0
- package/dist/commands/store/artifacts/receipt.js +34 -0
- package/dist/commands/store/artifacts/release.d.ts +2 -0
- package/dist/commands/store/artifacts/release.js +11 -0
- package/dist/commands/store/governance/publish-release.d.ts +2 -0
- package/dist/commands/store/governance/publish-release.js +30 -1
- package/dist/commands/store/governance/release-receipt.d.ts +12 -0
- package/dist/commands/store/governance/release-receipt.js +32 -0
- package/dist/commands/tasks/approvals/list.d.ts +11 -5
- package/dist/commands/tasks/approvals/list.js +13 -6
- package/dist/commands/tasks/attachments/facts.d.ts +12 -0
- package/dist/commands/tasks/attachments/facts.js +29 -0
- package/dist/commands/tasks/attachments/upload.d.ts +1 -0
- package/dist/commands/tasks/attachments/upload.js +16 -0
- package/dist/commands/tasks/bulk-update.js +35 -1
- package/dist/commands/tasks/context.d.ts +15 -0
- package/dist/commands/tasks/context.js +69 -0
- package/dist/commands/tasks/copy.d.ts +18 -0
- package/dist/commands/tasks/copy.js +85 -0
- package/dist/commands/tasks/create-tree.js +97 -23
- package/dist/commands/tasks/create.js +9 -1
- package/dist/commands/tasks/deliverable/add.js +20 -10
- package/dist/commands/tasks/move.js +8 -1
- package/dist/commands/tasks/operations/show.d.ts +16 -0
- package/dist/commands/tasks/operations/show.js +98 -0
- package/dist/commands/tasks/operations/wait.d.ts +22 -0
- package/dist/commands/tasks/operations/wait.js +114 -0
- package/dist/commands/tasks/preflight.d.ts +1 -0
- package/dist/commands/tasks/preflight.js +11 -1
- package/dist/commands/tasks/rearm-recurrence.js +4 -0
- package/dist/commands/tasks/result/show.js +22 -0
- package/dist/commands/tasks/result/submit.d.ts +1 -0
- package/dist/commands/tasks/result/submit.js +3 -0
- package/dist/commands/tasks/retry-receipt.d.ts +15 -0
- package/dist/commands/tasks/retry-receipt.js +89 -0
- package/dist/commands/tasks/retry-review.d.ts +16 -0
- package/dist/commands/tasks/retry-review.js +66 -0
- package/dist/commands/tasks/retry.d.ts +4 -8
- package/dist/commands/tasks/retry.js +34 -68
- package/dist/commands/tasks/runs.d.ts +21 -0
- package/dist/commands/tasks/runs.js +95 -0
- package/dist/commands/tasks/show.d.ts +2 -0
- package/dist/commands/tasks/show.js +15 -1
- package/dist/commands/tasks/start-receipt.d.ts +15 -0
- package/dist/commands/tasks/start-receipt.js +95 -0
- package/dist/commands/tasks/start.d.ts +4 -0
- package/dist/commands/tasks/start.js +49 -3
- package/dist/commands/tasks/update.js +3 -0
- package/dist/commands/tasks/upsert.js +17 -3
- package/dist/commands/tasks/watch.d.ts +3 -0
- package/dist/commands/tasks/watch.js +85 -20
- package/dist/commands/wiki/history.d.ts +33 -0
- package/dist/commands/wiki/history.js +134 -0
- package/dist/commands/wiki/ls.d.ts +2 -0
- package/dist/commands/wiki/ls.js +8 -0
- package/dist/commands/wiki/update.d.ts +3 -1
- package/dist/commands/wiki/update.js +24 -2
- package/dist/commands/wiki/write.d.ts +22 -0
- package/dist/commands/wiki/write.js +144 -6
- package/dist/generated/agent-guides.json +81 -81
- package/dist/generated/managed-instruction-guides.json +1 -1
- package/dist/lib/agent-guides.d.ts +9 -0
- package/dist/lib/agent-guides.js +23 -1
- package/dist/lib/agentic-stream.d.ts +5 -0
- package/dist/lib/agentic-stream.js +122 -3
- package/dist/lib/api-fetch.d.ts +4 -0
- package/dist/lib/api-fetch.js +25 -7
- package/dist/lib/auth-diagnostics.d.ts +39 -0
- package/dist/lib/auth-diagnostics.js +71 -0
- package/dist/lib/cli-client-identity.d.ts +6 -0
- package/dist/lib/cli-client-identity.js +16 -0
- package/dist/lib/cli-installation-diagnostics.d.ts +52 -0
- package/dist/lib/cli-installation-diagnostics.js +254 -0
- package/dist/lib/command-hygiene.d.ts +45 -0
- package/dist/lib/command-hygiene.js +413 -0
- package/dist/lib/commitments.d.ts +64 -3
- package/dist/lib/commitments.js +34 -6
- package/dist/lib/completion-index.d.ts +16 -4
- package/dist/lib/completion-index.js +50 -14
- package/dist/lib/config.js +7 -1
- package/dist/lib/daemon-maintenance-command.d.ts +22 -0
- package/dist/lib/daemon-maintenance-command.js +39 -0
- package/dist/lib/daemon-maintenance.d.ts +275 -0
- package/dist/lib/daemon-maintenance.js +1035 -0
- package/dist/lib/dedicated-service.d.ts +3 -2
- package/dist/lib/dedicated-service.js +11 -3
- package/dist/lib/governed-store.d.ts +8 -0
- package/dist/lib/governed-store.js +5 -0
- package/dist/lib/hosted-machines.d.ts +33 -2
- package/dist/lib/hosted-machines.js +19 -1
- package/dist/lib/json-output.d.ts +1 -1
- package/dist/lib/machines.d.ts +9 -1
- package/dist/lib/machines.js +15 -1
- package/dist/lib/memory-history-session.d.ts +14 -0
- package/dist/lib/memory-history-session.js +60 -0
- package/dist/lib/node-adapter.js +13 -0
- package/dist/lib/record-output.d.ts +22 -0
- package/dist/lib/record-output.js +78 -0
- package/dist/lib/roles.d.ts +3 -3
- package/dist/lib/scoped-memory-command.d.ts +24 -0
- package/dist/lib/scoped-memory-command.js +87 -5
- package/dist/lib/space-charter.d.ts +49 -0
- package/dist/lib/space-charter.js +78 -0
- package/dist/lib/space-loop-migration.d.ts +18 -0
- package/dist/lib/space-loop-migration.js +128 -0
- package/dist/lib/space-notebook.d.ts +22 -0
- package/dist/lib/space-notebook.js +140 -0
- package/dist/lib/space-proposal-decision-command.d.ts +36 -0
- package/dist/lib/space-proposal-decision-command.js +108 -0
- package/dist/lib/space-proposals.d.ts +44 -0
- package/dist/lib/space-proposals.js +186 -0
- package/dist/lib/space-templates.d.ts +8 -0
- package/dist/lib/space-templates.js +17 -0
- package/dist/lib/space-write-policy.d.ts +24 -0
- package/dist/lib/space-write-policy.js +103 -0
- package/dist/lib/task-agent-context-capabilities.d.ts +10 -0
- package/dist/lib/task-agent-context-capabilities.js +22 -0
- package/dist/lib/task-agent-context-compatibility.d.ts +9 -0
- package/dist/lib/task-agent-context-compatibility.js +30 -0
- package/dist/lib/task-asset-upload.d.ts +22 -0
- package/dist/lib/task-asset-upload.js +257 -29
- package/dist/lib/task-batch.d.ts +15 -1
- package/dist/lib/task-batch.js +35 -2
- package/dist/lib/task-operation-watch.d.ts +66 -0
- package/dist/lib/task-operation-watch.js +159 -0
- package/dist/lib/task-resolver.d.ts +1 -1
- package/dist/lib/task-resolver.js +4 -3
- package/dist/lib/task-runtime-headers.js +13 -3
- package/dist/lib/task-work.d.ts +1 -1
- package/dist/lib/task-work.js +35 -1
- package/dist/lib/task-write-attribution.d.ts +14 -0
- package/dist/lib/task-write-attribution.js +35 -2
- package/dist/lib/triggers.d.ts +11 -0
- package/dist/lib/triggers.js +7 -0
- package/dist/lib/wiki-update.d.ts +1 -0
- package/dist/lib/wiki-update.js +10 -1
- package/dist/lib/wiki.js +4 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerWire.d.ts +9 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerWire.js +31 -15
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.js +1 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonWire.js +13 -7
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/guidedUpdate.d.ts +370 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/guidedUpdate.js +315 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/maintenanceReportWire.d.ts +178 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/maintenanceReportWire.js +262 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/toolApprovalSignature.d.ts +51 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/toolApprovalSignature.js +106 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerWire.d.ts +9 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerWire.js +32 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.js +1 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonWire.js +33 -14
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/guidedUpdate.d.ts +370 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/guidedUpdate.js +404 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/maintenanceReportWire.d.ts +178 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/maintenanceReportWire.js +342 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/toolApprovalSignature.d.ts +51 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/toolApprovalSignature.js +138 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +20 -0
- package/dist/node_modules/@skrr-ai/data-provider/index.js +27085 -20250
- package/oclif.manifest.json +10570 -7247
- package/package.json +20 -2
|
@@ -37,6 +37,8 @@ exports.readTaskForUpload = readTaskForUpload;
|
|
|
37
37
|
exports.uploadTaskAssetFile = uploadTaskAssetFile;
|
|
38
38
|
exports.attachTaskAssetFiles = attachTaskAssetFiles;
|
|
39
39
|
exports.taskAssetMarkdownRef = taskAssetMarkdownRef;
|
|
40
|
+
exports.taskAssetUploadRecovery = taskAssetUploadRecovery;
|
|
41
|
+
exports.putFilePartToStorage = putFilePartToStorage;
|
|
40
42
|
exports.sha256OfFile = sha256OfFile;
|
|
41
43
|
exports.postFileToStorage = postFileToStorage;
|
|
42
44
|
/**
|
|
@@ -45,7 +47,7 @@ exports.postFileToStorage = postFileToStorage;
|
|
|
45
47
|
* funnel here so the routing rules live in exactly one place (OSK-9863).
|
|
46
48
|
*
|
|
47
49
|
* ≤25MB or image → buffered multipart (canonicalization needs the bytes)
|
|
48
|
-
* larger non-image → intent →
|
|
50
|
+
* larger non-image → intent → granted single/multipart transport → complete
|
|
49
51
|
*
|
|
50
52
|
* Agent uploads retain the authenticated session producer even beside a wide
|
|
51
53
|
* delegated credential. Human uploads use `/api/tasks/:id/*`.
|
|
@@ -100,6 +102,11 @@ async function readTaskForUpload(taskId) {
|
|
|
100
102
|
*/
|
|
101
103
|
async function uploadTaskAssetFile(opts) {
|
|
102
104
|
const { taskId, filePath, role, ledger } = opts;
|
|
105
|
+
if (opts.resumeAttachmentId !== undefined &&
|
|
106
|
+
(!opts.resumeAttachmentId || opts.resumeAttachmentId.trim() !== opts.resumeAttachmentId ||
|
|
107
|
+
opts.resumeAttachmentId.length > 200 || ledger !== undefined)) {
|
|
108
|
+
throw new Error('Resume requires one exact attachment id and retains its original ledger declaration.');
|
|
109
|
+
}
|
|
103
110
|
if (!fs.existsSync(filePath)) {
|
|
104
111
|
throw new Error(`File not found: ${filePath}`);
|
|
105
112
|
}
|
|
@@ -121,7 +128,11 @@ async function uploadTaskAssetFile(opts) {
|
|
|
121
128
|
const sessionTarget = await (0, session_task_write_1.sessionTaskWrite)('attachment', taskId);
|
|
122
129
|
let result;
|
|
123
130
|
try {
|
|
124
|
-
if (
|
|
131
|
+
if (opts.resumeAttachmentId !== undefined) {
|
|
132
|
+
result = await resumeDirectUpload({ taskId, filePath, size, filename, mimeType, role,
|
|
133
|
+
attachmentId: opts.resumeAttachmentId }, sessionTarget);
|
|
134
|
+
}
|
|
135
|
+
else if (sessionTarget) {
|
|
125
136
|
result = direct
|
|
126
137
|
? await sessionDirectUpload({ filePath, size, filename, mimeType, role, ledger }, sessionTarget)
|
|
127
138
|
: await sessionBufferedUpload({ filePath, filename, mimeType, role, ledger }, sessionTarget);
|
|
@@ -135,7 +146,9 @@ async function uploadTaskAssetFile(opts) {
|
|
|
135
146
|
catch (err) {
|
|
136
147
|
throw withServerReason(err, filename);
|
|
137
148
|
}
|
|
149
|
+
requireReadyAsset(result);
|
|
138
150
|
return {
|
|
151
|
+
status: 'ready',
|
|
139
152
|
id: String(result.id ?? result._id),
|
|
140
153
|
digest: storedDigest(result),
|
|
141
154
|
filename,
|
|
@@ -144,6 +157,12 @@ async function uploadTaskAssetFile(opts) {
|
|
|
144
157
|
...(result.resourceReceipt ? { resourceReceipt: result.resourceReceipt } : {}),
|
|
145
158
|
};
|
|
146
159
|
}
|
|
160
|
+
function requireReadyAsset(asset) {
|
|
161
|
+
const id = asset?.id ?? asset?._id;
|
|
162
|
+
if (typeof id !== 'string' || !id.trim() || asset.status !== 'ready') {
|
|
163
|
+
throw new Error('The server did not confirm a ready Task attachment; the upload is not complete.');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
147
166
|
/**
|
|
148
167
|
* Put the server's own reason on a refused upload.
|
|
149
168
|
*
|
|
@@ -163,6 +182,9 @@ function withServerReason(err, filename) {
|
|
|
163
182
|
if (!message)
|
|
164
183
|
return err;
|
|
165
184
|
err.message = `${filename}: ${message}${code ? ` (${code})` : ''}`;
|
|
185
|
+
const recovery = taskAssetUploadRecovery(err);
|
|
186
|
+
if (recovery)
|
|
187
|
+
err.message += ` ${resumeHint(recovery.attachmentId)}`;
|
|
166
188
|
return err;
|
|
167
189
|
}
|
|
168
190
|
/**
|
|
@@ -236,6 +258,7 @@ async function genericDirectUpload(p) {
|
|
|
236
258
|
mimeType: p.mimeType,
|
|
237
259
|
sizeBytes: p.size,
|
|
238
260
|
sha256: await sha256OfFile(p.filePath),
|
|
261
|
+
transport: 'auto',
|
|
239
262
|
role: p.role,
|
|
240
263
|
...(p.ledger?.identity
|
|
241
264
|
? {
|
|
@@ -246,20 +269,7 @@ async function genericDirectUpload(p) {
|
|
|
246
269
|
}
|
|
247
270
|
: {}),
|
|
248
271
|
});
|
|
249
|
-
|
|
250
|
-
return intent.attachment;
|
|
251
|
-
await postFileToStorage({
|
|
252
|
-
uploadUrl: intent.uploadUrl,
|
|
253
|
-
uploadFields: intent.uploadFields ?? {},
|
|
254
|
-
filePath: p.filePath,
|
|
255
|
-
size: p.size,
|
|
256
|
-
filename: p.filename,
|
|
257
|
-
mimeType: p.mimeType,
|
|
258
|
-
});
|
|
259
|
-
return data_provider_1.dataService.completeTaskAttachmentUpload({
|
|
260
|
-
taskId: p.taskId,
|
|
261
|
-
attachmentId: intent.attachment.id,
|
|
262
|
-
});
|
|
272
|
+
return uploadDirectIntent(p, intent, directUploadProtocol(p.taskId, directAttachmentId(intent)));
|
|
263
273
|
}
|
|
264
274
|
async function sessionDirectUpload(p, target) {
|
|
265
275
|
const intent = await (0, api_fetch_1.apiFetch)(`${target.path}/intent`, {
|
|
@@ -270,6 +280,7 @@ async function sessionDirectUpload(p, target) {
|
|
|
270
280
|
mimeType: p.mimeType,
|
|
271
281
|
sizeBytes: p.size,
|
|
272
282
|
sha256: await sha256OfFile(p.filePath),
|
|
283
|
+
transport: 'auto',
|
|
273
284
|
...(p.role ? { role: p.role } : {}),
|
|
274
285
|
...(p.ledger?.identity
|
|
275
286
|
? {
|
|
@@ -281,20 +292,237 @@ async function sessionDirectUpload(p, target) {
|
|
|
281
292
|
: {}),
|
|
282
293
|
},
|
|
283
294
|
});
|
|
284
|
-
|
|
295
|
+
return uploadDirectIntent(p, intent, directUploadProtocol(target.taskId, directAttachmentId(intent), target));
|
|
296
|
+
}
|
|
297
|
+
function directUploadProtocol(taskId, attachmentId, target) {
|
|
298
|
+
const recovery = { taskId, attachmentId, transport: 'multipart' };
|
|
299
|
+
const identity = { taskId, attachmentId };
|
|
300
|
+
if (!target)
|
|
301
|
+
return {
|
|
302
|
+
recovery,
|
|
303
|
+
status: () => data_provider_1.dataService.getTaskAssetUploadStatus(identity),
|
|
304
|
+
sign: (partNumbers) => data_provider_1.dataService.signTaskAssetUploadParts({ ...identity, partNumbers }),
|
|
305
|
+
record: (part) => data_provider_1.dataService.recordTaskAssetUploadedPart({ ...identity, ...part }),
|
|
306
|
+
complete: () => data_provider_1.dataService.completeTaskAttachmentUpload(identity),
|
|
307
|
+
};
|
|
308
|
+
const uploadPath = `${target.path}/${encodeURIComponent(attachmentId)}`;
|
|
309
|
+
return {
|
|
310
|
+
recovery,
|
|
311
|
+
status: () => (0, api_fetch_1.apiFetch)(`${uploadPath}/upload-status`, {
|
|
312
|
+
credential: target.credential,
|
|
313
|
+
}),
|
|
314
|
+
sign: (partNumbers) => (0, api_fetch_1.apiFetch)(`${uploadPath}/upload-parts/sign`, {
|
|
315
|
+
method: 'POST', credential: target.credential, body: { partNumbers },
|
|
316
|
+
}),
|
|
317
|
+
record: (part) => (0, api_fetch_1.apiFetch)(`${uploadPath}/upload-parts/complete`, {
|
|
318
|
+
method: 'POST', credential: target.credential, body: part,
|
|
319
|
+
}),
|
|
320
|
+
complete: () => (0, api_fetch_1.apiFetch)(`${target.path}/complete`, {
|
|
321
|
+
method: 'POST', credential: target.credential, body: { attachmentId },
|
|
322
|
+
}),
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
/** A bounded recovery receipt, never a storage credential or claim that bytes are ready. */
|
|
326
|
+
function taskAssetUploadRecovery(error) {
|
|
327
|
+
if (!error || typeof error !== 'object')
|
|
328
|
+
return undefined;
|
|
329
|
+
const value = error.uploadRecovery;
|
|
330
|
+
if (!value || typeof value.taskId !== 'string' || typeof value.attachmentId !== 'string' ||
|
|
331
|
+
value.transport !== 'multipart')
|
|
332
|
+
return undefined;
|
|
333
|
+
return { taskId: value.taskId, attachmentId: value.attachmentId, transport: 'multipart',
|
|
334
|
+
...(typeof value.sessionId === 'string' ? { sessionId: value.sessionId } : {}) };
|
|
335
|
+
}
|
|
336
|
+
function resumeHint(attachmentId) {
|
|
337
|
+
return `Upload attachment ${attachmentId} is not confirmed complete. ` +
|
|
338
|
+
`Use --resume-attachment ${attachmentId} with tasks attachments upload and the same file to recover this exact upload.`;
|
|
339
|
+
}
|
|
340
|
+
async function resumeDirectUpload(file, target) {
|
|
341
|
+
const taskId = target?.taskId ?? file.taskId;
|
|
342
|
+
const protocol = directUploadProtocol(taskId, file.attachmentId, target);
|
|
343
|
+
const progress = await protocol.status();
|
|
344
|
+
const rawDeclaration = progress?.declaredAttachment;
|
|
345
|
+
const declaration = rawDeclaration && typeof rawDeclaration === 'object' && !Array.isArray(rawDeclaration)
|
|
346
|
+
? rawDeclaration : undefined;
|
|
347
|
+
const sha256 = await sha256OfFile(file.filePath);
|
|
348
|
+
if (!declaration || file.size < 1 || declaration.basis !== 'declared_upload_intent' ||
|
|
349
|
+
declaration.attachmentId !== file.attachmentId || declaration.taskId !== taskId ||
|
|
350
|
+
declaration.filename !== file.filename || declaration.mimeType !== file.mimeType ||
|
|
351
|
+
declaration.sizeBytes !== file.size || declaration.sha256 !== sha256 ||
|
|
352
|
+
typeof declaration.role !== 'string' || !['input', 'evidence', 'deliverable'].includes(declaration.role) ||
|
|
353
|
+
(file.role !== undefined && declaration.role !== file.role)) {
|
|
354
|
+
throw new Error('This file does not match the existing upload declaration, or the server cannot verify it. No replacement upload was created.');
|
|
355
|
+
}
|
|
356
|
+
if (progress.transport !== 'multipart' || typeof progress.expiresAt !== 'string' ||
|
|
357
|
+
!Number.isFinite(Date.parse(progress.expiresAt)) ||
|
|
358
|
+
Date.parse(progress.expiresAt) <= Date.now()) {
|
|
359
|
+
throw new Error('Only an unexpired multipart upload can be resumed. No replacement upload was created.');
|
|
360
|
+
}
|
|
361
|
+
const intent = {
|
|
362
|
+
attachment: { id: file.attachmentId, status: 'pending' }, uploadUrl: null, uploadFields: null,
|
|
363
|
+
uploadTransport: 'multipart', partSize: progress.partSize, partCount: progress.expectedParts,
|
|
364
|
+
};
|
|
365
|
+
// Even a ready session goes through canonical completion to recover its
|
|
366
|
+
// verified attachment and original ledger receipt, never its declaration.
|
|
367
|
+
return uploadDirectIntent(file, intent, protocol);
|
|
368
|
+
}
|
|
369
|
+
function directAttachmentId(intent) {
|
|
370
|
+
const id = intent?.attachment?.id;
|
|
371
|
+
if (typeof id !== 'string' || !id.trim()) {
|
|
372
|
+
throw new Error('The upload intent did not identify a Task attachment.');
|
|
373
|
+
}
|
|
374
|
+
return id;
|
|
375
|
+
}
|
|
376
|
+
/** Shared transport mechanics; each caller retains its own authenticated API door. */
|
|
377
|
+
async function uploadDirectIntent(file, intent, protocol) {
|
|
378
|
+
directAttachmentId(intent);
|
|
379
|
+
if (intent.attachment.status === 'ready')
|
|
285
380
|
return intent.attachment;
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
381
|
+
if ((intent.attachment.status !== undefined && intent.attachment.status !== 'pending') ||
|
|
382
|
+
(intent.uploadTransport !== undefined && !['single', 'multipart'].includes(intent.uploadTransport))) {
|
|
383
|
+
throw new Error('The server returned an unusable Task attachment upload intent.');
|
|
384
|
+
}
|
|
385
|
+
try {
|
|
386
|
+
if (intent.uploadTransport === 'multipart') {
|
|
387
|
+
await uploadMultipartFile(file, intent, protocol);
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
if (!intent.uploadUrl || !intent.uploadFields) {
|
|
391
|
+
throw new Error('The pending Task attachment has no usable storage grant; it was not uploaded.');
|
|
392
|
+
}
|
|
393
|
+
await postFileToStorage({ ...file, uploadUrl: intent.uploadUrl, uploadFields: intent.uploadFields });
|
|
394
|
+
}
|
|
395
|
+
const completed = await protocol.complete();
|
|
396
|
+
requireReadyAsset(completed);
|
|
397
|
+
if ((completed.id ?? completed._id) !== intent.attachment.id) {
|
|
398
|
+
throw new Error('Upload completion returned a different Task attachment.');
|
|
399
|
+
}
|
|
400
|
+
return completed;
|
|
401
|
+
}
|
|
402
|
+
catch (error) {
|
|
403
|
+
if (intent.uploadTransport !== 'multipart' || !(error instanceof Error))
|
|
404
|
+
throw error;
|
|
405
|
+
Object.assign(error, { uploadRecovery: { ...protocol.recovery } });
|
|
406
|
+
error.message += ` ${resumeHint(intent.attachment.id)}`;
|
|
407
|
+
throw error;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
/** Resume only the parts this exact upload still owes. Completion remains server-verified. */
|
|
411
|
+
async function uploadMultipartFile(file, intent, protocol) {
|
|
412
|
+
const partSize = intent.partSize;
|
|
413
|
+
if (!Number.isSafeInteger(partSize) || !partSize || partSize < 1) {
|
|
414
|
+
throw new Error('The multipart grant has no valid part size.');
|
|
415
|
+
}
|
|
416
|
+
const partCount = Math.ceil(file.size / partSize);
|
|
417
|
+
if (partCount > 10000 || (intent.partCount !== undefined && intent.partCount !== partCount)) {
|
|
418
|
+
throw new Error('The multipart grant does not match the file size.');
|
|
419
|
+
}
|
|
420
|
+
const lengthOf = (partNumber) => Math.min(partSize, file.size - (partNumber - 1) * partSize);
|
|
421
|
+
let sessionId;
|
|
422
|
+
const inspect = (progress) => {
|
|
423
|
+
if (!progress || typeof progress.sessionId !== 'string' || !progress.sessionId ||
|
|
424
|
+
(sessionId && progress.sessionId !== sessionId) || progress.transport !== 'multipart' ||
|
|
425
|
+
progress.partSize !== partSize || progress.expectedSize !== file.size ||
|
|
426
|
+
progress.expectedParts !== partCount || !Array.isArray(progress.receivedParts) ||
|
|
427
|
+
!['created', 'receiving', 'landed', 'verified', 'scanning', 'canonicalizing', 'ready'].includes(progress.state)) {
|
|
428
|
+
throw new Error('The server returned inconsistent multipart upload progress.');
|
|
429
|
+
}
|
|
430
|
+
sessionId = progress.sessionId;
|
|
431
|
+
const received = new Set();
|
|
432
|
+
let bytes = 0;
|
|
433
|
+
for (const part of progress.receivedParts) {
|
|
434
|
+
if (!Number.isInteger(part.partNumber) || part.partNumber < 1 || part.partNumber > partCount ||
|
|
435
|
+
received.has(part.partNumber) || part.byteLength !== lengthOf(part.partNumber)) {
|
|
436
|
+
throw new Error('The server returned an invalid multipart part receipt.');
|
|
437
|
+
}
|
|
438
|
+
received.add(part.partNumber);
|
|
439
|
+
bytes += part.byteLength;
|
|
440
|
+
}
|
|
441
|
+
if (progress.uploadedBytes !== bytes) {
|
|
442
|
+
throw new Error('The server returned an inconsistent multipart byte count.');
|
|
443
|
+
}
|
|
444
|
+
return received;
|
|
445
|
+
};
|
|
446
|
+
let progress = await protocol.status();
|
|
447
|
+
const received = inspect(progress);
|
|
448
|
+
protocol.recovery.sessionId = progress.sessionId;
|
|
449
|
+
for (let partNumber = 1; partNumber <= partCount; partNumber += 1) {
|
|
450
|
+
if (received.has(partNumber))
|
|
451
|
+
continue;
|
|
452
|
+
if (!['created', 'receiving'].includes(progress.state)) {
|
|
453
|
+
throw new Error('The multipart upload cannot receive its missing parts.');
|
|
454
|
+
}
|
|
455
|
+
const signed = await protocol.sign([partNumber]);
|
|
456
|
+
inspect(signed?.progress);
|
|
457
|
+
if (signed.sessionId !== sessionId || !Array.isArray(signed.parts) || signed.parts.length !== 1 ||
|
|
458
|
+
signed.parts[0].partNumber !== partNumber || typeof signed.parts[0].url !== 'string') {
|
|
459
|
+
throw new Error('The server did not grant the requested multipart part.');
|
|
460
|
+
}
|
|
461
|
+
const receipt = await putFilePartToStorage({
|
|
462
|
+
uploadUrl: signed.parts[0].url, filePath: file.filePath,
|
|
463
|
+
offset: (partNumber - 1) * partSize, byteLength: lengthOf(partNumber),
|
|
464
|
+
});
|
|
465
|
+
const recorded = await protocol.record({ partNumber, ...receipt });
|
|
466
|
+
const landed = inspect(recorded?.progress);
|
|
467
|
+
if (recorded.sessionId !== sessionId || recorded.recorded !== true || !landed.has(partNumber)) {
|
|
468
|
+
throw new Error('The server did not acknowledge the uploaded multipart part.');
|
|
469
|
+
}
|
|
470
|
+
progress = recorded.progress;
|
|
471
|
+
}
|
|
472
|
+
const final = await protocol.status();
|
|
473
|
+
if (inspect(final).size !== partCount || final.uploadedBytes !== file.size ||
|
|
474
|
+
fs.statSync(file.filePath).size !== file.size) {
|
|
475
|
+
throw new Error('The multipart upload is incomplete or the file changed during upload.');
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
/** PUT one bounded file range and report only the actual storage receipt and streamed bytes. */
|
|
479
|
+
async function putFilePartToStorage(p) {
|
|
480
|
+
const url = new URL(p.uploadUrl);
|
|
481
|
+
if (!['http:', 'https:'].includes(url.protocol))
|
|
482
|
+
throw new Error('Unsupported storage upload protocol.');
|
|
483
|
+
return new Promise((resolve, reject) => {
|
|
484
|
+
let settled = false;
|
|
485
|
+
let sent = 0;
|
|
486
|
+
let ended = false;
|
|
487
|
+
let etag;
|
|
488
|
+
const stream = fs.createReadStream(p.filePath, { start: p.offset, end: p.offset + p.byteLength - 1 });
|
|
489
|
+
const fail = (message) => {
|
|
490
|
+
if (settled)
|
|
491
|
+
return;
|
|
492
|
+
settled = true;
|
|
493
|
+
stream.destroy();
|
|
494
|
+
req.destroy();
|
|
495
|
+
reject(new Error(message));
|
|
496
|
+
};
|
|
497
|
+
const done = () => {
|
|
498
|
+
if (settled || !ended || !etag)
|
|
499
|
+
return;
|
|
500
|
+
if (sent !== p.byteLength)
|
|
501
|
+
return fail('The file changed while its multipart part was uploaded.');
|
|
502
|
+
settled = true;
|
|
503
|
+
resolve({ etag, byteLength: sent });
|
|
504
|
+
};
|
|
505
|
+
const req = (url.protocol === 'http:' ? http : https).request(url, {
|
|
506
|
+
method: 'PUT', headers: { 'Content-Length': String(p.byteLength) },
|
|
507
|
+
}, (res) => {
|
|
508
|
+
const status = res.statusCode ?? 0;
|
|
509
|
+
const actualEtag = res.headers.etag;
|
|
510
|
+
if (status < 200 || status >= 300)
|
|
511
|
+
return fail(`Storage rejected the multipart part (${status}).`);
|
|
512
|
+
if (typeof actualEtag !== 'string' || !actualEtag.trim() || actualEtag.length > 220) {
|
|
513
|
+
return fail('Storage did not return a valid multipart part receipt.');
|
|
514
|
+
}
|
|
515
|
+
res.on('error', () => fail('Storage multipart response failed.'));
|
|
516
|
+
res.on('aborted', () => fail('Storage multipart response was interrupted.'));
|
|
517
|
+
res.on('end', () => { etag = actualEtag; done(); });
|
|
518
|
+
res.resume();
|
|
519
|
+
});
|
|
520
|
+
req.on('error', () => fail('Storage multipart upload failed.'));
|
|
521
|
+
req.setTimeout(60000, () => fail('Storage multipart upload timed out.'));
|
|
522
|
+
stream.on('data', (chunk) => { sent += chunk.length; });
|
|
523
|
+
stream.on('error', () => fail('The multipart file could not be read.'));
|
|
524
|
+
stream.on('end', () => { ended = true; done(); });
|
|
525
|
+
stream.pipe(req);
|
|
298
526
|
});
|
|
299
527
|
}
|
|
300
528
|
/** sha256 of a file, read as a stream: a 250MB recording never sits in memory. */
|
package/dist/lib/task-batch.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* sends a command back to its serial path. The shared transport owns each
|
|
9
9
|
* request's fresh idempotency key.
|
|
10
10
|
*/
|
|
11
|
-
import { type TaskBatchItemResult, type TaskBatchPatchItem } from '@skrr-ai/data-provider';
|
|
11
|
+
import { type TaskBatchItemResult, type TaskBatchPatchItem, type SpaceWriteProposalRef } from '@skrr-ai/data-provider';
|
|
12
12
|
export type ApiFailure = {
|
|
13
13
|
message: string;
|
|
14
14
|
status?: number;
|
|
@@ -45,6 +45,18 @@ export type BatchCreatedRow = {
|
|
|
45
45
|
status: 'created' | 'exists';
|
|
46
46
|
externalKey?: string;
|
|
47
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* A node the Space write policy held for approval (OSK-17988): nothing was
|
|
50
|
+
* written, and a person approves it from the named bundle. A node that needs a
|
|
51
|
+
* held one as its parent or blocker is held with it (`depends_on_held`): it
|
|
52
|
+
* cannot be placed until the held one exists, and is not a failure.
|
|
53
|
+
*/
|
|
54
|
+
export type BatchHeldRow = {
|
|
55
|
+
key: string;
|
|
56
|
+
reason: 'pending_approval' | 'depends_on_held';
|
|
57
|
+
proposal?: SpaceWriteProposalRef;
|
|
58
|
+
externalKey?: string;
|
|
59
|
+
};
|
|
48
60
|
export type BatchCreateFailure = {
|
|
49
61
|
key: string;
|
|
50
62
|
/** Not attempted because its parent was not created (or an earlier chunk failed). */
|
|
@@ -65,6 +77,8 @@ export type BatchCreateOutcome = {
|
|
|
65
77
|
supported: true;
|
|
66
78
|
rows: BatchCreatedRow[];
|
|
67
79
|
failures: BatchCreateFailure[];
|
|
80
|
+
/** Held for approval by the Space write policy, and the nodes waiting on them. */
|
|
81
|
+
held: BatchHeldRow[];
|
|
68
82
|
/** Requests made to the batch route, retries included. */
|
|
69
83
|
rounds: number;
|
|
70
84
|
};
|
package/dist/lib/task-batch.js
CHANGED
|
@@ -183,6 +183,8 @@ async function createNodesViaBatch(nodes, mapping, options = {}) {
|
|
|
183
183
|
const position = new Map(nodes.map((node, index) => [node.key, index]));
|
|
184
184
|
const rows = [];
|
|
185
185
|
const failures = [];
|
|
186
|
+
const held = [];
|
|
187
|
+
const heldKeys = new Set();
|
|
186
188
|
const chunkSize = data_provider_1.TASK_BATCH_MAX_ITEMS ?? 100;
|
|
187
189
|
let remaining = nodes.filter((node) => !(node.key in mapping));
|
|
188
190
|
/** Nodes whose last submission ended without a verdict: they may exist. */
|
|
@@ -230,6 +232,7 @@ async function createNodesViaBatch(nodes, mapping, options = {}) {
|
|
|
230
232
|
const items = [];
|
|
231
233
|
const sent = [];
|
|
232
234
|
const dropped = new Set();
|
|
235
|
+
const heldDropped = new Set();
|
|
233
236
|
for (const [i, node] of chunk.entries()) {
|
|
234
237
|
const { parentId: _ignored, ...payload } = node.payload;
|
|
235
238
|
const item = { ...payload, tempId: `n${i}` };
|
|
@@ -270,6 +273,14 @@ async function createNodesViaBatch(nodes, mapping, options = {}) {
|
|
|
270
273
|
}
|
|
271
274
|
}
|
|
272
275
|
}
|
|
276
|
+
const waitsOnHeld = (node.parentKey && heldKeys.has(node.parentKey)) ||
|
|
277
|
+
(node.blockerKeys ?? []).some((key) => heldKeys.has(key));
|
|
278
|
+
if (unresolved && waitsOnHeld) {
|
|
279
|
+
heldDropped.add(node.key);
|
|
280
|
+
heldKeys.add(node.key);
|
|
281
|
+
held.push({ key: node.key, reason: 'depends_on_held' });
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
273
284
|
if (unresolved) {
|
|
274
285
|
dropped.add(node.key);
|
|
275
286
|
failures.push({
|
|
@@ -291,8 +302,11 @@ async function createNodesViaBatch(nodes, mapping, options = {}) {
|
|
|
291
302
|
}
|
|
292
303
|
// A dropped node is a failure already recorded; it leaves the queue now, or
|
|
293
304
|
// it would be examined, and recorded, again every round.
|
|
294
|
-
remaining = remaining.filter((node) => !dropped.has(node.key));
|
|
305
|
+
remaining = remaining.filter((node) => !dropped.has(node.key) && !heldDropped.has(node.key));
|
|
295
306
|
const upstreamBroken = dropped.size > 0;
|
|
307
|
+
// Every node of this chunk waits on a held one; the queue already moved on.
|
|
308
|
+
if (items.length === 0 && !upstreamBroken)
|
|
309
|
+
continue;
|
|
296
310
|
if (items.length === 0) {
|
|
297
311
|
// Everything in this chunk depended on something that is gone, so nothing
|
|
298
312
|
// further can be built on it.
|
|
@@ -354,6 +368,24 @@ async function createNodesViaBatch(nodes, mapping, options = {}) {
|
|
|
354
368
|
const result = results.get(i);
|
|
355
369
|
const code = result?.error?.code;
|
|
356
370
|
const status = result?.error?.status ?? 0;
|
|
371
|
+
// Held for approval: nothing was written, and resubmitting would only
|
|
372
|
+
// find the same approval item. Not a failure, and not retried.
|
|
373
|
+
if (result?.status === 'pending_approval') {
|
|
374
|
+
held.push({
|
|
375
|
+
key: node.key,
|
|
376
|
+
reason: 'pending_approval',
|
|
377
|
+
...(result.proposal ? { proposal: result.proposal } : {}),
|
|
378
|
+
...(result.externalKey ? { externalKey: result.externalKey } : {}),
|
|
379
|
+
});
|
|
380
|
+
heldKeys.add(node.key);
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
if (result?.status === 'skipped' &&
|
|
384
|
+
(code === 'BATCH_PARENT_PENDING_APPROVAL' || code === 'BATCH_BLOCKER_PENDING_APPROVAL')) {
|
|
385
|
+
held.push({ key: node.key, reason: 'depends_on_held' });
|
|
386
|
+
heldKeys.add(node.key);
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
357
389
|
const confirmed = result?.ok && result.id;
|
|
358
390
|
if (confirmed) {
|
|
359
391
|
mapping[node.key] = result.id;
|
|
@@ -467,7 +499,8 @@ async function createNodesViaBatch(nodes, mapping, options = {}) {
|
|
|
467
499
|
}
|
|
468
500
|
rows.sort((a, b) => (position.get(a.key) ?? 0) - (position.get(b.key) ?? 0));
|
|
469
501
|
failures.sort((a, b) => (position.get(a.key) ?? 0) - (position.get(b.key) ?? 0));
|
|
470
|
-
|
|
502
|
+
held.sort((a, b) => (position.get(a.key) ?? 0) - (position.get(b.key) ?? 0));
|
|
503
|
+
return { supported: true, rows, failures, held, rounds: round };
|
|
471
504
|
}
|
|
472
505
|
/**
|
|
473
506
|
* Apply `updates` in chunks; `results[i]` answers `updates[i]`. A chunk the
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only waiting for a server-authoritative Task Operation.
|
|
3
|
+
*
|
|
4
|
+
* A Task status is intentionally not used as a completion signal here. One
|
|
5
|
+
* Task can have several sequential or competing operations, so only the
|
|
6
|
+
* operation's immutable id and server projection may settle this wait.
|
|
7
|
+
*/
|
|
8
|
+
export type TaskOperationProjection = {
|
|
9
|
+
object?: string;
|
|
10
|
+
schemaVersion?: number;
|
|
11
|
+
operationId?: string;
|
|
12
|
+
taskId?: string;
|
|
13
|
+
intent?: string;
|
|
14
|
+
state?: string;
|
|
15
|
+
phase?: string;
|
|
16
|
+
terminal?: boolean;
|
|
17
|
+
outcome?: string;
|
|
18
|
+
updatedAt?: string;
|
|
19
|
+
recovery?: unknown;
|
|
20
|
+
[key: string]: unknown;
|
|
21
|
+
};
|
|
22
|
+
export type TaskOperationCheckpoint = {
|
|
23
|
+
object: 'task_operation_watch_checkpoint';
|
|
24
|
+
schemaVersion: 1;
|
|
25
|
+
observedAt: string;
|
|
26
|
+
taskId: string;
|
|
27
|
+
operationId: string;
|
|
28
|
+
observation: 'observed' | 'timeout';
|
|
29
|
+
changed: boolean;
|
|
30
|
+
terminal: boolean;
|
|
31
|
+
result: 'pending' | 'succeeded' | 'failed' | 'unknown';
|
|
32
|
+
operation: TaskOperationProjection | null;
|
|
33
|
+
};
|
|
34
|
+
export type TaskOperationWatchOptions = {
|
|
35
|
+
taskId: string;
|
|
36
|
+
operationId: string;
|
|
37
|
+
intervalMs: number;
|
|
38
|
+
timeoutMs: number | null;
|
|
39
|
+
read: (taskId: string, operationId: string) => Promise<TaskOperationProjection>;
|
|
40
|
+
onCheckpoint: (checkpoint: TaskOperationCheckpoint) => void | Promise<void>;
|
|
41
|
+
sleep?: (milliseconds: number) => Promise<void>;
|
|
42
|
+
now?: () => number;
|
|
43
|
+
};
|
|
44
|
+
export type TaskOperationWatchResult = {
|
|
45
|
+
terminal: boolean;
|
|
46
|
+
result: 'succeeded' | 'failed';
|
|
47
|
+
operation: TaskOperationProjection;
|
|
48
|
+
};
|
|
49
|
+
/** Parse the same bounded duration grammar used by `tasks watch`. */
|
|
50
|
+
export declare function parseTaskOperationWatchDuration(value: string | undefined): number | null;
|
|
51
|
+
/**
|
|
52
|
+
* Classify only server-declared terminal status or the narrow, stable terminal
|
|
53
|
+
* vocabulary. An absent/malformed/unknown projection never becomes success by
|
|
54
|
+
* inference, and neither does a Task's own workflow status.
|
|
55
|
+
*/
|
|
56
|
+
export declare function classifyTaskOperation(operation: TaskOperationProjection | null): {
|
|
57
|
+
terminal: boolean;
|
|
58
|
+
result: 'pending' | 'succeeded' | 'failed' | 'unknown';
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Poll one immutable operation until it reaches an authoritative terminal
|
|
62
|
+
* result. Every read is emitted as a checkpoint so JSON consumers can prove
|
|
63
|
+
* liveness even where a server does not yet offer an event stream.
|
|
64
|
+
*/
|
|
65
|
+
export declare function watchTaskOperation(options: TaskOperationWatchOptions): Promise<TaskOperationWatchResult>;
|
|
66
|
+
export declare function taskOperationCheckpointLine(checkpoint: TaskOperationCheckpoint): string;
|