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.
Files changed (117) hide show
  1. package/README.md +77 -0
  2. package/dist/dataset-clone-workflow.d.ts +11 -0
  3. package/dist/dataset-clone-workflow.d.ts.map +1 -0
  4. package/dist/dataset-clone-workflow.js +100 -0
  5. package/dist/dataset-commands.d.ts +3 -1
  6. package/dist/dataset-commands.d.ts.map +1 -1
  7. package/dist/dataset-commands.js +107 -91
  8. package/dist/dataset-delete-workflow.d.ts +4 -0
  9. package/dist/dataset-delete-workflow.d.ts.map +1 -0
  10. package/dist/dataset-delete-workflow.js +75 -0
  11. package/dist/dataset-lock-workflow.d.ts +12 -0
  12. package/dist/dataset-lock-workflow.d.ts.map +1 -0
  13. package/dist/dataset-lock-workflow.js +76 -0
  14. package/dist/dataset-readme-workflow.d.ts +13 -0
  15. package/dist/dataset-readme-workflow.d.ts.map +1 -0
  16. package/dist/dataset-readme-workflow.js +82 -0
  17. package/dist/dataset-row-workflow.d.ts +14 -0
  18. package/dist/dataset-row-workflow.d.ts.map +1 -0
  19. package/dist/dataset-row-workflow.js +202 -0
  20. package/dist/dataset-split-reads.d.ts +28 -0
  21. package/dist/dataset-split-reads.d.ts.map +1 -0
  22. package/dist/dataset-split-reads.js +47 -0
  23. package/dist/dataset-transfer-upload.d.ts +2 -6
  24. package/dist/dataset-transfer-upload.d.ts.map +1 -1
  25. package/dist/dataset-transfer-upload.js +9 -2
  26. package/dist/dataset-upload-snapshot.d.ts.map +1 -1
  27. package/dist/dataset-upload-snapshot.js +7 -1
  28. package/dist/dataset-version-workflow.d.ts +24 -0
  29. package/dist/dataset-version-workflow.d.ts.map +1 -0
  30. package/dist/dataset-version-workflow.js +99 -0
  31. package/dist/feedback-cli.js +3 -3
  32. package/dist/help.d.ts.map +1 -1
  33. package/dist/help.js +29 -3
  34. package/dist/hosted-boot.d.ts +1 -0
  35. package/dist/hosted-boot.d.ts.map +1 -1
  36. package/dist/hosted-boot.js +17 -0
  37. package/dist/hosted-harness-opencode.d.ts +5 -6
  38. package/dist/hosted-harness-opencode.d.ts.map +1 -1
  39. package/dist/hosted-harness-opencode.js +73 -59
  40. package/dist/hosted-loop-lifecycle.d.ts +6 -11
  41. package/dist/hosted-loop-lifecycle.d.ts.map +1 -1
  42. package/dist/hosted-loop-lifecycle.js +6 -11
  43. package/dist/hosted-loop.d.ts +9 -0
  44. package/dist/hosted-loop.d.ts.map +1 -1
  45. package/dist/hosted-loop.js +19 -10
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +11 -3
  48. package/dist/instruction-delivery.d.ts +14 -0
  49. package/dist/instruction-delivery.d.ts.map +1 -0
  50. package/dist/instruction-delivery.js +103 -0
  51. package/dist/instruction-set-sync/index.d.ts +17 -0
  52. package/dist/instruction-set-sync/index.d.ts.map +1 -1
  53. package/dist/instruction-set-sync/index.js +44 -17
  54. package/dist/instruction-set-update/index.d.ts +2 -2
  55. package/dist/instruction-set-update/index.d.ts.map +1 -1
  56. package/dist/instruction-set-update/index.js +2 -2
  57. package/dist/instruction-sets-cli.d.ts.map +1 -1
  58. package/dist/instruction-sets-cli.js +19 -27
  59. package/dist/json-response.d.ts +1 -0
  60. package/dist/json-response.d.ts.map +1 -1
  61. package/dist/json-response.js +7 -0
  62. package/dist/last-error-record.d.ts +4 -2
  63. package/dist/last-error-record.d.ts.map +1 -1
  64. package/dist/last-error-record.js +10 -68
  65. package/dist/last-error-workflow.d.ts +4 -0
  66. package/dist/last-error-workflow.d.ts.map +1 -0
  67. package/dist/last-error-workflow.js +78 -0
  68. package/dist/preview-runtime.d.ts +2 -2
  69. package/dist/preview-runtime.d.ts.map +1 -1
  70. package/dist/preview-runtime.js +37 -7
  71. package/dist/task-commands.d.ts.map +1 -1
  72. package/dist/task-commands.js +9 -16
  73. package/dist/task-export-workflow.d.ts +4 -0
  74. package/dist/task-export-workflow.d.ts.map +1 -0
  75. package/dist/task-export-workflow.js +46 -0
  76. package/dist/task-report-workflow.d.ts +47 -0
  77. package/dist/task-report-workflow.d.ts.map +1 -0
  78. package/dist/task-report-workflow.js +160 -0
  79. package/dist/workbench-cli.js +1 -1
  80. package/package.json +1 -1
  81. package/src/dataset-clone-workflow.ts +100 -0
  82. package/src/dataset-commands.ts +95 -142
  83. package/src/dataset-delete-workflow.ts +72 -0
  84. package/src/dataset-lock-workflow.ts +74 -0
  85. package/src/dataset-readme-workflow.ts +80 -0
  86. package/src/dataset-row-workflow.ts +248 -0
  87. package/src/dataset-split-reads.ts +43 -0
  88. package/src/dataset-transfer-upload.ts +12 -5
  89. package/src/dataset-upload-snapshot.ts +8 -1
  90. package/src/dataset-version-workflow.ts +105 -0
  91. package/src/feedback-cli.ts +4 -4
  92. package/src/help.ts +29 -3
  93. package/src/hosted-boot.ts +16 -1
  94. package/src/hosted-harness-opencode.ts +74 -72
  95. package/src/hosted-loop-lifecycle.ts +6 -11
  96. package/src/hosted-loop.ts +20 -10
  97. package/src/hosted-model-cache.d.mts +2 -0
  98. package/src/hosted-model-cache.mjs +53 -0
  99. package/src/index.ts +12 -2
  100. package/src/instruction-delivery.ts +123 -0
  101. package/src/instruction-set-sync/index.ts +61 -14
  102. package/src/instruction-set-update/index.ts +4 -3
  103. package/src/instruction-sets-cli.ts +19 -27
  104. package/src/json-response.ts +8 -0
  105. package/src/last-error-record.ts +11 -85
  106. package/src/last-error-workflow.ts +121 -0
  107. package/src/preview-runtime.ts +40 -10
  108. package/src/task-commands.ts +9 -43
  109. package/src/task-export-workflow.ts +56 -0
  110. package/src/task-report-workflow.ts +179 -0
  111. package/src/workbench-cli.ts +1 -1
  112. package/vendor/skills/orizu/references/cli-reference.md +34 -0
  113. package/vendor/skills/orizu/references/dataset-design.md +1 -1
  114. package/dist/dataset-edit-rows.d.ts +0 -10
  115. package/dist/dataset-edit-rows.d.ts.map +0 -1
  116. package/dist/dataset-edit-rows.js +0 -90
  117. 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 path = getLastErrorRecordPath(configDirectory);
