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
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { randomBytes } from 'crypto';
|
|
2
|
-
import { chmodSync, closeSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from 'fs';
|
|
3
1
|
import { homedir } from 'os';
|
|
4
2
|
import { join } from 'path';
|
|
5
3
|
import { getConfigDir, getSecretsSeenThisProcess, resolveAuthTokenForBaseUrl, resolveEnvBearerToken, } from './credentials.js';
|
|
@@ -154,7 +152,7 @@ function resolveHomeDir() {
|
|
|
154
152
|
return null;
|
|
155
153
|
}
|
|
156
154
|
}
|
|
157
|
-
function isStringWithinCap(value, maximumCharacters, wasTruncated) {
|
|
155
|
+
export function isStringWithinCap(value, maximumCharacters, wasTruncated) {
|
|
158
156
|
if (typeof value !== 'string')
|
|
159
157
|
return false;
|
|
160
158
|
if (value.length <= maximumCharacters)
|
|
@@ -163,41 +161,11 @@ function isStringWithinCap(value, maximumCharacters, wasTruncated) {
|
|
|
163
161
|
&& value.length <= maximumCharacters + LAST_ERROR_TRUNCATION_MARKER.length
|
|
164
162
|
&& value.endsWith(LAST_ERROR_TRUNCATION_MARKER);
|
|
165
163
|
}
|
|
166
|
-
function boundedServerBaseUrl(value) {
|
|
164
|
+
export function boundedServerBaseUrl(value) {
|
|
167
165
|
return value && Buffer.byteLength(value, 'utf8') <= LAST_ERROR_SERVER_BASE_URL_MAX_BYTES
|
|
168
166
|
? value
|
|
169
167
|
: null;
|
|
170
168
|
}
|
|
171
|
-
function isLastErrorRecord(value) {
|
|
172
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
173
|
-
return false;
|
|
174
|
-
const record = value;
|
|
175
|
-
if (record.version !== LAST_ERROR_RECORD_VERSION)
|
|
176
|
-
return false;
|
|
177
|
-
if (typeof record.recordedAt !== 'string' || !Number.isFinite(Date.parse(record.recordedAt)))
|
|
178
|
-
return false;
|
|
179
|
-
if (typeof record.cliVersion !== 'string' && record.cliVersion !== null)
|
|
180
|
-
return false;
|
|
181
|
-
if (record.code !== undefined && record.code !== null && (typeof record.code !== 'string'
|
|
182
|
-
|| Array.from(record.code).length > LAST_ERROR_CODE_MAX_CHARS))
|
|
183
|
-
return false;
|
|
184
|
-
if (record.serverBaseUrl !== undefined && typeof record.serverBaseUrl !== 'string' && record.serverBaseUrl !== null)
|
|
185
|
-
return false;
|
|
186
|
-
if (record.teamSlug !== undefined && typeof record.teamSlug !== 'string' && record.teamSlug !== null)
|
|
187
|
-
return false;
|
|
188
|
-
if (typeof record.argvTruncated !== 'boolean')
|
|
189
|
-
return false;
|
|
190
|
-
const argvTruncated = record.argvTruncated;
|
|
191
|
-
if (record.command !== null && !isStringWithinCap(record.command, LAST_ERROR_ARG_MAX_CHARS, argvTruncated))
|
|
192
|
-
return false;
|
|
193
|
-
if (!Array.isArray(record.argv) || record.argv.length > LAST_ERROR_MAX_ARGS)
|
|
194
|
-
return false;
|
|
195
|
-
if (!record.argv.every(argument => isStringWithinCap(argument, LAST_ERROR_ARG_MAX_CHARS, argvTruncated)))
|
|
196
|
-
return false;
|
|
197
|
-
if (typeof record.messageTruncated !== 'boolean')
|
|
198
|
-
return false;
|
|
199
|
-
return isStringWithinCap(record.message, LAST_ERROR_MESSAGE_MAX_CHARS, record.messageTruncated);
|
|
200
|
-
}
|
|
201
169
|
export function buildLastErrorRecord(input) {
|
|
202
170
|
const context = {
|
|
203
171
|
homeDir: input.homeDir ?? null,
|
|
@@ -255,11 +223,9 @@ export function buildLastErrorRecord(input) {
|
|
|
255
223
|
export function getLastErrorRecordPath(configDir) {
|
|
256
224
|
return join(configDir ?? getConfigDir(), LAST_ERROR_FILENAME);
|
|
257
225
|
}
|
|
258
|
-
export function writeLastErrorRecord(input, options = {}) {
|
|
226
|
+
export async function writeLastErrorRecord(input, options = {}) {
|
|
259
227
|
try {
|
|
260
228
|
const configDirectory = options.configDir ?? getConfigDir();
|
|
261
|
-
mkdirSync(configDirectory, { recursive: true, mode: 0o700 });
|
|
262
|
-
chmodSync(configDirectory, 0o700);
|
|
263
229
|
const record = buildLastErrorRecord({
|
|
264
230
|
...input,
|
|
265
231
|
homeDir: input.homeDir === undefined ? resolveHomeDir() : input.homeDir,
|
|
@@ -272,43 +238,19 @@ export function writeLastErrorRecord(input, options = {}) {
|
|
|
272
238
|
const serialized = serializeRecord(record);
|
|
273
239
|
if (Buffer.byteLength(serialized, 'utf8') > LAST_ERROR_FILE_MAX_BYTES)
|
|
274
240
|
return;
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
try {
|
|
278
|
-
const fileDescriptor = openSync(temporaryPath, 'wx', 0o600);
|
|
279
|
-
try {
|
|
280
|
-
writeFileSync(fileDescriptor, serialized, 'utf8');
|
|
281
|
-
fsyncSync(fileDescriptor);
|
|
282
|
-
}
|
|
283
|
-
finally {
|
|
284
|
-
closeSync(fileDescriptor);
|
|
285
|
-
}
|
|
286
|
-
chmodSync(temporaryPath, 0o600);
|
|
287
|
-
renameSync(temporaryPath, path);
|
|
288
|
-
chmodSync(path, 0o600);
|
|
289
|
-
}
|
|
290
|
-
finally {
|
|
291
|
-
rmSync(temporaryPath, { force: true });
|
|
292
|
-
}
|
|
241
|
+
const { publishLastErrorRecord } = await import('./last-error-workflow.js');
|
|
242
|
+
await publishLastErrorRecord(configDirectory, serialized);
|
|
293
243
|
}
|
|
294
244
|
catch {
|
|
295
|
-
// A diagnostic record is best effort and must never replace the original CLI failure
|
|
245
|
+
// A diagnostic record is best effort and must never replace the original CLI failure,
|
|
246
|
+
// including when Effect cannot load (a packed CLI without its dependencies).
|
|
296
247
|
}
|
|
297
248
|
}
|
|
298
|
-
export function readLastErrorRecord(configDir) {
|
|
249
|
+
export async function readLastErrorRecord(configDir) {
|
|
299
250
|
try {
|
|
300
251
|
const path = getLastErrorRecordPath(configDir);
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const record = JSON.parse(readFileSync(path, 'utf8'));
|
|
304
|
-
return isLastErrorRecord(record)
|
|
305
|
-
? {
|
|
306
|
-
...record,
|
|
307
|
-
code: record.code ?? null,
|
|
308
|
-
serverBaseUrl: boundedServerBaseUrl(record.serverBaseUrl),
|
|
309
|
-
teamSlug: record.teamSlug ?? null,
|
|
310
|
-
}
|
|
311
|
-
: null;
|
|
252
|
+
const { loadLastErrorRecord } = await import('./last-error-workflow.js');
|
|
253
|
+
return await loadLastErrorRecord(path);
|
|
312
254
|
}
|
|
313
255
|
catch {
|
|
314
256
|
return null;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type LastErrorRecord } from './last-error-record.js';
|
|
2
|
+
export declare function publishLastErrorRecord(configDirectory: string, serialized: string): Promise<void>;
|
|
3
|
+
export declare function loadLastErrorRecord(path: string): Promise<LastErrorRecord | null>;
|
|
4
|
+
//# sourceMappingURL=last-error-workflow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"last-error-workflow.d.ts","sourceRoot":"","sources":["../src/last-error-workflow.ts"],"names":[],"mappings":"AAuBA,OAAO,EAKL,KAAK,eAAe,EACrB,MAAM,wBAAwB,CAAA;AAmE/B,wBAAgB,sBAAsB,CAAC,eAAe,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEjG;AAoBD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAEjF"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { randomBytes } from 'crypto';
|
|
2
|
+
import { chmodSync, closeSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { Effect, Schema } from 'effect';
|
|
5
|
+
import { LAST_ERROR_ARG_MAX_CHARS, LAST_ERROR_CODE_MAX_CHARS, LAST_ERROR_FILE_MAX_BYTES, LAST_ERROR_MAX_ARGS, LAST_ERROR_MESSAGE_MAX_CHARS, } from './last-error-caps.js';
|
|
6
|
+
import { boundedServerBaseUrl, isStringWithinCap, LAST_ERROR_FILENAME, LAST_ERROR_RECORD_VERSION, } from './last-error-record.js';
|
|
7
|
+
// ORI-2450: Effect owns the last-error file's publication and reading.
|
|
8
|
+
// last-error-record.ts loads this module with a literal dynamic import, so the
|
|
9
|
+
// npm CLI only loads Effect on a failure or a feedback read, and the source
|
|
10
|
+
// bundle still includes it.
|
|
11
|
+
class LastErrorFileFailure extends Error {
|
|
12
|
+
operation;
|
|
13
|
+
constructor(operation) {
|
|
14
|
+
super(`Could not ${operation}`);
|
|
15
|
+
this.operation = operation;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const file = (operation, action) => Effect.try({
|
|
19
|
+
try: action,
|
|
20
|
+
catch: () => new LastErrorFileFailure(operation),
|
|
21
|
+
});
|
|
22
|
+
const NullableString = Schema.NullOr(Schema.String);
|
|
23
|
+
const LastErrorRecordSchema = Schema.Struct({
|
|
24
|
+
version: Schema.Literal(LAST_ERROR_RECORD_VERSION),
|
|
25
|
+
recordedAt: Schema.String.check(Schema.makeFilter(value => Number.isFinite(Date.parse(value)))),
|
|
26
|
+
cliVersion: NullableString,
|
|
27
|
+
code: Schema.optionalKey(NullableString.check(Schema.makeFilter(value => value === null || Array.from(value).length <= LAST_ERROR_CODE_MAX_CHARS))),
|
|
28
|
+
serverBaseUrl: Schema.optionalKey(NullableString),
|
|
29
|
+
teamSlug: Schema.optionalKey(NullableString),
|
|
30
|
+
command: NullableString,
|
|
31
|
+
argv: Schema.Array(Schema.String),
|
|
32
|
+
argvTruncated: Schema.Boolean,
|
|
33
|
+
message: Schema.String,
|
|
34
|
+
messageTruncated: Schema.Boolean,
|
|
35
|
+
}).check(Schema.makeFilter(record => (record.command === null || isStringWithinCap(record.command, LAST_ERROR_ARG_MAX_CHARS, record.argvTruncated))
|
|
36
|
+
&& record.argv.length <= LAST_ERROR_MAX_ARGS
|
|
37
|
+
&& record.argv.every(argument => isStringWithinCap(argument, LAST_ERROR_ARG_MAX_CHARS, record.argvTruncated))
|
|
38
|
+
&& isStringWithinCap(record.message, LAST_ERROR_MESSAGE_MAX_CHARS, record.messageTruncated)));
|
|
39
|
+
const publication = (configDirectory, serialized) => Effect.gen(function* () {
|
|
40
|
+
yield* file('create directory', () => mkdirSync(configDirectory, { recursive: true, mode: 0o700 }));
|
|
41
|
+
yield* file('protect directory', () => chmodSync(configDirectory, 0o700));
|
|
42
|
+
const path = join(configDirectory, LAST_ERROR_FILENAME);
|
|
43
|
+
// Write a private temporary file, then rename it over the record, so a
|
|
44
|
+
// reader never sees a half-written record and a failed write keeps the old one.
|
|
45
|
+
yield* Effect.acquireUseRelease(Effect.sync(() => join(configDirectory, `.${LAST_ERROR_FILENAME}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`)), temporaryPath => Effect.gen(function* () {
|
|
46
|
+
yield* Effect.acquireUseRelease(file('open temporary record', () => openSync(temporaryPath, 'wx', 0o600)), descriptor => Effect.gen(function* () {
|
|
47
|
+
yield* file('write temporary record', () => writeFileSync(descriptor, serialized, 'utf8'));
|
|
48
|
+
yield* file('flush temporary record', () => fsyncSync(descriptor));
|
|
49
|
+
}), descriptor => Effect.sync(() => closeSync(descriptor)));
|
|
50
|
+
yield* file('protect temporary record', () => chmodSync(temporaryPath, 0o600));
|
|
51
|
+
yield* file('publish record', () => renameSync(temporaryPath, path));
|
|
52
|
+
yield* file('protect published record', () => chmodSync(path, 0o600));
|
|
53
|
+
}), temporaryPath => Effect.sync(() => rmSync(temporaryPath, { force: true })));
|
|
54
|
+
});
|
|
55
|
+
export function publishLastErrorRecord(configDirectory, serialized) {
|
|
56
|
+
return Effect.runPromise(publication(configDirectory, serialized));
|
|
57
|
+
}
|
|
58
|
+
const loading = (path) => Effect.gen(function* () {
|
|
59
|
+
const stat = yield* file('inspect record', () => statSync(path));
|
|
60
|
+
if (stat.size > LAST_ERROR_FILE_MAX_BYTES)
|
|
61
|
+
return null;
|
|
62
|
+
const text = yield* file('read record', () => readFileSync(path, 'utf8'));
|
|
63
|
+
const raw = yield* file('parse record', () => JSON.parse(text));
|
|
64
|
+
const record = yield* Schema.decodeUnknownEffect(LastErrorRecordSchema)(raw);
|
|
65
|
+
// The Schema checks the known fields; the reader has always kept any others.
|
|
66
|
+
const admitted = {
|
|
67
|
+
...raw,
|
|
68
|
+
...record,
|
|
69
|
+
argv: [...record.argv],
|
|
70
|
+
code: record.code ?? null,
|
|
71
|
+
serverBaseUrl: boundedServerBaseUrl(record.serverBaseUrl),
|
|
72
|
+
teamSlug: record.teamSlug ?? null,
|
|
73
|
+
};
|
|
74
|
+
return admitted;
|
|
75
|
+
});
|
|
76
|
+
export function loadLastErrorRecord(path) {
|
|
77
|
+
return Effect.runPromise(loading(path).pipe(Effect.orElseSucceed(() => null)));
|
|
78
|
+
}
|
|
@@ -44,8 +44,8 @@ interface PreviewPage {
|
|
|
44
44
|
}) => Promise<unknown>;
|
|
45
45
|
waitForTimeout: (timeout: number) => Promise<unknown>;
|
|
46
46
|
once?: (event: string, listener: () => void) => unknown;
|
|
47
|
-
on?: (event: string, listener: () => void) => unknown;
|
|
48
|
-
off?: (event: string, listener: () => void) => unknown;
|
|
47
|
+
on?: (event: string, listener: (error: Error) => void) => unknown;
|
|
48
|
+
off?: (event: string, listener: (error: Error) => void) => unknown;
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
51
51
|
* Removes line and block comments, walking left to right so a `//` or `/*`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preview-runtime.d.ts","sourceRoot":"","sources":["../src/preview-runtime.ts"],"names":[],"mappings":"AAeA,eAAO,MAAM,gCAAgC,IAAI,CAAA;AAGjD,eAAO,MAAM,uBAAuB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CA+C5D,CAAA;AAGD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACpC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACrC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAClC,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,UAAU,CAAC,EAAE,iBAAiB,CAAA;IAC9B,sBAAsB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACvC;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAA;IACX,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB;AAED,UAAU,KAAK;IACb,KAAK,EAAE,OAAO,GAAG,MAAM,CAAA;IACvB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,UAAU,iBAAiB;IACzB,QAAQ,EAAE;QACR,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,cAAc,CAAC,CAAA;KACtE,CAAA;CACF;AAED,UAAU,cAAc;IACtB,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,WAAW,CAAC,CAAA;IACnE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1B,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,KAAK,OAAO,CAAA;IACvD,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,KAAK,OAAO,CAAA;IACrD,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,KAAK,OAAO,CAAA;CACvD;AAED,UAAU,WAAW;IACnB,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IACzE,eAAe,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IACzF,UAAU,EAAE,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAC9E,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IACrD,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,KAAK,OAAO,CAAA;IACvD,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,
|
|
1
|
+
{"version":3,"file":"preview-runtime.d.ts","sourceRoot":"","sources":["../src/preview-runtime.ts"],"names":[],"mappings":"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"}
|
package/dist/preview-runtime.js
CHANGED
|
@@ -186,19 +186,32 @@ export async function runLocalAppPreview(options) {
|
|
|
186
186
|
headless: !options.headed,
|
|
187
187
|
...(chromiumExecutablePath ? { executablePath: chromiumExecutablePath } : {}),
|
|
188
188
|
});
|
|
189
|
-
|
|
189
|
+
let page = null;
|
|
190
|
+
let handleRenderError;
|
|
191
|
+
let hasFinishedKeepingOpen = false;
|
|
190
192
|
try {
|
|
191
|
-
await
|
|
192
|
-
|
|
193
|
+
page = await browser.newPage({ viewport: { width: 1440, height: 1000 } });
|
|
194
|
+
const renderError = new Promise((_, reject) => {
|
|
195
|
+
handleRenderError = error => reject(new Error(`App preview render failed: ${error.message}`));
|
|
196
|
+
page?.on?.('pageerror', handleRenderError);
|
|
197
|
+
});
|
|
198
|
+
// Listen before navigation: React can fail before the ready marker mounts.
|
|
199
|
+
await Promise.race([
|
|
200
|
+
page.goto(url, { waitUntil: 'networkidle' }).then(() => page.waitForSelector('[data-orizu-preview-ready="true"]', { timeout: 15_000 })),
|
|
201
|
+
renderError,
|
|
202
|
+
]);
|
|
193
203
|
if (options.screenshotPath) {
|
|
194
204
|
await page.screenshot({ path: options.screenshotPath, fullPage: true });
|
|
195
205
|
}
|
|
196
206
|
if (options.keepOpen) {
|
|
197
207
|
await waitForPreviewClose(page, browser);
|
|
208
|
+
hasFinishedKeepingOpen = true;
|
|
198
209
|
}
|
|
199
210
|
}
|
|
200
211
|
finally {
|
|
201
|
-
if (
|
|
212
|
+
if (handleRenderError)
|
|
213
|
+
page?.off?.('pageerror', handleRenderError);
|
|
214
|
+
if (!hasFinishedKeepingOpen) {
|
|
202
215
|
await browser.close();
|
|
203
216
|
}
|
|
204
217
|
}
|
|
@@ -712,7 +725,8 @@ export default TextContent
|
|
|
712
725
|
import React from 'react'
|
|
713
726
|
function cn(...values) { return values.filter(Boolean).join(' ') }
|
|
714
727
|
export function CodeBlock({ code, children, className, ...props }) {
|
|
715
|
-
|
|
728
|
+
const codeText = Array.isArray(code) ? code.map(line => line.content).join('\\n') : code
|
|
729
|
+
return <pre {...props} className={cn('overflow-auto rounded-md border bg-muted p-3 font-mono text-xs', className)}><code>{children ?? codeText}</code></pre>
|
|
716
730
|
}
|
|
717
731
|
export default CodeBlock
|
|
718
732
|
`;
|
|
@@ -727,8 +741,24 @@ export default Prose
|
|
|
727
741
|
const contentRendererSource = `
|
|
728
742
|
import React from 'react'
|
|
729
743
|
function cn(...values) { return values.filter(Boolean).join(' ') }
|
|
730
|
-
export function ContentRenderer({ content,
|
|
731
|
-
|
|
744
|
+
export function ContentRenderer({ contentType, content, maxHeight = '400px', customRenderer, className }) {
|
|
745
|
+
let renderedContent
|
|
746
|
+
switch (contentType) {
|
|
747
|
+
case 'image':
|
|
748
|
+
renderedContent = <div className="flex items-center justify-center p-2"><img src={content} alt="Content" width={800} height={600} className="max-w-full object-contain" style={{ maxHeight, height: 'auto', width: 'auto' }} /></div>
|
|
749
|
+
break
|
|
750
|
+
case 'video':
|
|
751
|
+
renderedContent = <div className="flex h-full justify-center p-2"><video src={content} controls className="max-h-full max-w-full" /></div>
|
|
752
|
+
break
|
|
753
|
+
case 'custom':
|
|
754
|
+
renderedContent = customRenderer ? customRenderer({ content }) : <p className="text-destructive">Custom renderer required for custom content type</p>
|
|
755
|
+
break
|
|
756
|
+
default:
|
|
757
|
+
renderedContent = <p>Invalid content type</p>
|
|
758
|
+
}
|
|
759
|
+
return maxHeight
|
|
760
|
+
? <div className={cn('overflow-auto', className)} style={{ maxHeight }}>{renderedContent}</div>
|
|
761
|
+
: renderedContent
|
|
732
762
|
}
|
|
733
763
|
export default ContentRenderer
|
|
734
764
|
`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-commands.d.ts","sourceRoot":"","sources":["../src/task-commands.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"task-commands.d.ts","sourceRoot":"","sources":["../src/task-commands.ts"],"names":[],"mappings":"AAgCA,iBAAe,SAAS,kBAWvB;AAED,iBAAS,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,EAAE,CAQ3D;AA6CD,iBAAe,UAAU,kBA0IxB;AAED,iBAAe,UAAU,kBA+GxB;AAED,iBAAe,WAAW,kBAkCzB;AAED,iBAAe,WAAW,kBAoEzB;AAED,iBAAe,UAAU,kBA8DxB;AAED,iBAAe,UAAU,kBA0CxB;AAED,iBAAe,gBAAgB,CAAC,YAAY,EAAE,QAAQ,GAAG,QAAQ,GAAG,WAAW,iBAwB9E;AAED,iBAAe,aAAa,kBAkC3B;AAED,iBAAe,aAAa,kBA2B3B;AAED,OAAO,EACL,mBAAmB,EACnB,SAAS,EACT,UAAU,EACV,UAAU,EACV,WAAW,EACX,WAAW,EACX,UAAU,EACV,UAAU,EACV,aAAa,EACb,aAAa,EACb,gBAAgB,GACjB,CAAA"}
|
package/dist/task-commands.js
CHANGED
|
@@ -461,18 +461,14 @@ async function setTaskReport() {
|
|
|
461
461
|
if (!report) {
|
|
462
462
|
throw new Error('Usage: orizu tasks report set --task <taskId> (--report <markdown|@file> | --report-file <path>) [--json]');
|
|
463
463
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
}),
|
|
471
|
-
});
|
|
472
|
-
if (!response.ok) {
|
|
473
|
-
throw new Error(`Failed to update task report: ${await response.text()}`);
|
|
464
|
+
// Loaded lazily: the workflow pulls in Effect (ORI-2408).
|
|
465
|
+
const workflow = await import('./task-report-workflow.js');
|
|
466
|
+
const outcome = await workflow.setTaskReport(taskId, report);
|
|
467
|
+
if (outcome.kind === 'interrupted') {
|
|
468
|
+
process.exitCode = outcome.exitCode;
|
|
469
|
+
return;
|
|
474
470
|
}
|
|
475
|
-
const data =
|
|
471
|
+
const data = outcome.reply;
|
|
476
472
|
if (hasJsonFlag()) {
|
|
477
473
|
printJson(data);
|
|
478
474
|
return;
|
|
@@ -486,11 +482,8 @@ async function getTaskReport() {
|
|
|
486
482
|
if (!taskId) {
|
|
487
483
|
throw new Error('Usage: orizu tasks report get --task <taskId> [--json]');
|
|
488
484
|
}
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
throw new Error(`Failed to read task report: ${await response.text()}`);
|
|
492
|
-
}
|
|
493
|
-
const data = await parseJsonResponse(response, 'Task report get');
|
|
485
|
+
const workflow = await import('./task-report-workflow.js');
|
|
486
|
+
const data = await workflow.getTaskReport(taskId);
|
|
494
487
|
if (hasJsonFlag()) {
|
|
495
488
|
printJson(data);
|
|
496
489
|
return;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type TaskExportFormat = 'csv' | 'json' | 'jsonl';
|
|
2
|
+
/** Promise runner for the command: rejects with the message the CLI prints. */
|
|
3
|
+
export declare function runTaskExportDownload(taskId: string, format: TaskExportFormat, target: string): Promise<void>;
|
|
4
|
+
//# sourceMappingURL=task-export-workflow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-export-workflow.d.ts","sourceRoot":"","sources":["../src/task-export-workflow.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,CAAA;AA6CvD,+EAA+E;AAC/E,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGnH"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Effect, Exit, Result } from 'effect';
|
|
2
|
+
import { renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { basename, dirname, join } from 'node:path';
|
|
4
|
+
import { authedFetch } from './http.js';
|
|
5
|
+
// Node's "CODE: description, syscall 'path'" without the syscall and path, so a
|
|
6
|
+
// failed save names the user's target rather than the temp file.
|
|
7
|
+
function saveFailureReason(cause) {
|
|
8
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
9
|
+
return message.replace(/, \w+( '.*')?$/, '');
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Writes the export beside `target` and renames it over the target, so a failed
|
|
13
|
+
* or interrupted save never touches the file the user already has. The temp file
|
|
14
|
+
* is removed whenever the save does not finish.
|
|
15
|
+
*/
|
|
16
|
+
function saveExportFile(target, bytes) {
|
|
17
|
+
const temp = join(dirname(target), `.${basename(target)}.${process.pid}.tmp`);
|
|
18
|
+
return Effect.acquireUseRelease(Effect.succeed(temp), path => Effect.try({
|
|
19
|
+
try: () => {
|
|
20
|
+
writeFileSync(path, bytes, { flag: 'wx' });
|
|
21
|
+
renameSync(path, target);
|
|
22
|
+
},
|
|
23
|
+
catch: cause => new Error(`Could not save export to ${target}: ${saveFailureReason(cause)}`, { cause }),
|
|
24
|
+
}), (path, exit) => Exit.isSuccess(exit) ? Effect.void : Effect.sync(() => rmSync(path, { force: true })));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* `orizu tasks export`: one GET of the export, never retried, then a safe save.
|
|
28
|
+
* A refused export fails with `Download failed: <body>` before anything is written.
|
|
29
|
+
*/
|
|
30
|
+
function downloadTaskExport(taskId, format, target) {
|
|
31
|
+
return Effect.gen(function* () {
|
|
32
|
+
const response = yield* Effect.promise(() => authedFetch(`/api/cli/tasks/${taskId}/export?format=${format}`));
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
const body = yield* Effect.promise(() => response.text());
|
|
35
|
+
return yield* Effect.fail(new Error(`Download failed: ${body}`));
|
|
36
|
+
}
|
|
37
|
+
const bytes = new Uint8Array(yield* Effect.promise(() => response.arrayBuffer()));
|
|
38
|
+
yield* saveExportFile(target, bytes);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/** Promise runner for the command: rejects with the message the CLI prints. */
|
|
42
|
+
export async function runTaskExportDownload(taskId, format, target) {
|
|
43
|
+
const result = await Effect.runPromise(Effect.result(downloadTaskExport(taskId, format, target)));
|
|
44
|
+
if (Result.isFailure(result))
|
|
45
|
+
throw result.failure;
|
|
46
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Schema } from 'effect';
|
|
2
|
+
declare const SetReply: Schema.Struct<{
|
|
3
|
+
readonly task: Schema.Struct<{
|
|
4
|
+
readonly id: Schema.String;
|
|
5
|
+
readonly status: Schema.String;
|
|
6
|
+
readonly report: Schema.Struct<{
|
|
7
|
+
readonly markdown: Schema.String;
|
|
8
|
+
readonly sourceName: Schema.NullOr<Schema.String>;
|
|
9
|
+
readonly createdAt: Schema.NullOr<Schema.String>;
|
|
10
|
+
}>;
|
|
11
|
+
}>;
|
|
12
|
+
}>;
|
|
13
|
+
declare const GetReply: Schema.Struct<{
|
|
14
|
+
readonly task: Schema.Struct<{
|
|
15
|
+
readonly id: Schema.String;
|
|
16
|
+
readonly title: Schema.optionalKey<Schema.NullOr<Schema.String>>;
|
|
17
|
+
readonly status: Schema.optionalKey<Schema.NullOr<Schema.String>>;
|
|
18
|
+
readonly report: Schema.Struct<{
|
|
19
|
+
readonly markdown: Schema.String;
|
|
20
|
+
readonly sourceName: Schema.NullOr<Schema.String>;
|
|
21
|
+
readonly createdAt: Schema.NullOr<Schema.String>;
|
|
22
|
+
readonly updatedAt: Schema.optionalKey<Schema.NullOr<Schema.String>>;
|
|
23
|
+
}>;
|
|
24
|
+
}>;
|
|
25
|
+
}>;
|
|
26
|
+
type TaskReportSetReply = typeof SetReply.Type;
|
|
27
|
+
export type TaskReportGetReply = typeof GetReply.Type;
|
|
28
|
+
export type TaskReportSetOutcome = {
|
|
29
|
+
kind: 'saved';
|
|
30
|
+
reply: TaskReportSetReply;
|
|
31
|
+
} | {
|
|
32
|
+
kind: 'interrupted';
|
|
33
|
+
exitCode: 130 | 143;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* `orizu tasks report set`. Ctrl-C or SIGTERM aborts the request and, once it
|
|
37
|
+
* was sent, says the report may already be saved: the server finishes on its
|
|
38
|
+
* own. Other failures throw, adding the same line when the outcome is unknown.
|
|
39
|
+
*/
|
|
40
|
+
export declare function setTaskReport(taskId: string, report: {
|
|
41
|
+
markdown: string;
|
|
42
|
+
sourceName: string | null;
|
|
43
|
+
}): Promise<TaskReportSetOutcome>;
|
|
44
|
+
/** `orizu tasks report get`: one read with a deadline. Signals keep Node's default handling. */
|
|
45
|
+
export declare function getTaskReport(taskId: string): Promise<TaskReportGetReply>;
|
|
46
|
+
export {};
|
|
47
|
+
//# sourceMappingURL=task-report-workflow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-report-workflow.d.ts","sourceRoot":"","sources":["../src/task-report-workflow.ts"],"names":[],"mappings":"AAAA,OAAO,EAA+B,MAAM,EAAE,MAAM,QAAQ,CAAA;AAa5D,QAAA,MAAM,QAAQ;;;;;;;;;;EAUZ,CAAA;AAGF,QAAA,MAAM,QAAQ;;;;;;;;;;;;EAYZ,CAAA;AAEF,KAAK,kBAAkB,GAAG,OAAO,QAAQ,CAAC,IAAI,CAAA;AAC9C,MAAM,MAAM,kBAAkB,GAAG,OAAO,QAAQ,CAAC,IAAI,CAAA;AAwErD,MAAM,MAAM,oBAAoB,GAC5B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,kBAAkB,CAAA;CAAE,GAC5C;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,GAAG,GAAG,GAAG,CAAA;CAAE,CAAA;AAEhD;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAkC1I;AAQD,gGAAgG;AAChG,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAa/E"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { Cause, Effect, Exit, Result, Schema } from 'effect';
|
|
2
|
+
import { datasetTransferTimeoutMs } from './dataset-transfer-policy.js';
|
|
3
|
+
import { isJsonErrorResponsePayload } from './error-response.js';
|
|
4
|
+
import { authedFetch } from './http.js';
|
|
5
|
+
import { parseJsonResponse } from './json-response.js';
|
|
6
|
+
// Loaded only through `await import(...)` from task-commands. One request per
|
|
7
|
+
// command, never retried, with the same deadline as the dataset workflows.
|
|
8
|
+
const mayBeSaved = (taskId) => `The task report may already be saved: check with \`orizu tasks report get --task ${taskId}\` before setting it again.`;
|
|
9
|
+
const SetReply = Schema.Struct({
|
|
10
|
+
task: Schema.Struct({
|
|
11
|
+
id: Schema.String,
|
|
12
|
+
status: Schema.String,
|
|
13
|
+
report: Schema.Struct({
|
|
14
|
+
markdown: Schema.String,
|
|
15
|
+
sourceName: Schema.NullOr(Schema.String),
|
|
16
|
+
createdAt: Schema.NullOr(Schema.String),
|
|
17
|
+
}),
|
|
18
|
+
}),
|
|
19
|
+
});
|
|
20
|
+
// title, status and updatedAt stay optional, as in the payload type this replaced.
|
|
21
|
+
const GetReply = Schema.Struct({
|
|
22
|
+
task: Schema.Struct({
|
|
23
|
+
id: Schema.String,
|
|
24
|
+
title: Schema.optionalKey(Schema.NullOr(Schema.String)),
|
|
25
|
+
status: Schema.optionalKey(Schema.NullOr(Schema.String)),
|
|
26
|
+
report: Schema.Struct({
|
|
27
|
+
markdown: Schema.String,
|
|
28
|
+
sourceName: Schema.NullOr(Schema.String),
|
|
29
|
+
createdAt: Schema.NullOr(Schema.String),
|
|
30
|
+
updatedAt: Schema.optionalKey(Schema.NullOr(Schema.String)),
|
|
31
|
+
}),
|
|
32
|
+
}),
|
|
33
|
+
});
|
|
34
|
+
// `mayBeSaved` (the #2164 rule): an error reply in the app's own `{ error }`
|
|
35
|
+
// shape is reported plainly, whatever its status. A lost, stalled or unreadable
|
|
36
|
+
// reply, a 5xx that did not come from the app (a gateway giving up while the
|
|
37
|
+
// app kept working), or a reply of the wrong shape means it may have been saved.
|
|
38
|
+
class ReportRequestFailure extends Error {
|
|
39
|
+
mayBeSaved;
|
|
40
|
+
constructor(message, mayBeSaved, cause) {
|
|
41
|
+
super(message, { cause });
|
|
42
|
+
this.mayBeSaved = mayBeSaved;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function isAppError(response, text) {
|
|
46
|
+
try {
|
|
47
|
+
return isJsonErrorResponsePayload(response, JSON.parse(text));
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** One request with a deadline. Returns the reply as parsed once `accepts` agrees with its shape. */
|
|
54
|
+
function reportRequest(input) {
|
|
55
|
+
return Effect.suspend(() => {
|
|
56
|
+
let sent = false;
|
|
57
|
+
let timedOut = false;
|
|
58
|
+
return Effect.tryPromise({
|
|
59
|
+
try: async (signal) => {
|
|
60
|
+
const request = new AbortController();
|
|
61
|
+
const abort = () => request.abort();
|
|
62
|
+
const timer = setTimeout(() => { timedOut = true; request.abort(); }, input.timeoutMs);
|
|
63
|
+
if (signal.aborted)
|
|
64
|
+
abort();
|
|
65
|
+
else
|
|
66
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
67
|
+
try {
|
|
68
|
+
const response = await authedFetch(`/api/cli/tasks/${encodeURIComponent(input.taskId)}/report`, { ...input.init, signal: request.signal }, () => { sent = true; input.onSent?.(); });
|
|
69
|
+
if (!response.ok) {
|
|
70
|
+
const text = await response.text();
|
|
71
|
+
const fromApp = isAppError(response, text);
|
|
72
|
+
throw new ReportRequestFailure(`${input.failurePrefix}: ${text}`, response.status < 400 || (response.status >= 500 && !fromApp));
|
|
73
|
+
}
|
|
74
|
+
const data = await parseJsonResponse(response, input.label);
|
|
75
|
+
if (!input.accepts(data)) {
|
|
76
|
+
throw new ReportRequestFailure(`${input.label} returned an unexpected response (status ${response.status}).`, true);
|
|
77
|
+
}
|
|
78
|
+
return data;
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
clearTimeout(timer);
|
|
82
|
+
signal.removeEventListener('abort', abort);
|
|
83
|
+
request.abort();
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
catch: cause => {
|
|
87
|
+
if (timedOut)
|
|
88
|
+
return new ReportRequestFailure(input.deadlineMessage, sent, cause);
|
|
89
|
+
if (cause instanceof ReportRequestFailure)
|
|
90
|
+
return cause;
|
|
91
|
+
return new ReportRequestFailure(cause instanceof Error ? cause.message : String(cause), sent, cause);
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
const accepts = (schema) => (data) => Result.isSuccess(Schema.decodeUnknownResult(schema)(data));
|
|
97
|
+
/**
|
|
98
|
+
* `orizu tasks report set`. Ctrl-C or SIGTERM aborts the request and, once it
|
|
99
|
+
* was sent, says the report may already be saved: the server finishes on its
|
|
100
|
+
* own. Other failures throw, adding the same line when the outcome is unknown.
|
|
101
|
+
*/
|
|
102
|
+
export async function setTaskReport(taskId, report) {
|
|
103
|
+
const timeoutMs = datasetTransferTimeoutMs();
|
|
104
|
+
const controller = new AbortController();
|
|
105
|
+
let interrupted;
|
|
106
|
+
let sent = false;
|
|
107
|
+
const interrupt = () => { interrupted ??= 130; controller.abort(); };
|
|
108
|
+
const terminate = () => { interrupted ??= 143; controller.abort(); };
|
|
109
|
+
process.on('SIGINT', interrupt);
|
|
110
|
+
process.on('SIGTERM', terminate);
|
|
111
|
+
try {
|
|
112
|
+
const exit = await Effect.runPromiseExit(reportRequest({
|
|
113
|
+
taskId,
|
|
114
|
+
init: {
|
|
115
|
+
method: 'PATCH',
|
|
116
|
+
headers: { 'Content-Type': 'application/json' },
|
|
117
|
+
body: JSON.stringify({ reportMarkdown: report.markdown, reportSourceName: report.sourceName }),
|
|
118
|
+
},
|
|
119
|
+
failurePrefix: 'Failed to update task report',
|
|
120
|
+
label: 'Task report update',
|
|
121
|
+
accepts: accepts(SetReply),
|
|
122
|
+
deadlineMessage: 'Task report save exceeded its deadline',
|
|
123
|
+
timeoutMs,
|
|
124
|
+
onSent: () => { sent = true; },
|
|
125
|
+
}), { signal: controller.signal });
|
|
126
|
+
if (interrupted !== undefined) {
|
|
127
|
+
process.stderr.write(sent ? `Task report save interrupted. ${mayBeSaved(taskId)}\n` : 'Task report save interrupted.\n');
|
|
128
|
+
return { kind: 'interrupted', exitCode: interrupted };
|
|
129
|
+
}
|
|
130
|
+
if (Exit.isSuccess(exit))
|
|
131
|
+
return { kind: 'saved', reply: exit.value };
|
|
132
|
+
throw withMayBeSaved(Cause.squash(exit.cause), taskId);
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
process.removeListener('SIGINT', interrupt);
|
|
136
|
+
process.removeListener('SIGTERM', terminate);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function withMayBeSaved(failure, taskId) {
|
|
140
|
+
if (!(failure instanceof ReportRequestFailure))
|
|
141
|
+
return failure;
|
|
142
|
+
const message = failure.mayBeSaved ? `${failure.message}\n${mayBeSaved(taskId)}` : failure.message;
|
|
143
|
+
return new Error(message, { cause: failure.cause });
|
|
144
|
+
}
|
|
145
|
+
/** `orizu tasks report get`: one read with a deadline. Signals keep Node's default handling. */
|
|
146
|
+
export async function getTaskReport(taskId) {
|
|
147
|
+
const exit = await Effect.runPromiseExit(reportRequest({
|
|
148
|
+
taskId,
|
|
149
|
+
init: {},
|
|
150
|
+
failurePrefix: 'Failed to read task report',
|
|
151
|
+
label: 'Task report get',
|
|
152
|
+
accepts: accepts(GetReply),
|
|
153
|
+
deadlineMessage: 'Task report read exceeded its deadline',
|
|
154
|
+
timeoutMs: datasetTransferTimeoutMs(),
|
|
155
|
+
}));
|
|
156
|
+
if (Exit.isSuccess(exit))
|
|
157
|
+
return exit.value;
|
|
158
|
+
const failure = Cause.squash(exit.cause);
|
|
159
|
+
throw failure instanceof ReportRequestFailure ? new Error(failure.message, { cause: failure.cause }) : failure;
|
|
160
|
+
}
|
package/dist/workbench-cli.js
CHANGED
|
@@ -803,7 +803,7 @@ function formatSessionFinishOutcome(result) {
|
|
|
803
803
|
// The web page's failed line, with the reason the server mapped (ORI-2264).
|
|
804
804
|
function failedRunLine(run) {
|
|
805
805
|
return run.status === 'failed' && typeof run.failureReason === 'string' && run.failureReason
|
|
806
|
-
? `The agent stopped: ${run.failureReason}.`
|
|
806
|
+
? run.failureReason === 'The selected model is unavailable in this session.' ? run.failureReason : `The agent stopped: ${run.failureReason}.`
|
|
807
807
|
: null;
|
|
808
808
|
}
|
|
809
809
|
function formatRunStatus(run) {
|