orizu 0.5.40 → 0.5.41
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/dataset-clone-workflow.d.ts +11 -0
- package/dist/dataset-clone-workflow.d.ts.map +1 -0
- package/dist/dataset-clone-workflow.js +100 -0
- package/dist/dataset-commands.d.ts.map +1 -1
- package/dist/dataset-commands.js +6 -29
- package/dist/dataset-transfer-upload.d.ts +2 -6
- package/dist/dataset-transfer-upload.d.ts.map +1 -1
- package/dist/dataset-transfer-upload.js +9 -2
- package/dist/dataset-upload-snapshot.d.ts.map +1 -1
- package/dist/dataset-upload-snapshot.js +7 -1
- package/dist/dataset-version-workflow.d.ts +24 -0
- package/dist/dataset-version-workflow.d.ts.map +1 -0
- package/dist/dataset-version-workflow.js +99 -0
- package/dist/feedback-cli.js +3 -3
- package/dist/hosted-harness-opencode.d.ts +1 -1
- package/dist/hosted-harness-opencode.js +7 -7
- package/dist/hosted-loop-lifecycle.d.ts +3 -2
- package/dist/hosted-loop-lifecycle.d.ts.map +1 -1
- package/dist/hosted-loop-lifecycle.js +3 -2
- package/dist/hosted-loop.d.ts +7 -0
- package/dist/hosted-loop.d.ts.map +1 -1
- package/dist/hosted-loop.js +14 -6
- package/dist/index.js +2 -2
- package/dist/last-error-record.d.ts +4 -2
- package/dist/last-error-record.d.ts.map +1 -1
- package/dist/last-error-record.js +10 -68
- package/dist/last-error-workflow.d.ts +4 -0
- package/dist/last-error-workflow.d.ts.map +1 -0
- package/dist/last-error-workflow.js +78 -0
- package/dist/task-commands.d.ts.map +1 -1
- package/dist/task-commands.js +9 -16
- package/dist/task-report-workflow.d.ts +47 -0
- package/dist/task-report-workflow.d.ts.map +1 -0
- package/dist/task-report-workflow.js +160 -0
- package/package.json +1 -1
- package/src/dataset-clone-workflow.ts +100 -0
- package/src/dataset-commands.ts +6 -53
- package/src/dataset-transfer-upload.ts +12 -5
- package/src/dataset-upload-snapshot.ts +8 -1
- package/src/dataset-version-workflow.ts +105 -0
- package/src/feedback-cli.ts +4 -4
- package/src/hosted-harness-opencode.ts +7 -7
- package/src/hosted-loop-lifecycle.ts +3 -2
- package/src/hosted-loop.ts +14 -6
- package/src/index.ts +2 -2
- package/src/last-error-record.ts +11 -85
- package/src/last-error-workflow.ts +121 -0
- package/src/task-commands.ts +9 -43
- package/src/task-report-workflow.ts +179 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { randomBytes } from 'crypto'
|
|
2
|
+
import {
|
|
3
|
+
chmodSync,
|
|
4
|
+
closeSync,
|
|
5
|
+
fsyncSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
openSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
renameSync,
|
|
10
|
+
rmSync,
|
|
11
|
+
statSync,
|
|
12
|
+
writeFileSync,
|
|
13
|
+
} from 'fs'
|
|
14
|
+
import { join } from 'path'
|
|
15
|
+
import { Effect, Schema } from 'effect'
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
LAST_ERROR_ARG_MAX_CHARS,
|
|
19
|
+
LAST_ERROR_CODE_MAX_CHARS,
|
|
20
|
+
LAST_ERROR_FILE_MAX_BYTES,
|
|
21
|
+
LAST_ERROR_MAX_ARGS,
|
|
22
|
+
LAST_ERROR_MESSAGE_MAX_CHARS,
|
|
23
|
+
} from './last-error-caps.js'
|
|
24
|
+
import {
|
|
25
|
+
boundedServerBaseUrl,
|
|
26
|
+
isStringWithinCap,
|
|
27
|
+
LAST_ERROR_FILENAME,
|
|
28
|
+
LAST_ERROR_RECORD_VERSION,
|
|
29
|
+
type LastErrorRecord,
|
|
30
|
+
} from './last-error-record.js'
|
|
31
|
+
|
|
32
|
+
// ORI-2450: Effect owns the last-error file's publication and reading.
|
|
33
|
+
// last-error-record.ts loads this module with a literal dynamic import, so the
|
|
34
|
+
// npm CLI only loads Effect on a failure or a feedback read, and the source
|
|
35
|
+
// bundle still includes it.
|
|
36
|
+
|
|
37
|
+
class LastErrorFileFailure extends Error {
|
|
38
|
+
constructor(readonly operation: string) {
|
|
39
|
+
super(`Could not ${operation}`)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const file = <A>(operation: string, action: () => A) => Effect.try({
|
|
44
|
+
try: action,
|
|
45
|
+
catch: () => new LastErrorFileFailure(operation),
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const NullableString = Schema.NullOr(Schema.String)
|
|
49
|
+
|
|
50
|
+
const LastErrorRecordSchema = Schema.Struct({
|
|
51
|
+
version: Schema.Literal(LAST_ERROR_RECORD_VERSION),
|
|
52
|
+
recordedAt: Schema.String.check(Schema.makeFilter(value => Number.isFinite(Date.parse(value)))),
|
|
53
|
+
cliVersion: NullableString,
|
|
54
|
+
code: Schema.optionalKey(NullableString.check(Schema.makeFilter(value =>
|
|
55
|
+
value === null || Array.from(value).length <= LAST_ERROR_CODE_MAX_CHARS))),
|
|
56
|
+
serverBaseUrl: Schema.optionalKey(NullableString),
|
|
57
|
+
teamSlug: Schema.optionalKey(NullableString),
|
|
58
|
+
command: NullableString,
|
|
59
|
+
argv: Schema.Array(Schema.String),
|
|
60
|
+
argvTruncated: Schema.Boolean,
|
|
61
|
+
message: Schema.String,
|
|
62
|
+
messageTruncated: Schema.Boolean,
|
|
63
|
+
}).check(Schema.makeFilter(record =>
|
|
64
|
+
(record.command === null || isStringWithinCap(record.command, LAST_ERROR_ARG_MAX_CHARS, record.argvTruncated))
|
|
65
|
+
&& record.argv.length <= LAST_ERROR_MAX_ARGS
|
|
66
|
+
&& record.argv.every(argument => isStringWithinCap(argument, LAST_ERROR_ARG_MAX_CHARS, record.argvTruncated))
|
|
67
|
+
&& isStringWithinCap(record.message, LAST_ERROR_MESSAGE_MAX_CHARS, record.messageTruncated)))
|
|
68
|
+
|
|
69
|
+
const publication = (configDirectory: string, serialized: string) => Effect.gen(function*() {
|
|
70
|
+
yield* file('create directory', () => mkdirSync(configDirectory, { recursive: true, mode: 0o700 }))
|
|
71
|
+
yield* file('protect directory', () => chmodSync(configDirectory, 0o700))
|
|
72
|
+
const path = join(configDirectory, LAST_ERROR_FILENAME)
|
|
73
|
+
// Write a private temporary file, then rename it over the record, so a
|
|
74
|
+
// reader never sees a half-written record and a failed write keeps the old one.
|
|
75
|
+
yield* Effect.acquireUseRelease(
|
|
76
|
+
Effect.sync(() => join(
|
|
77
|
+
configDirectory,
|
|
78
|
+
`.${LAST_ERROR_FILENAME}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`
|
|
79
|
+
)),
|
|
80
|
+
temporaryPath => Effect.gen(function*() {
|
|
81
|
+
yield* Effect.acquireUseRelease(
|
|
82
|
+
file('open temporary record', () => openSync(temporaryPath, 'wx', 0o600)),
|
|
83
|
+
descriptor => Effect.gen(function*() {
|
|
84
|
+
yield* file('write temporary record', () => writeFileSync(descriptor, serialized, 'utf8'))
|
|
85
|
+
yield* file('flush temporary record', () => fsyncSync(descriptor))
|
|
86
|
+
}),
|
|
87
|
+
descriptor => Effect.sync(() => closeSync(descriptor)),
|
|
88
|
+
)
|
|
89
|
+
yield* file('protect temporary record', () => chmodSync(temporaryPath, 0o600))
|
|
90
|
+
yield* file('publish record', () => renameSync(temporaryPath, path))
|
|
91
|
+
yield* file('protect published record', () => chmodSync(path, 0o600))
|
|
92
|
+
}),
|
|
93
|
+
temporaryPath => Effect.sync(() => rmSync(temporaryPath, { force: true })),
|
|
94
|
+
)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
export function publishLastErrorRecord(configDirectory: string, serialized: string): Promise<void> {
|
|
98
|
+
return Effect.runPromise(publication(configDirectory, serialized))
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const loading = (path: string) => Effect.gen(function*() {
|
|
102
|
+
const stat = yield* file('inspect record', () => statSync(path))
|
|
103
|
+
if (stat.size > LAST_ERROR_FILE_MAX_BYTES) return null
|
|
104
|
+
const text = yield* file('read record', () => readFileSync(path, 'utf8'))
|
|
105
|
+
const raw = yield* file('parse record', () => JSON.parse(text) as unknown)
|
|
106
|
+
const record = yield* Schema.decodeUnknownEffect(LastErrorRecordSchema)(raw)
|
|
107
|
+
// The Schema checks the known fields; the reader has always kept any others.
|
|
108
|
+
const admitted: LastErrorRecord = {
|
|
109
|
+
...(raw as Record<string, unknown>),
|
|
110
|
+
...record,
|
|
111
|
+
argv: [...record.argv],
|
|
112
|
+
code: record.code ?? null,
|
|
113
|
+
serverBaseUrl: boundedServerBaseUrl(record.serverBaseUrl),
|
|
114
|
+
teamSlug: record.teamSlug ?? null,
|
|
115
|
+
}
|
|
116
|
+
return admitted
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
export function loadLastErrorRecord(path: string): Promise<LastErrorRecord | null> {
|
|
120
|
+
return Effect.runPromise(loading(path).pipe(Effect.orElseSucceed(() => null)))
|
|
121
|
+
}
|
package/src/task-commands.ts
CHANGED
|
@@ -26,20 +26,6 @@ import type { TaskStatusPayload } from './account-directory.js'
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
interface TaskReportPayload {
|
|
30
|
-
task: {
|
|
31
|
-
id: string
|
|
32
|
-
title?: string | null
|
|
33
|
-
status?: string | null
|
|
34
|
-
report: {
|
|
35
|
-
markdown: string
|
|
36
|
-
sourceName: string | null
|
|
37
|
-
createdAt: string | null
|
|
38
|
-
updatedAt?: string | null
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
29
|
function readTaskReportInput(): { markdown: string; sourceName: string | null } | null {
|
|
44
30
|
return readMarkdownReportInput(cliArgs, 'Task')
|
|
45
31
|
}
|
|
@@ -619,30 +605,14 @@ async function setTaskReport() {
|
|
|
619
605
|
)
|
|
620
606
|
}
|
|
621
607
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
}),
|
|
629
|
-
})
|
|
630
|
-
|
|
631
|
-
if (!response.ok) {
|
|
632
|
-
throw new Error(`Failed to update task report: ${await response.text()}`)
|
|
608
|
+
// Loaded lazily: the workflow pulls in Effect (ORI-2408).
|
|
609
|
+
const workflow = await import('./task-report-workflow.js')
|
|
610
|
+
const outcome = await workflow.setTaskReport(taskId, report)
|
|
611
|
+
if (outcome.kind === 'interrupted') {
|
|
612
|
+
process.exitCode = outcome.exitCode
|
|
613
|
+
return
|
|
633
614
|
}
|
|
634
|
-
|
|
635
|
-
const data = await parseJsonResponse<{
|
|
636
|
-
task: {
|
|
637
|
-
id: string
|
|
638
|
-
status: string
|
|
639
|
-
report: {
|
|
640
|
-
markdown: string
|
|
641
|
-
sourceName: string | null
|
|
642
|
-
createdAt: string | null
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
}>(response, 'Task report update')
|
|
615
|
+
const data = outcome.reply
|
|
646
616
|
|
|
647
617
|
if (hasJsonFlag()) {
|
|
648
618
|
printJson(data)
|
|
@@ -662,12 +632,8 @@ async function getTaskReport() {
|
|
|
662
632
|
throw new Error('Usage: orizu tasks report get --task <taskId> [--json]')
|
|
663
633
|
}
|
|
664
634
|
|
|
665
|
-
const
|
|
666
|
-
|
|
667
|
-
throw new Error(`Failed to read task report: ${await response.text()}`)
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
const data = await parseJsonResponse<TaskReportPayload>(response, 'Task report get')
|
|
635
|
+
const workflow = await import('./task-report-workflow.js')
|
|
636
|
+
const data = await workflow.getTaskReport(taskId)
|
|
671
637
|
if (hasJsonFlag()) {
|
|
672
638
|
printJson(data as unknown as Record<string, unknown>)
|
|
673
639
|
return
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { Cause, Effect, Exit, Result, Schema } from 'effect'
|
|
2
|
+
|
|
3
|
+
import { datasetTransferTimeoutMs } from './dataset-transfer-policy.js'
|
|
4
|
+
import { isJsonErrorResponsePayload } from './error-response.js'
|
|
5
|
+
import { authedFetch } from './http.js'
|
|
6
|
+
import { parseJsonResponse } from './json-response.js'
|
|
7
|
+
|
|
8
|
+
// Loaded only through `await import(...)` from task-commands. One request per
|
|
9
|
+
// command, never retried, with the same deadline as the dataset workflows.
|
|
10
|
+
|
|
11
|
+
const mayBeSaved = (taskId: string) =>
|
|
12
|
+
`The task report may already be saved: check with \`orizu tasks report get --task ${taskId}\` before setting it again.`
|
|
13
|
+
|
|
14
|
+
const SetReply = Schema.Struct({
|
|
15
|
+
task: Schema.Struct({
|
|
16
|
+
id: Schema.String,
|
|
17
|
+
status: Schema.String,
|
|
18
|
+
report: Schema.Struct({
|
|
19
|
+
markdown: Schema.String,
|
|
20
|
+
sourceName: Schema.NullOr(Schema.String),
|
|
21
|
+
createdAt: Schema.NullOr(Schema.String),
|
|
22
|
+
}),
|
|
23
|
+
}),
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
// title, status and updatedAt stay optional, as in the payload type this replaced.
|
|
27
|
+
const GetReply = Schema.Struct({
|
|
28
|
+
task: Schema.Struct({
|
|
29
|
+
id: Schema.String,
|
|
30
|
+
title: Schema.optionalKey(Schema.NullOr(Schema.String)),
|
|
31
|
+
status: Schema.optionalKey(Schema.NullOr(Schema.String)),
|
|
32
|
+
report: Schema.Struct({
|
|
33
|
+
markdown: Schema.String,
|
|
34
|
+
sourceName: Schema.NullOr(Schema.String),
|
|
35
|
+
createdAt: Schema.NullOr(Schema.String),
|
|
36
|
+
updatedAt: Schema.optionalKey(Schema.NullOr(Schema.String)),
|
|
37
|
+
}),
|
|
38
|
+
}),
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
type TaskReportSetReply = typeof SetReply.Type
|
|
42
|
+
export type TaskReportGetReply = typeof GetReply.Type
|
|
43
|
+
|
|
44
|
+
// `mayBeSaved` (the #2164 rule): an error reply in the app's own `{ error }`
|
|
45
|
+
// shape is reported plainly, whatever its status. A lost, stalled or unreadable
|
|
46
|
+
// reply, a 5xx that did not come from the app (a gateway giving up while the
|
|
47
|
+
// app kept working), or a reply of the wrong shape means it may have been saved.
|
|
48
|
+
class ReportRequestFailure extends Error {
|
|
49
|
+
constructor(message: string, readonly mayBeSaved: boolean, cause?: unknown) { super(message, { cause }) }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function isAppError(response: Response, text: string): boolean {
|
|
53
|
+
try {
|
|
54
|
+
return isJsonErrorResponsePayload(response, JSON.parse(text))
|
|
55
|
+
} catch {
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** One request with a deadline. Returns the reply as parsed once `accepts` agrees with its shape. */
|
|
61
|
+
function reportRequest(input: {
|
|
62
|
+
taskId: string
|
|
63
|
+
init: RequestInit
|
|
64
|
+
failurePrefix: string
|
|
65
|
+
label: string
|
|
66
|
+
accepts: (data: unknown) => boolean
|
|
67
|
+
deadlineMessage: string
|
|
68
|
+
timeoutMs: number
|
|
69
|
+
onSent?: () => void
|
|
70
|
+
}) {
|
|
71
|
+
return Effect.suspend(() => {
|
|
72
|
+
let sent = false
|
|
73
|
+
let timedOut = false
|
|
74
|
+
return Effect.tryPromise({
|
|
75
|
+
try: async signal => {
|
|
76
|
+
const request = new AbortController()
|
|
77
|
+
const abort = () => request.abort()
|
|
78
|
+
const timer = setTimeout(() => { timedOut = true; request.abort() }, input.timeoutMs)
|
|
79
|
+
if (signal.aborted) abort()
|
|
80
|
+
else signal.addEventListener('abort', abort, { once: true })
|
|
81
|
+
try {
|
|
82
|
+
const response = await authedFetch(
|
|
83
|
+
`/api/cli/tasks/${encodeURIComponent(input.taskId)}/report`,
|
|
84
|
+
{ ...input.init, signal: request.signal },
|
|
85
|
+
() => { sent = true; input.onSent?.() }
|
|
86
|
+
)
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
const text = await response.text()
|
|
89
|
+
const fromApp = isAppError(response, text)
|
|
90
|
+
throw new ReportRequestFailure(`${input.failurePrefix}: ${text}`, response.status < 400 || (response.status >= 500 && !fromApp))
|
|
91
|
+
}
|
|
92
|
+
const data = await parseJsonResponse<unknown>(response, input.label)
|
|
93
|
+
if (!input.accepts(data)) {
|
|
94
|
+
throw new ReportRequestFailure(`${input.label} returned an unexpected response (status ${response.status}).`, true)
|
|
95
|
+
}
|
|
96
|
+
return data
|
|
97
|
+
} finally {
|
|
98
|
+
clearTimeout(timer)
|
|
99
|
+
signal.removeEventListener('abort', abort)
|
|
100
|
+
request.abort()
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
catch: cause => {
|
|
104
|
+
if (timedOut) return new ReportRequestFailure(input.deadlineMessage, sent, cause)
|
|
105
|
+
if (cause instanceof ReportRequestFailure) return cause
|
|
106
|
+
return new ReportRequestFailure(cause instanceof Error ? cause.message : String(cause), sent, cause)
|
|
107
|
+
},
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const accepts = (schema: typeof SetReply | typeof GetReply) => (data: unknown) => Result.isSuccess(Schema.decodeUnknownResult(schema)(data))
|
|
113
|
+
|
|
114
|
+
export type TaskReportSetOutcome =
|
|
115
|
+
| { kind: 'saved'; reply: TaskReportSetReply }
|
|
116
|
+
| { kind: 'interrupted'; exitCode: 130 | 143 }
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* `orizu tasks report set`. Ctrl-C or SIGTERM aborts the request and, once it
|
|
120
|
+
* was sent, says the report may already be saved: the server finishes on its
|
|
121
|
+
* own. Other failures throw, adding the same line when the outcome is unknown.
|
|
122
|
+
*/
|
|
123
|
+
export async function setTaskReport(taskId: string, report: { markdown: string; sourceName: string | null }): Promise<TaskReportSetOutcome> {
|
|
124
|
+
const timeoutMs = datasetTransferTimeoutMs()
|
|
125
|
+
const controller = new AbortController()
|
|
126
|
+
let interrupted: 130 | 143 | undefined
|
|
127
|
+
let sent = false
|
|
128
|
+
const interrupt = () => { interrupted ??= 130; controller.abort() }
|
|
129
|
+
const terminate = () => { interrupted ??= 143; controller.abort() }
|
|
130
|
+
process.on('SIGINT', interrupt)
|
|
131
|
+
process.on('SIGTERM', terminate)
|
|
132
|
+
try {
|
|
133
|
+
const exit = await Effect.runPromiseExit(reportRequest({
|
|
134
|
+
taskId,
|
|
135
|
+
init: {
|
|
136
|
+
method: 'PATCH',
|
|
137
|
+
headers: { 'Content-Type': 'application/json' },
|
|
138
|
+
body: JSON.stringify({ reportMarkdown: report.markdown, reportSourceName: report.sourceName }),
|
|
139
|
+
},
|
|
140
|
+
failurePrefix: 'Failed to update task report',
|
|
141
|
+
label: 'Task report update',
|
|
142
|
+
accepts: accepts(SetReply),
|
|
143
|
+
deadlineMessage: 'Task report save exceeded its deadline',
|
|
144
|
+
timeoutMs,
|
|
145
|
+
onSent: () => { sent = true },
|
|
146
|
+
}), { signal: controller.signal })
|
|
147
|
+
if (interrupted !== undefined) {
|
|
148
|
+
process.stderr.write(sent ? `Task report save interrupted. ${mayBeSaved(taskId)}\n` : 'Task report save interrupted.\n')
|
|
149
|
+
return { kind: 'interrupted', exitCode: interrupted }
|
|
150
|
+
}
|
|
151
|
+
if (Exit.isSuccess(exit)) return { kind: 'saved', reply: exit.value as TaskReportSetReply }
|
|
152
|
+
throw withMayBeSaved(Cause.squash(exit.cause), taskId)
|
|
153
|
+
} finally {
|
|
154
|
+
process.removeListener('SIGINT', interrupt)
|
|
155
|
+
process.removeListener('SIGTERM', terminate)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function withMayBeSaved(failure: unknown, taskId: string): unknown {
|
|
160
|
+
if (!(failure instanceof ReportRequestFailure)) return failure
|
|
161
|
+
const message = failure.mayBeSaved ? `${failure.message}\n${mayBeSaved(taskId)}` : failure.message
|
|
162
|
+
return new Error(message, { cause: failure.cause })
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** `orizu tasks report get`: one read with a deadline. Signals keep Node's default handling. */
|
|
166
|
+
export async function getTaskReport(taskId: string): Promise<TaskReportGetReply> {
|
|
167
|
+
const exit = await Effect.runPromiseExit(reportRequest({
|
|
168
|
+
taskId,
|
|
169
|
+
init: {},
|
|
170
|
+
failurePrefix: 'Failed to read task report',
|
|
171
|
+
label: 'Task report get',
|
|
172
|
+
accepts: accepts(GetReply),
|
|
173
|
+
deadlineMessage: 'Task report read exceeded its deadline',
|
|
174
|
+
timeoutMs: datasetTransferTimeoutMs(),
|
|
175
|
+
}))
|
|
176
|
+
if (Exit.isSuccess(exit)) return exit.value as TaskReportGetReply
|
|
177
|
+
const failure = Cause.squash(exit.cause)
|
|
178
|
+
throw failure instanceof ReportRequestFailure ? new Error(failure.message, { cause: failure.cause }) : failure
|
|
179
|
+
}
|