276
- const temporaryPath = join(configDirectory, `.${LAST_ERROR_FILENAME}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`);
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
- if (statSync(path).size > LAST_ERROR_FILE_MAX_BYTES)
302
- return null;
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":"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"}
1
+ {"version":3,"file":"preview-runtime.d.ts","sourceRoot":"","sources":["../src/preview-runtime.ts"],"names":[],"mappings":"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"}
@@ -186,19 +186,32 @@ export async function runLocalAppPreview(options) {
186
186
  headless: !options.headed,
187
187
  ...(chromiumExecutablePath ? { executablePath: chromiumExecutablePath } : {}),
188
188
  });
189
- const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } });
189
+ let page = null;
190
+ let handleRenderError;
191
+ let hasFinishedKeepingOpen = false;
190
192
  try {
191
- await page.goto(url, { waitUntil: 'networkidle' });
192
- await page.waitForSelector('[data-orizu-preview-ready="true"]', { timeout: 15_000 });
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 (!options.keepOpen) {
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
- return <pre {...props} className={cn('overflow-auto rounded-md border bg-muted p-3 font-mono text-xs', className)}><code>{children ?? code}</code></pre>
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, children, className, ...props }) {
731
- return <div {...props} className={cn('whitespace-pre-wrap text-sm leading-relaxed', className)}>{children ?? content}</div>
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":"AA8CA,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,kBAkD3B;AAED,iBAAe,aAAa,kBA+B3B;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"}
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"}
@@ -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
- const response = await authedFetch(`/api/cli/tasks/${encodeURIComponent(taskId)}/report`, {
465
- method: 'PATCH',
466
- headers: { 'Content-Type': 'application/json' },
467
- body: JSON.stringify({
468
- reportMarkdown: report.markdown,
469
- reportSourceName: report.sourceName,
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 = await parseJsonResponse(response, 'Task report update');
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 response = await authedFetch(`/api/cli/tasks/${encodeURIComponent(taskId)}/report`);
490
- if (!response.ok) {
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
+ }
@@ -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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orizu",
3
- "version": "0.5.39",
3
+ "version": "0.5.41",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",