orizu 0.5.39 → 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/README.md +77 -0
- 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 +3 -1
- package/dist/dataset-commands.d.ts.map +1 -1
- package/dist/dataset-commands.js +107 -91
- package/dist/dataset-delete-workflow.d.ts +4 -0
- package/dist/dataset-delete-workflow.d.ts.map +1 -0
- package/dist/dataset-delete-workflow.js +75 -0
- package/dist/dataset-lock-workflow.d.ts +12 -0
- package/dist/dataset-lock-workflow.d.ts.map +1 -0
- package/dist/dataset-lock-workflow.js +76 -0
- package/dist/dataset-readme-workflow.d.ts +13 -0
- package/dist/dataset-readme-workflow.d.ts.map +1 -0
- package/dist/dataset-readme-workflow.js +82 -0
- package/dist/dataset-row-workflow.d.ts +14 -0
- package/dist/dataset-row-workflow.d.ts.map +1 -0
- package/dist/dataset-row-workflow.js +202 -0
- package/dist/dataset-split-reads.d.ts +28 -0
- package/dist/dataset-split-reads.d.ts.map +1 -0
- package/dist/dataset-split-reads.js +47 -0
- 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/help.d.ts.map +1 -1
- package/dist/help.js +29 -3
- package/dist/hosted-boot.d.ts +1 -0
- package/dist/hosted-boot.d.ts.map +1 -1
- package/dist/hosted-boot.js +17 -0
- package/dist/hosted-harness-opencode.d.ts +5 -6
- package/dist/hosted-harness-opencode.d.ts.map +1 -1
- package/dist/hosted-harness-opencode.js +73 -59
- package/dist/hosted-loop-lifecycle.d.ts +6 -11
- package/dist/hosted-loop-lifecycle.d.ts.map +1 -1
- package/dist/hosted-loop-lifecycle.js +6 -11
- package/dist/hosted-loop.d.ts +9 -0
- package/dist/hosted-loop.d.ts.map +1 -1
- package/dist/hosted-loop.js +19 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -3
- package/dist/instruction-delivery.d.ts +14 -0
- package/dist/instruction-delivery.d.ts.map +1 -0
- package/dist/instruction-delivery.js +103 -0
- package/dist/instruction-set-sync/index.d.ts +17 -0
- package/dist/instruction-set-sync/index.d.ts.map +1 -1
- package/dist/instruction-set-sync/index.js +44 -17
- package/dist/instruction-set-update/index.d.ts +2 -2
- package/dist/instruction-set-update/index.d.ts.map +1 -1
- package/dist/instruction-set-update/index.js +2 -2
- package/dist/instruction-sets-cli.d.ts.map +1 -1
- package/dist/instruction-sets-cli.js +19 -27
- package/dist/json-response.d.ts +1 -0
- package/dist/json-response.d.ts.map +1 -1
- package/dist/json-response.js +7 -0
- 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/preview-runtime.d.ts +2 -2
- package/dist/preview-runtime.d.ts.map +1 -1
- package/dist/preview-runtime.js +37 -7
- package/dist/task-commands.d.ts.map +1 -1
- package/dist/task-commands.js +9 -16
- package/dist/task-export-workflow.d.ts +4 -0
- package/dist/task-export-workflow.d.ts.map +1 -0
- package/dist/task-export-workflow.js +46 -0
- 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/dist/workbench-cli.js +1 -1
- package/package.json +1 -1
- package/src/dataset-clone-workflow.ts +100 -0
- package/src/dataset-commands.ts +95 -142
- package/src/dataset-delete-workflow.ts +72 -0
- package/src/dataset-lock-workflow.ts +74 -0
- package/src/dataset-readme-workflow.ts +80 -0
- package/src/dataset-row-workflow.ts +248 -0
- package/src/dataset-split-reads.ts +43 -0
- 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/help.ts +29 -3
- package/src/hosted-boot.ts +16 -1
- package/src/hosted-harness-opencode.ts +74 -72
- package/src/hosted-loop-lifecycle.ts +6 -11
- package/src/hosted-loop.ts +20 -10
- package/src/hosted-model-cache.d.mts +2 -0
- package/src/hosted-model-cache.mjs +53 -0
- package/src/index.ts +12 -2
- package/src/instruction-delivery.ts +123 -0
- package/src/instruction-set-sync/index.ts +61 -14
- package/src/instruction-set-update/index.ts +4 -3
- package/src/instruction-sets-cli.ts +19 -27
- package/src/json-response.ts +8 -0
- package/src/last-error-record.ts +11 -85
- package/src/last-error-workflow.ts +121 -0
- package/src/preview-runtime.ts +40 -10
- package/src/task-commands.ts +9 -43
- package/src/task-export-workflow.ts +56 -0
- package/src/task-report-workflow.ts +179 -0
- package/src/workbench-cli.ts +1 -1
- package/vendor/skills/orizu/references/cli-reference.md +34 -0
- package/vendor/skills/orizu/references/dataset-design.md +1 -1
- package/dist/dataset-edit-rows.d.ts +0 -10
- package/dist/dataset-edit-rows.d.ts.map +0 -1
- package/dist/dataset-edit-rows.js +0 -90
- package/src/dataset-edit-rows.ts +0 -124
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,56 @@
|
|
|
1
|
+
import { Effect, Exit, Result } from 'effect'
|
|
2
|
+
import { renameSync, rmSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import { basename, dirname, join } from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { authedFetch } from './http.js'
|
|
6
|
+
|
|
7
|
+
export type TaskExportFormat = 'csv' | 'json' | 'jsonl'
|
|
8
|
+
|
|
9
|
+
// Node's "CODE: description, syscall 'path'" without the syscall and path, so a
|
|
10
|
+
// failed save names the user's target rather than the temp file.
|
|
11
|
+
function saveFailureReason(cause: unknown): string {
|
|
12
|
+
const message = cause instanceof Error ? cause.message : String(cause)
|
|
13
|
+
return message.replace(/, \w+( '.*')?$/, '')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Writes the export beside `target` and renames it over the target, so a failed
|
|
18
|
+
* or interrupted save never touches the file the user already has. The temp file
|
|
19
|
+
* is removed whenever the save does not finish.
|
|
20
|
+
*/
|
|
21
|
+
function saveExportFile(target: string, bytes: Uint8Array) {
|
|
22
|
+
const temp = join(dirname(target), `.${basename(target)}.${process.pid}.tmp`)
|
|
23
|
+
return Effect.acquireUseRelease(
|
|
24
|
+
Effect.succeed(temp),
|
|
25
|
+
path => Effect.try({
|
|
26
|
+
try: () => {
|
|
27
|
+
writeFileSync(path, bytes, { flag: 'wx' })
|
|
28
|
+
renameSync(path, target)
|
|
29
|
+
},
|
|
30
|
+
catch: cause => new Error(`Could not save export to ${target}: ${saveFailureReason(cause)}`, { cause }),
|
|
31
|
+
}),
|
|
32
|
+
(path, exit) => Exit.isSuccess(exit) ? Effect.void : Effect.sync(() => rmSync(path, { force: true }))
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* `orizu tasks export`: one GET of the export, never retried, then a safe save.
|
|
38
|
+
* A refused export fails with `Download failed: <body>` before anything is written.
|
|
39
|
+
*/
|
|
40
|
+
function downloadTaskExport(taskId: string, format: TaskExportFormat, target: string) {
|
|
41
|
+
return Effect.gen(function* () {
|
|
42
|
+
const response = yield* Effect.promise(() => authedFetch(`/api/cli/tasks/${taskId}/export?format=${format}`))
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
const body = yield* Effect.promise(() => response.text())
|
|
45
|
+
return yield* Effect.fail(new Error(`Download failed: ${body}`))
|
|
46
|
+
}
|
|
47
|
+
const bytes = new Uint8Array(yield* Effect.promise(() => response.arrayBuffer()))
|
|
48
|
+
yield* saveExportFile(target, bytes)
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Promise runner for the command: rejects with the message the CLI prints. */
|
|
53
|
+
export async function runTaskExportDownload(taskId: string, format: TaskExportFormat, target: string): Promise<void> {
|
|
54
|
+
const result = await Effect.runPromise(Effect.result(downloadTaskExport(taskId, format, target)))
|
|
55
|
+
if (Result.isFailure(result)) throw result.failure
|
|
56
|
+
}
|
|
@@ -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
|
+
}
|
package/src/workbench-cli.ts
CHANGED
|
@@ -1176,7 +1176,7 @@ function formatSessionFinishOutcome(result: SessionFinishResult): string {
|
|
|
1176
1176
|
// The web page's failed line, with the reason the server mapped (ORI-2264).
|
|
1177
1177
|
function failedRunLine(run: { status: string; failureReason?: unknown }): string | null {
|
|
1178
1178
|
return run.status === 'failed' && typeof run.failureReason === 'string' && run.failureReason
|
|
1179
|
-
? `The agent stopped: ${run.failureReason}.`
|
|
1179
|
+
? run.failureReason === 'The selected model is unavailable in this session.' ? run.failureReason : `The agent stopped: ${run.failureReason}.`
|
|
1180
1180
|
: null
|
|
1181
1181
|
}
|
|
1182
1182
|
|
|
@@ -351,6 +351,40 @@ orizu datasets lock --dataset <datasetId|datasetUrl> --reason "Finalize for labe
|
|
|
351
351
|
orizu datasets clone --dataset <datasetId|datasetUrl> --name "Batch 1 Copy"
|
|
352
352
|
```
|
|
353
353
|
|
|
354
|
+
For validation-first enrichment, start with an explicit immutable version ID
|
|
355
|
+
returned by upload or version creation. Discover the saved names, export the
|
|
356
|
+
assignments, then download only the selected partition:
|
|
357
|
+
|
|
358
|
+
```bash
|
|
359
|
+
orizu datasets splits list DATASET_VERSION_ID --json
|
|
360
|
+
orizu datasets splits show SPLIT_SET_ID --json > split.json
|
|
361
|
+
orizu datasets download --dataset-version DATASET_VERSION_ID --split-set SPLIT_SET_ID --split validation --format jsonl --out validation.jsonl
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
The download retains canonical `id` values and existing fields in saved partition
|
|
365
|
+
order; `--format json` is also supported. Custom partition names and empty splits
|
|
366
|
+
are valid. Pinned reads remain unchanged after live row edits and require no
|
|
367
|
+
prompt, runner, scorer or optimization. Unknown/inaccessible or mismatched
|
|
368
|
+
selectors and incomplete artifacts fail without live-data fallback.
|
|
369
|
+
|
|
370
|
+
For saved split names beginning with `--`, use the unambiguous equals spelling:
|
|
371
|
+
`orizu datasets download --dataset-version DATASET_VERSION_ID --split-set SPLIT_SET_ID --split=--validation --format jsonl --out validation.jsonl`.
|
|
372
|
+
The same spelling supports `--split=--json` and `--split=--help`; all pinned selectors remain required.
|
|
373
|
+
|
|
374
|
+
Enrich rows outside Orizu, preserving every canonical `id`. Assemble the complete
|
|
375
|
+
enriched dataset, including other partitions, and upload or version it. Import
|
|
376
|
+
assignments into the explicit new version:
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
orizu datasets splits create TARGET_DATASET_VERSION_ID --from-file split.json --json
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
Show JSON preserves `name`, `strategy`, `seed`, `metadata`, and
|
|
383
|
+
`partitions: [{name, row_ids, metadata}]`. Set and partition metadata retain
|
|
384
|
+
their JSON values, including arrays, scalars and null. Source identity never overrides the command's
|
|
385
|
+
target. Every referenced ID must exist in the target; missing IDs fail rather
|
|
386
|
+
than being dropped or remapped. Reads follow existing project permissions.
|
|
387
|
+
|
|
354
388
|
Dataset version and split-set commands used by prompt runs are covered in `prompt-control-plane.md`.
|
|
355
389
|
|
|
356
390
|
### Model configs
|
|
@@ -73,7 +73,7 @@ orizu datasets splits create <upload-returned-dataset-version-id> --from-file ./
|
|
|
73
73
|
|
|
74
74
|
Reserve `orizu datasets versions create` for a subsequent snapshot, and give it a new label such as `v2`; labels are unique within a dataset. The dataset README should record source approval, source locators, sampling decisions, deduplication rules, the coverage table, ground-truth status, and the split rationale.
|
|
75
75
|
|
|
76
|
-
The dataset version freezes its rows.
|
|
76
|
+
The dataset version freezes its rows. After creating the split set, re-read its saved membership with `orizu datasets splits show <split-set-id> --json` and confirm it matches the reviewed local split file before relying on it; the saved split set, not the local file, is the canonical record. Do not mutate that server-side split set; pass the recorded `--dataset-version-id` and `--split-set-id` to `orizu optimizations run-gepa`.
|
|
77
77
|
|
|
78
78
|
## Guard live rows before annotation and version later data
|
|
79
79
|
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export interface DatasetEditRowsOptions {
|
|
2
|
-
datasetId: string;
|
|
3
|
-
file: string;
|
|
4
|
-
json: boolean;
|
|
5
|
-
printJson: (value: Record<string, unknown>) => void;
|
|
6
|
-
printLine: (value: string) => void;
|
|
7
|
-
sanitize: (value: string) => string;
|
|
8
|
-
}
|
|
9
|
-
export declare function editDatasetRows(options: DatasetEditRowsOptions): Promise<void>;
|
|
10
|
-
//# sourceMappingURL=dataset-edit-rows.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dataset-edit-rows.d.ts","sourceRoot":"","sources":["../src/dataset-edit-rows.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,OAAO,CAAA;IACb,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;IACnD,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;CACpC;AAsDD,wBAAsB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+CpF"}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { extractErrorMessage } from './error-response.js';
|
|
2
|
-
import { parseDatasetFile } from './file-parser.js';
|
|
3
|
-
import { authedFetch } from './http.js';
|
|
4
|
-
const MAX_REQUEST_BYTES = 3 * 1024 * 1024;
|
|
5
|
-
const MAX_CHUNK_ROWS = 10;
|
|
6
|
-
const EMPTY_REQUEST_BYTES = Buffer.byteLength('{"rows":[]}', 'utf8');
|
|
7
|
-
function normalizeRows(rows) {
|
|
8
|
-
if (rows.length === 0) {
|
|
9
|
-
throw new Error('Dataset edit file must contain at least one row');
|
|
10
|
-
}
|
|
11
|
-
const seenRowIds = new Set();
|
|
12
|
-
return rows.map((row, index) => {
|
|
13
|
-
const rowId = typeof row.id === 'string' ? row.id.trim() : '';
|
|
14
|
-
if (!rowId) {
|
|
15
|
-
throw new Error(`Dataset edit file rows[${index}] must include a non-empty string id`);
|
|
16
|
-
}
|
|
17
|
-
if (seenRowIds.has(rowId)) {
|
|
18
|
-
throw new Error(`Dataset edit file rows[${index}].id duplicates a previous row id`);
|
|
19
|
-
}
|
|
20
|
-
seenRowIds.add(rowId);
|
|
21
|
-
return { ...row, id: rowId };
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
function chunkRows(rows, sanitize) {
|
|
25
|
-
const chunks = [];
|
|
26
|
-
let currentChunk = [];
|
|
27
|
-
let currentBytes = EMPTY_REQUEST_BYTES;
|
|
28
|
-
for (const row of rows) {
|
|
29
|
-
const rowBytes = Buffer.byteLength(JSON.stringify(row), 'utf8');
|
|
30
|
-
const framedRowBytes = rowBytes + (currentChunk.length > 0 ? 1 : 0);
|
|
31
|
-
if (currentChunk.length > 0 &&
|
|
32
|
-
(currentChunk.length >= MAX_CHUNK_ROWS || currentBytes + framedRowBytes > MAX_REQUEST_BYTES)) {
|
|
33
|
-
chunks.push(currentChunk);
|
|
34
|
-
currentChunk = [];
|
|
35
|
-
currentBytes = EMPTY_REQUEST_BYTES;
|
|
36
|
-
}
|
|
37
|
-
const singleRowRequestBytes = EMPTY_REQUEST_BYTES + rowBytes;
|
|
38
|
-
if (singleRowRequestBytes > MAX_REQUEST_BYTES) {
|
|
39
|
-
throw new Error(`Dataset edit row ${sanitize(row.id)} is ${singleRowRequestBytes} bytes, ` +
|
|
40
|
-
`which exceeds the ${MAX_REQUEST_BYTES}-byte edit request limit`);
|
|
41
|
-
}
|
|
42
|
-
currentChunk.push(row);
|
|
43
|
-
currentBytes += rowBytes + (currentChunk.length > 1 ? 1 : 0);
|
|
44
|
-
}
|
|
45
|
-
if (currentChunk.length > 0)
|
|
46
|
-
chunks.push(currentChunk);
|
|
47
|
-
return chunks;
|
|
48
|
-
}
|
|
49
|
-
export async function editDatasetRows(options) {
|
|
50
|
-
const { rows } = parseDatasetFile(options.file);
|
|
51
|
-
const chunks = chunkRows(normalizeRows(rows), options.sanitize);
|
|
52
|
-
let totalUpdated = 0;
|
|
53
|
-
let lastResult = null;
|
|
54
|
-
for (let index = 0; index < chunks.length; index += 1) {
|
|
55
|
-
const chunk = chunks[index];
|
|
56
|
-
if (!options.json && chunks.length > 1) {
|
|
57
|
-
options.printLine(`Updating chunk ${index + 1}/${chunks.length} (${chunk.length} rows)...`);
|
|
58
|
-
}
|
|
59
|
-
try {
|
|
60
|
-
const response = await authedFetch(`/api/cli/datasets/${encodeURIComponent(options.datasetId)}/rows`, {
|
|
61
|
-
method: 'PATCH',
|
|
62
|
-
headers: { 'Content-Type': 'application/json' },
|
|
63
|
-
body: JSON.stringify({ rows: chunk }),
|
|
64
|
-
});
|
|
65
|
-
if (!response.ok) {
|
|
66
|
-
throw new Error(`HTTP ${response.status}: ${await extractErrorMessage(response)}`);
|
|
67
|
-
}
|
|
68
|
-
const data = await response.json();
|
|
69
|
-
if (!Number.isInteger(data.updatedCount) || data.updatedCount !== chunk.length) {
|
|
70
|
-
throw new Error(`Server reported ${String(data.updatedCount)} updated rows for a ${chunk.length}-row chunk`);
|
|
71
|
-
}
|
|
72
|
-
totalUpdated += data.updatedCount;
|
|
73
|
-
lastResult = data;
|
|
74
|
-
}
|
|
75
|
-
catch (error) {
|
|
76
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
77
|
-
throw new Error(`Chunk ${index + 1}/${chunks.length} failed: ${message}\n` +
|
|
78
|
-
`${totalUpdated} rows from ${index} chunk(s) were already updated. ` +
|
|
79
|
-
'Retry with a file containing only the remaining row edits.');
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
if (!lastResult)
|
|
83
|
-
throw new Error('Dataset edit file must contain at least one row');
|
|
84
|
-
if (options.json) {
|
|
85
|
-
options.printJson({ dataset: lastResult.dataset, updatedCount: totalUpdated });
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
options.printLine(`Updated ${totalUpdated} rows in dataset ${options.sanitize(lastResult.dataset.name)} ` +
|
|
89
|
-
`(${options.sanitize(lastResult.dataset.id)}). Current row count: ${lastResult.dataset.rowCount}`);
|
|
90
|
-
}
|
package/src/dataset-edit-rows.ts
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { extractErrorMessage } from './error-response.js'
|
|
2
|
-
import { parseDatasetFile } from './file-parser.js'
|
|
3
|
-
import { authedFetch } from './http.js'
|
|
4
|
-
|
|
5
|
-
const MAX_REQUEST_BYTES = 3 * 1024 * 1024
|
|
6
|
-
const MAX_CHUNK_ROWS = 10
|
|
7
|
-
const EMPTY_REQUEST_BYTES = Buffer.byteLength('{"rows":[]}', 'utf8')
|
|
8
|
-
|
|
9
|
-
type DatasetRowEdit = Record<string, unknown> & { id: string }
|
|
10
|
-
|
|
11
|
-
type EditRowsResponse = {
|
|
12
|
-
dataset: { id: string; name: string; rowCount: number }
|
|
13
|
-
updatedCount: number
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface DatasetEditRowsOptions {
|
|
17
|
-
datasetId: string
|
|
18
|
-
file: string
|
|
19
|
-
json: boolean
|
|
20
|
-
printJson: (value: Record<string, unknown>) => void
|
|
21
|
-
printLine: (value: string) => void
|
|
22
|
-
sanitize: (value: string) => string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function normalizeRows(rows: Array<Record<string, unknown>>): DatasetRowEdit[] {
|
|
26
|
-
if (rows.length === 0) {
|
|
27
|
-
throw new Error('Dataset edit file must contain at least one row')
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const seenRowIds = new Set<string>()
|
|
31
|
-
return rows.map((row, index) => {
|
|
32
|
-
const rowId = typeof row.id === 'string' ? row.id.trim() : ''
|
|
33
|
-
if (!rowId) {
|
|
34
|
-
throw new Error(`Dataset edit file rows[${index}] must include a non-empty string id`)
|
|
35
|
-
}
|
|
36
|
-
if (seenRowIds.has(rowId)) {
|
|
37
|
-
throw new Error(`Dataset edit file rows[${index}].id duplicates a previous row id`)
|
|
38
|
-
}
|
|
39
|
-
seenRowIds.add(rowId)
|
|
40
|
-
return { ...row, id: rowId }
|
|
41
|
-
})
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function chunkRows(rows: DatasetRowEdit[], sanitize: (value: string) => string): DatasetRowEdit[][] {
|
|
45
|
-
const chunks: DatasetRowEdit[][] = []
|
|
46
|
-
let currentChunk: DatasetRowEdit[] = []
|
|
47
|
-
let currentBytes = EMPTY_REQUEST_BYTES
|
|
48
|
-
|
|
49
|
-
for (const row of rows) {
|
|
50
|
-
const rowBytes = Buffer.byteLength(JSON.stringify(row), 'utf8')
|
|
51
|
-
const framedRowBytes = rowBytes + (currentChunk.length > 0 ? 1 : 0)
|
|
52
|
-
if (
|
|
53
|
-
currentChunk.length > 0 &&
|
|
54
|
-
(currentChunk.length >= MAX_CHUNK_ROWS || currentBytes + framedRowBytes > MAX_REQUEST_BYTES)
|
|
55
|
-
) {
|
|
56
|
-
chunks.push(currentChunk)
|
|
57
|
-
currentChunk = []
|
|
58
|
-
currentBytes = EMPTY_REQUEST_BYTES
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const singleRowRequestBytes = EMPTY_REQUEST_BYTES + rowBytes
|
|
62
|
-
if (singleRowRequestBytes > MAX_REQUEST_BYTES) {
|
|
63
|
-
throw new Error(
|
|
64
|
-
`Dataset edit row ${sanitize(row.id)} is ${singleRowRequestBytes} bytes, ` +
|
|
65
|
-
`which exceeds the ${MAX_REQUEST_BYTES}-byte edit request limit`
|
|
66
|
-
)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
currentChunk.push(row)
|
|
70
|
-
currentBytes += rowBytes + (currentChunk.length > 1 ? 1 : 0)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (currentChunk.length > 0) chunks.push(currentChunk)
|
|
74
|
-
return chunks
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export async function editDatasetRows(options: DatasetEditRowsOptions): Promise<void> {
|
|
78
|
-
const { rows } = parseDatasetFile(options.file)
|
|
79
|
-
const chunks = chunkRows(normalizeRows(rows), options.sanitize)
|
|
80
|
-
let totalUpdated = 0
|
|
81
|
-
let lastResult: EditRowsResponse | null = null
|
|
82
|
-
|
|
83
|
-
for (let index = 0; index < chunks.length; index += 1) {
|
|
84
|
-
const chunk = chunks[index]
|
|
85
|
-
if (!options.json && chunks.length > 1) {
|
|
86
|
-
options.printLine(`Updating chunk ${index + 1}/${chunks.length} (${chunk.length} rows)...`)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
const response = await authedFetch(`/api/cli/datasets/${encodeURIComponent(options.datasetId)}/rows`, {
|
|
91
|
-
method: 'PATCH',
|
|
92
|
-
headers: { 'Content-Type': 'application/json' },
|
|
93
|
-
body: JSON.stringify({ rows: chunk }),
|
|
94
|
-
})
|
|
95
|
-
if (!response.ok) {
|
|
96
|
-
throw new Error(`HTTP ${response.status}: ${await extractErrorMessage(response)}`)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const data = await response.json() as EditRowsResponse
|
|
100
|
-
if (!Number.isInteger(data.updatedCount) || data.updatedCount !== chunk.length) {
|
|
101
|
-
throw new Error(`Server reported ${String(data.updatedCount)} updated rows for a ${chunk.length}-row chunk`)
|
|
102
|
-
}
|
|
103
|
-
totalUpdated += data.updatedCount
|
|
104
|
-
lastResult = data
|
|
105
|
-
} catch (error) {
|
|
106
|
-
const message = error instanceof Error ? error.message : String(error)
|
|
107
|
-
throw new Error(
|
|
108
|
-
`Chunk ${index + 1}/${chunks.length} failed: ${message}\n` +
|
|
109
|
-
`${totalUpdated} rows from ${index} chunk(s) were already updated. ` +
|
|
110
|
-
'Retry with a file containing only the remaining row edits.'
|
|
111
|
-
)
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (!lastResult) throw new Error('Dataset edit file must contain at least one row')
|
|
116
|
-
if (options.json) {
|
|
117
|
-
options.printJson({ dataset: lastResult.dataset, updatedCount: totalUpdated })
|
|
118
|
-
return
|
|
119
|
-
}
|
|
120
|
-
options.printLine(
|
|
121
|
-
`Updated ${totalUpdated} rows in dataset ${options.sanitize(lastResult.dataset.name)} ` +
|
|
122
|
-
`(${options.sanitize(lastResult.dataset.id)}). Current row count: ${lastResult.dataset.rowCount}`
|
|
123
|
-
)
|
|
124
|
-
}
|