orizu 0.5.16 → 0.5.18
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 +15 -0
- package/dist/archive-artifacts-cli.js +213 -0
- package/dist/artifact-pull.js +1 -3
- package/dist/dataset-edit-rows.js +90 -0
- package/dist/dataset-upload-snapshot.js +22 -0
- package/dist/diff-comments-cli.js +175 -0
- package/dist/egress-policy.js +12 -0
- package/dist/error-response.js +21 -15
- package/dist/git-credential.js +316 -10
- package/dist/git-recovery-environment.js +156 -0
- package/dist/github-setup.js +3 -11
- package/dist/help.js +134 -5
- package/dist/hosted-boot.js +36 -1
- package/dist/hosted-bootstrap-rehearsal.js +8 -0
- package/dist/hosted-broker-server.js +141 -129
- package/dist/hosted-git-http-server.js +70 -71
- package/dist/hosted-runtime-assets.js +136 -10
- package/dist/hosted-session-cli.js +435 -61
- package/dist/index.js +176 -213
- package/dist/json-response.js +21 -0
- package/dist/loopback-serve.js +38 -0
- package/dist/markdown-report-input.js +67 -0
- package/dist/optimization-export-cli.js +30 -0
- package/dist/optimizations-list-cli.js +18 -8
- package/dist/prompt-draft-push.js +7 -5
- package/dist/prompt-report-cli.js +86 -0
- package/dist/repository-backup-core.js +1518 -0
- package/dist/repository-recovery-manifest.js +326 -0
- package/dist/scorer-draft-push.js +1 -22
- package/dist/scores-accept-cli.js +1 -3
- package/dist/start-intent.js +25 -0
- package/dist/workbench-cli.js +164 -25
- package/dist/workspace-sync.js +5 -0
- package/dist/zip-draft-push.js +1 -24
- package/package.json +1 -1
- package/src/archive-artifacts-cli.ts +376 -0
- package/src/artifact-pull.ts +1 -4
- package/src/dataset-edit-rows.ts +124 -0
- package/src/dataset-upload-snapshot.ts +41 -0
- package/src/diff-comments-cli.ts +299 -0
- package/src/egress-policy.ts +24 -1
- package/src/error-response.ts +29 -16
- package/src/git-credential.ts +424 -9
- package/src/git-recovery-environment.ts +212 -0
- package/src/github-setup.ts +3 -12
- package/src/help.ts +134 -5
- package/src/hosted-boot.ts +47 -1
- package/src/hosted-bootstrap-rehearsal.ts +7 -0
- package/src/hosted-broker-server.ts +152 -134
- package/src/hosted-git-http-server.ts +64 -65
- package/src/hosted-runtime-assets.ts +141 -10
- package/src/hosted-session-cli.ts +553 -25
- package/src/index.ts +229 -276
- package/src/json-response.ts +37 -0
- package/src/loopback-serve.ts +55 -0
- package/src/markdown-report-input.ts +80 -0
- package/src/optimization-export-cli.ts +72 -0
- package/src/optimizations-list-cli.ts +26 -9
- package/src/prompt-draft-push.ts +8 -6
- package/src/prompt-report-cli.ts +135 -0
- package/src/repository-backup-core.ts +2537 -0
- package/src/repository-recovery-manifest.ts +521 -0
- package/src/scorer-draft-push.ts +1 -6
- package/src/scores-accept-cli.ts +1 -4
- package/src/start-intent.ts +41 -0
- package/src/workbench-cli.ts +219 -21
- package/src/workspace-sync.ts +4 -0
- package/src/zip-draft-push.ts +1 -6
- package/vendor/orizu-gepa-python/src/orizu_gepa/cli.py +1 -1
- package/vendor/orizu-gepa-python/src/orizu_gepa/client.py +4 -1
- package/vendor/skills/orizu-cli/SKILL.md +1 -0
- package/vendor/skills/orizu-cli/references/cli-reference.md +43 -0
package/README.md
CHANGED
|
@@ -173,6 +173,14 @@ orizu logout
|
|
|
173
173
|
<td>Archive or restore a prompt</td>
|
|
174
174
|
<td><code>orizu prompts archive <promptIdOrName> --project ops-eval/support-qa</code><br><code>orizu prompts restore <promptIdOrName> --project ops-eval/support-qa</code></td>
|
|
175
175
|
</tr>
|
|
176
|
+
<tr>
|
|
177
|
+
<td>Push a prompt with a report</td>
|
|
178
|
+
<td><code>orizu prompts push ./prompts/support --project ops-eval/support-qa --report-file ./prompt-report.md</code></td>
|
|
179
|
+
</tr>
|
|
180
|
+
<tr>
|
|
181
|
+
<td>Upload a report for an existing prompt version</td>
|
|
182
|
+
<td><code>orizu prompts report set --prompt <promptIdOrName> --project ops-eval/support-qa --version 3 --report-file ./prompt-report.md</code><br><code>prompts report upload</code> is an exact alias; select exactly one of <code>--version</code> or <code>--version-id</code>.</td>
|
|
183
|
+
</tr>
|
|
176
184
|
<tr>
|
|
177
185
|
<td colspan="2"><strong>Report comments</strong></td>
|
|
178
186
|
</tr>
|
|
@@ -184,6 +192,13 @@ orizu logout
|
|
|
184
192
|
<td>Add or update comments</td>
|
|
185
193
|
<td><code>orizu comments add --run <runId> --body @comment.md --anchor "Summary" --lines 4:6</code><br><code>orizu comments reply <commentId> --body "Fixed"</code><br><code>orizu comments resolve <commentId></code></td>
|
|
186
194
|
</tr>
|
|
195
|
+
<tr>
|
|
196
|
+
<td colspan="2"><strong>Diff comments</strong></td>
|
|
197
|
+
</tr>
|
|
198
|
+
<tr>
|
|
199
|
+
<td>Export diff comments</td>
|
|
200
|
+
<td><code>orizu comments diff --run <optimizationRunId> [--from <candidateId> --to <candidateId>] [--detail hunk|diff|full] [--json]</code><br><code>orizu comments diff --prompt <promptId> [--from <version> --to <version>] [--detail hunk|diff|full] [--json]</code></td>
|
|
201
|
+
</tr>
|
|
187
202
|
<tr>
|
|
188
203
|
<td colspan="2"><strong>Datasets</strong></td>
|
|
189
204
|
</tr>
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ALI-1199 archive/restore commands and assignment inventory.
|
|
3
|
+
*
|
|
4
|
+
* Kept outside index.ts per the CLI line ratchet. The server owns
|
|
5
|
+
* authorization and idempotency; this module validates the CLI grammar and
|
|
6
|
+
* renders the canonical response without inventing client-side state.
|
|
7
|
+
*/
|
|
8
|
+
import { authedFetch } from './http.js';
|
|
9
|
+
import { extractErrorMessage } from './error-response.js';
|
|
10
|
+
import { sanitizeTerminalText } from './json-response.js';
|
|
11
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
12
|
+
const ARTIFACT_TYPES = {
|
|
13
|
+
app: 'app',
|
|
14
|
+
apps: 'app',
|
|
15
|
+
dataset: 'dataset',
|
|
16
|
+
datasets: 'dataset',
|
|
17
|
+
task: 'task',
|
|
18
|
+
tasks: 'task',
|
|
19
|
+
assignment: 'assignment',
|
|
20
|
+
assignments: 'assignment',
|
|
21
|
+
scorer: 'scorer',
|
|
22
|
+
scorers: 'scorer',
|
|
23
|
+
optimization: 'optimization',
|
|
24
|
+
optimizations: 'optimization',
|
|
25
|
+
};
|
|
26
|
+
function argValue(args, flag) {
|
|
27
|
+
const index = args.indexOf(flag);
|
|
28
|
+
if (index === -1 ||
|
|
29
|
+
index + 1 >= args.length ||
|
|
30
|
+
args[index + 1].startsWith('--')) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
return args[index + 1];
|
|
34
|
+
}
|
|
35
|
+
function columnWidths(headers, rows) {
|
|
36
|
+
return headers.map((header, index) => Math.max(header.length, ...rows.map(row => row[index]?.length || 0)));
|
|
37
|
+
}
|
|
38
|
+
function printTable(headers, rows, print) {
|
|
39
|
+
const widths = columnWidths(headers, rows);
|
|
40
|
+
print(headers.map((header, index) => header.padEnd(widths[index])).join(' '));
|
|
41
|
+
print(widths.map(width => '-'.repeat(width)).join(' '));
|
|
42
|
+
rows.forEach(row => {
|
|
43
|
+
print(row.map((cell, index) => cell.padEnd(widths[index])).join(' '));
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
export function printTaskSummaries(items, print) {
|
|
47
|
+
if (items.length === 0)
|
|
48
|
+
return print('No tasks found.');
|
|
49
|
+
printTable(['TASK ID', 'TASK NAME', 'STATUS', 'ARCHIVE', 'TEAM/PROJECT'], items.map(item => [
|
|
50
|
+
sanitizeTerminalText(item.id),
|
|
51
|
+
sanitizeTerminalText(item.title || '-'),
|
|
52
|
+
sanitizeTerminalText(item.status || '-'),
|
|
53
|
+
sanitizeTerminalText(item.archiveStatus || 'active'),
|
|
54
|
+
item.teamSlug && item.projectSlug
|
|
55
|
+
? sanitizeTerminalText(`${item.teamSlug}/${item.projectSlug}`)
|
|
56
|
+
: 'unknown-project',
|
|
57
|
+
]), print);
|
|
58
|
+
}
|
|
59
|
+
export function printAppSummaries(items, print) {
|
|
60
|
+
if (items.length === 0)
|
|
61
|
+
return print('No apps found.');
|
|
62
|
+
printTable(['APP ID', 'APP NAME', 'VERSION', 'ARCHIVE'], items.map(item => [
|
|
63
|
+
sanitizeTerminalText(item.id),
|
|
64
|
+
sanitizeTerminalText(item.name || '-'),
|
|
65
|
+
`v${item.currentVersionNum || 1}`,
|
|
66
|
+
sanitizeTerminalText(item.status || 'active'),
|
|
67
|
+
]), print);
|
|
68
|
+
}
|
|
69
|
+
export function printDatasetSummaries(items, print) {
|
|
70
|
+
if (items.length === 0)
|
|
71
|
+
return print('No datasets found.');
|
|
72
|
+
printTable(['DATASET ID', 'DATASET', 'ROWS', 'ARCHIVE'], items.map(item => [
|
|
73
|
+
sanitizeTerminalText(item.id),
|
|
74
|
+
sanitizeTerminalText(item.name || '-'),
|
|
75
|
+
sanitizeTerminalText(item.rowCount),
|
|
76
|
+
sanitizeTerminalText(item.status || 'active'),
|
|
77
|
+
]), print);
|
|
78
|
+
}
|
|
79
|
+
export function printScorerSummaries(items, print) {
|
|
80
|
+
if (items.length === 0)
|
|
81
|
+
return print('No scorers found.');
|
|
82
|
+
printTable(['ID', 'NAME', 'MODE', 'METRIC', 'ARCHIVE', 'IMPLEMENTATION'], items.map(item => [
|
|
83
|
+
sanitizeTerminalText(item.id),
|
|
84
|
+
sanitizeTerminalText(item.name),
|
|
85
|
+
sanitizeTerminalText(item.mode),
|
|
86
|
+
sanitizeTerminalText(item.metricLabel),
|
|
87
|
+
sanitizeTerminalText(item.status || 'active'),
|
|
88
|
+
sanitizeTerminalText(item.implementationKind),
|
|
89
|
+
]), print);
|
|
90
|
+
}
|
|
91
|
+
async function parseJsonPayload(response, context) {
|
|
92
|
+
const contentType = response.headers.get('content-type') || '';
|
|
93
|
+
const rawBody = await response.text();
|
|
94
|
+
if (!contentType.includes('application/json')) {
|
|
95
|
+
throw new Error(`${context} returned non-JSON response (status ${response.status}). ` +
|
|
96
|
+
`Body preview: ${sanitizeTerminalText(rawBody.slice(0, 180))}`);
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
return JSON.parse(rawBody);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
throw new Error(`${context} returned invalid JSON (status ${response.status}). ` +
|
|
103
|
+
`Body preview: ${sanitizeTerminalText(rawBody.slice(0, 180))}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function parseStatus(args) {
|
|
107
|
+
const value = argValue(args, '--status') || 'active';
|
|
108
|
+
if (value !== 'active' && value !== 'archived' && value !== 'all') {
|
|
109
|
+
throw new Error('--status must be active, archived, or all');
|
|
110
|
+
}
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
function artifactUsage() {
|
|
114
|
+
return ('Usage: orizu <apps|datasets|tasks|assignments|scorers|optimizations> ' +
|
|
115
|
+
'<archive|restore> <id> [--project <team/project>] ' +
|
|
116
|
+
'[--assignee <user-id>] [--json]');
|
|
117
|
+
}
|
|
118
|
+
export async function archiveArtifactCommand(args, io) {
|
|
119
|
+
const artifactType = ARTIFACT_TYPES[args[0] || ''];
|
|
120
|
+
const action = args[1];
|
|
121
|
+
const artifactId = args[2];
|
|
122
|
+
if (!artifactType ||
|
|
123
|
+
(action !== 'archive' && action !== 'restore') ||
|
|
124
|
+
!artifactId ||
|
|
125
|
+
!UUID_PATTERN.test(artifactId)) {
|
|
126
|
+
throw new Error(artifactUsage());
|
|
127
|
+
}
|
|
128
|
+
const assigneeId = argValue(args, '--assignee');
|
|
129
|
+
if (assigneeId && !UUID_PATTERN.test(assigneeId)) {
|
|
130
|
+
throw new Error('--assignee must be a UUID');
|
|
131
|
+
}
|
|
132
|
+
if (artifactType !== 'assignment' && assigneeId) {
|
|
133
|
+
throw new Error('--assignee is only valid for assignment archive commands');
|
|
134
|
+
}
|
|
135
|
+
const projectArg = argValue(args, '--project');
|
|
136
|
+
const project = projectArg || await io.resolveProjectSlug(null);
|
|
137
|
+
const params = new URLSearchParams({ project });
|
|
138
|
+
const body = {
|
|
139
|
+
archived: action === 'archive',
|
|
140
|
+
...(assigneeId ? { assigneeId } : {}),
|
|
141
|
+
};
|
|
142
|
+
const fetcher = io.fetcher || authedFetch;
|
|
143
|
+
const response = await fetcher(`/api/cli/archive-artifacts/${artifactType}/${encodeURIComponent(artifactId)}` +
|
|
144
|
+
`?${params.toString()}`, {
|
|
145
|
+
method: 'PATCH',
|
|
146
|
+
headers: { 'content-type': 'application/json' },
|
|
147
|
+
body: JSON.stringify(body),
|
|
148
|
+
});
|
|
149
|
+
if (!response.ok) {
|
|
150
|
+
throw new Error(`Failed to ${action} ${artifactType}: ` +
|
|
151
|
+
await extractErrorMessage(response));
|
|
152
|
+
}
|
|
153
|
+
const data = await parseJsonPayload(response, `${action === 'archive' ? 'Archive' : 'Restore'} ${artifactType}`);
|
|
154
|
+
if (io.json) {
|
|
155
|
+
io.print(JSON.stringify(data));
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const verb = action === 'archive' ? 'Archived' : 'Restored';
|
|
159
|
+
const assigneeSuffix = data.artifact.assigneeId
|
|
160
|
+
? ` for ${sanitizeTerminalText(data.artifact.assigneeId)}`
|
|
161
|
+
: '';
|
|
162
|
+
io.print(`${verb} ${sanitizeTerminalText(data.artifact.type)} ` +
|
|
163
|
+
`${sanitizeTerminalText(data.artifact.id)}${assigneeSuffix}.`);
|
|
164
|
+
}
|
|
165
|
+
export async function listAssignmentsCommand(args, io) {
|
|
166
|
+
const status = parseStatus(args);
|
|
167
|
+
const assigneeId = argValue(args, '--assignee');
|
|
168
|
+
if (assigneeId && !UUID_PATTERN.test(assigneeId)) {
|
|
169
|
+
throw new Error('--assignee must be a UUID');
|
|
170
|
+
}
|
|
171
|
+
const projectArg = argValue(args, '--project');
|
|
172
|
+
const project = projectArg || await io.resolveProjectSlug(null);
|
|
173
|
+
const params = new URLSearchParams({ project, status });
|
|
174
|
+
if (assigneeId)
|
|
175
|
+
params.set('assignee', assigneeId);
|
|
176
|
+
const fetcher = io.fetcher || authedFetch;
|
|
177
|
+
const response = await fetcher(`/api/cli/assignments?${params.toString()}`);
|
|
178
|
+
if (!response.ok) {
|
|
179
|
+
throw new Error(`Failed to list assignments: ${await extractErrorMessage(response)}`);
|
|
180
|
+
}
|
|
181
|
+
const data = await parseJsonPayload(response, 'Assignments list');
|
|
182
|
+
if (io.json) {
|
|
183
|
+
io.print(JSON.stringify(data));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const rows = data.assignments || [];
|
|
187
|
+
if (rows.length === 0) {
|
|
188
|
+
io.print('No assignment task-groups found.');
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const printable = rows.map(row => ({
|
|
192
|
+
id: sanitizeTerminalText(row.taskId),
|
|
193
|
+
title: sanitizeTerminalText(row.taskTitle || '-'),
|
|
194
|
+
lifecycle: sanitizeTerminalText(row.taskStatus || '-'),
|
|
195
|
+
archive: sanitizeTerminalText(row.status || 'active'),
|
|
196
|
+
progress: `${row.completedAssignments}/${row.totalAssignments}`,
|
|
197
|
+
}));
|
|
198
|
+
const idWidth = Math.max('TASK ID'.length, ...printable.map(row => row.id.length));
|
|
199
|
+
const titleWidth = Math.max('TASK'.length, ...printable.map(row => row.title.length));
|
|
200
|
+
const lifecycleWidth = Math.max('LIFECYCLE'.length, ...printable.map(row => row.lifecycle.length));
|
|
201
|
+
const archiveWidth = Math.max('ARCHIVE'.length, ...printable.map(row => row.archive.length));
|
|
202
|
+
io.print(`${'TASK ID'.padEnd(idWidth)} ${'TASK'.padEnd(titleWidth)} ` +
|
|
203
|
+
`${'LIFECYCLE'.padEnd(lifecycleWidth)} ` +
|
|
204
|
+
`${'ARCHIVE'.padEnd(archiveWidth)} PROGRESS`);
|
|
205
|
+
io.print(`${'-'.repeat(idWidth)} ${'-'.repeat(titleWidth)} ` +
|
|
206
|
+
`${'-'.repeat(lifecycleWidth)} ${'-'.repeat(archiveWidth)} ` +
|
|
207
|
+
`${'-'.repeat('PROGRESS'.length)}`);
|
|
208
|
+
printable.forEach(row => {
|
|
209
|
+
io.print(`${row.id.padEnd(idWidth)} ${row.title.padEnd(titleWidth)} ` +
|
|
210
|
+
`${row.lifecycle.padEnd(lifecycleWidth)} ` +
|
|
211
|
+
`${row.archive.padEnd(archiveWidth)} ${row.progress}`);
|
|
212
|
+
});
|
|
213
|
+
}
|
package/dist/artifact-pull.js
CHANGED
|
@@ -16,11 +16,9 @@ import { mkdirSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from 'fs';
|
|
|
16
16
|
import { tmpdir } from 'os';
|
|
17
17
|
import { dirname, join } from 'path';
|
|
18
18
|
import { authedFetch } from './http.js';
|
|
19
|
+
import { sanitizeTerminalText } from './json-response.js';
|
|
19
20
|
// Shared with the runners exec download path in index.ts.
|
|
20
21
|
export const ARTIFACT_MAX_BYTES = 25 * 1024 * 1024;
|
|
21
|
-
function sanitizeTerminalText(value) {
|
|
22
|
-
return String(value).replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, '');
|
|
23
|
-
}
|
|
24
22
|
function expandHomePath(path) {
|
|
25
23
|
if (path.startsWith('~/')) {
|
|
26
24
|
const home = process.env.HOME || '';
|
|
@@ -0,0 +1,90 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export async function ensureDatasetUploadSnapshot(dataset, jsonOutput, createSnapshot) {
|
|
2
|
+
try {
|
|
3
|
+
return await createSnapshot(dataset.id);
|
|
4
|
+
}
|
|
5
|
+
catch (error) {
|
|
6
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
7
|
+
const retryCommand = `orizu datasets versions create --dataset ${dataset.id}`;
|
|
8
|
+
if (jsonOutput) {
|
|
9
|
+
throw new Error(JSON.stringify({
|
|
10
|
+
error: 'Dataset upload completed but dataset version creation failed',
|
|
11
|
+
detail,
|
|
12
|
+
upload_completed: true,
|
|
13
|
+
dataset_id: dataset.id,
|
|
14
|
+
dataset_version_id: null,
|
|
15
|
+
retry_command: retryCommand,
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
throw new Error(`Dataset upload completed but dataset version creation failed: ${detail}\n` +
|
|
19
|
+
`Dataset ${dataset.name} (${dataset.id}) was created with ${dataset.rowCount} rows. ` +
|
|
20
|
+
`Retry only the snapshot step with: ${retryCommand}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { extractErrorMessage, isJsonErrorResponsePayload, } from './error-response.js';
|
|
2
|
+
import { sanitizeHumanInlineText } from './json-response.js';
|
|
3
|
+
const DETAIL_LEVELS = ['hunk', 'diff', 'full'];
|
|
4
|
+
const CONTEXT_UNAVAILABLE_WITHOUT_REASON = ' Context unavailable (no reason supplied)';
|
|
5
|
+
function isCandidatePair(pair) {
|
|
6
|
+
return 'candidateId' in pair.from;
|
|
7
|
+
}
|
|
8
|
+
function usage() {
|
|
9
|
+
return 'Usage: orizu comments diff (--run <run-id> | --prompt <prompt-id>) [--from <revision> --to <revision>] [--detail hunk|diff|full] [--json]';
|
|
10
|
+
}
|
|
11
|
+
function sanitizeHumanText(ctx, value) {
|
|
12
|
+
return ctx.sanitizeTerminalText(value).replaceAll('\r', '');
|
|
13
|
+
}
|
|
14
|
+
// Quote untrusted multiline blocks so none of their rows can impersonate a
|
|
15
|
+
// genuine hunk row, whose marker begins immediately after the four-space indent.
|
|
16
|
+
function printHumanTextBlock(ctx, value) {
|
|
17
|
+
for (const line of sanitizeHumanText(ctx, value).split('\n')) {
|
|
18
|
+
ctx.printLine(` │ ${line}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function hasExplicitlyEmptyJsonError(response, rawBody) {
|
|
22
|
+
try {
|
|
23
|
+
const payload = JSON.parse(rawBody);
|
|
24
|
+
return isJsonErrorResponsePayload(response, payload) && payload.error.length === 0;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function extractDiffCommentsErrorMessage(response) {
|
|
31
|
+
const statusFallback = `HTTP ${response.status}`;
|
|
32
|
+
try {
|
|
33
|
+
const rawBody = await response.text();
|
|
34
|
+
if (hasExplicitlyEmptyJsonError(response, rawBody)) {
|
|
35
|
+
return statusFallback;
|
|
36
|
+
}
|
|
37
|
+
const copiedResponse = new Response(rawBody, { headers: response.headers });
|
|
38
|
+
const extractedMessage = await extractErrorMessage(copiedResponse);
|
|
39
|
+
return extractedMessage.trim() ? extractedMessage : statusFallback;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return statusFallback;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function humanLineNumber(ctx, line) {
|
|
46
|
+
return line === null
|
|
47
|
+
? '-'
|
|
48
|
+
: sanitizeHumanInlineText(ctx.sanitizeTerminalText, String(line + 1));
|
|
49
|
+
}
|
|
50
|
+
function buildParams(ctx) {
|
|
51
|
+
const runId = ctx.getArg('--run');
|
|
52
|
+
const promptId = ctx.getArg('--prompt');
|
|
53
|
+
const from = ctx.getArg('--from');
|
|
54
|
+
const to = ctx.getArg('--to');
|
|
55
|
+
const detail = ctx.getArg('--detail') ?? 'diff';
|
|
56
|
+
for (const [name, value] of [
|
|
57
|
+
['--run', runId],
|
|
58
|
+
['--prompt', promptId],
|
|
59
|
+
['--from', from],
|
|
60
|
+
['--to', to],
|
|
61
|
+
['--detail', detail],
|
|
62
|
+
]) {
|
|
63
|
+
ctx.rejectDashPrefixedOptionValue(name, value);
|
|
64
|
+
}
|
|
65
|
+
if (runId === '' || promptId === '') {
|
|
66
|
+
throw new Error(usage());
|
|
67
|
+
}
|
|
68
|
+
if (Number(Boolean(runId)) + Number(Boolean(promptId)) !== 1) {
|
|
69
|
+
throw new Error(usage());
|
|
70
|
+
}
|
|
71
|
+
if (from === '' || to === '') {
|
|
72
|
+
throw new Error('--from and --to must not be empty');
|
|
73
|
+
}
|
|
74
|
+
if (Boolean(from) !== Boolean(to)) {
|
|
75
|
+
throw new Error('--from and --to must be provided together');
|
|
76
|
+
}
|
|
77
|
+
if (!DETAIL_LEVELS.includes(detail)) {
|
|
78
|
+
throw new Error('--detail must be hunk, diff, or full');
|
|
79
|
+
}
|
|
80
|
+
if (promptId && from && to) {
|
|
81
|
+
if (!Number.isInteger(Number(from)) || !Number.isInteger(Number(to))) {
|
|
82
|
+
throw new Error('--from and --to must be integer prompt versions');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const params = new URLSearchParams();
|
|
86
|
+
params.set(runId ? 'run' : 'prompt', (runId ?? promptId));
|
|
87
|
+
if (from && to) {
|
|
88
|
+
params.set('from', from);
|
|
89
|
+
params.set('to', to);
|
|
90
|
+
}
|
|
91
|
+
params.set('detail', detail);
|
|
92
|
+
return params;
|
|
93
|
+
}
|
|
94
|
+
function printDiffComments(ctx, payload) {
|
|
95
|
+
ctx.printLine(`Diff comments: ${payload.pairs.length} pair${payload.pairs.length === 1 ? '' : 's'}`);
|
|
96
|
+
if (payload.pairs.length === 0) {
|
|
97
|
+
ctx.printLine('');
|
|
98
|
+
ctx.printLine('No diff comments found.');
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
for (const pair of payload.pairs) {
|
|
102
|
+
const hasCandidateIds = isCandidatePair(pair);
|
|
103
|
+
const from = hasCandidateIds ? pair.from.candidateId : String(pair.from.version);
|
|
104
|
+
const to = hasCandidateIds ? pair.to.candidateId : String(pair.to.version);
|
|
105
|
+
ctx.printLine('');
|
|
106
|
+
ctx.printLine(`${hasCandidateIds ? 'Candidates' : 'Versions'} ` +
|
|
107
|
+
`${sanitizeHumanInlineText(ctx.sanitizeTerminalText, from)} → ` +
|
|
108
|
+
`${sanitizeHumanInlineText(ctx.sanitizeTerminalText, to)}`);
|
|
109
|
+
if (pair.degraded) {
|
|
110
|
+
ctx.printLine(` Context unavailable: ${sanitizeHumanInlineText(ctx.sanitizeTerminalText, pair.degraded.reason)}`);
|
|
111
|
+
}
|
|
112
|
+
if (pair.diff !== null) {
|
|
113
|
+
ctx.printLine(' Diff:');
|
|
114
|
+
printHumanTextBlock(ctx, pair.diff);
|
|
115
|
+
}
|
|
116
|
+
if (pair.bodies !== null) {
|
|
117
|
+
for (const [label, body] of [
|
|
118
|
+
['From body', pair.bodies.from],
|
|
119
|
+
['To body', pair.bodies.to],
|
|
120
|
+
]) {
|
|
121
|
+
ctx.printLine(` ${label}:`);
|
|
122
|
+
printHumanTextBlock(ctx, body);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
for (const comment of pair.comments) {
|
|
126
|
+
const authorName = comment.author?.person?.name || comment.author?.name || 'Unknown';
|
|
127
|
+
ctx.printLine(` ${sanitizeHumanInlineText(ctx.sanitizeTerminalText, comment.id)} · ` +
|
|
128
|
+
`${sanitizeHumanInlineText(ctx.sanitizeTerminalText, authorName)} · ` +
|
|
129
|
+
`${sanitizeHumanInlineText(ctx.sanitizeTerminalText, comment.createdAt)} · ` +
|
|
130
|
+
`${sanitizeHumanInlineText(ctx.sanitizeTerminalText, comment.anchor.side)} line ` +
|
|
131
|
+
`${sanitizeHumanInlineText(ctx.sanitizeTerminalText, String(comment.anchor.line + 1))}`);
|
|
132
|
+
printHumanTextBlock(ctx, comment.body);
|
|
133
|
+
if (comment.contextUnavailableReason) {
|
|
134
|
+
ctx.printLine(` Context unavailable: ${sanitizeHumanInlineText(ctx.sanitizeTerminalText, comment.contextUnavailableReason)}`);
|
|
135
|
+
if (comment.context) {
|
|
136
|
+
ctx.printLine(` Anchored line: ${sanitizeHumanInlineText(ctx.sanitizeTerminalText, comment.context.lineText)}`);
|
|
137
|
+
}
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (!comment.context) {
|
|
141
|
+
ctx.printLine(CONTEXT_UNAVAILABLE_WITHOUT_REASON);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (!comment.context.hunk) {
|
|
145
|
+
ctx.printLine(CONTEXT_UNAVAILABLE_WITHOUT_REASON);
|
|
146
|
+
ctx.printLine(` Anchored line: ${sanitizeHumanInlineText(ctx.sanitizeTerminalText, comment.context.lineText)}`);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
for (const line of comment.context.hunk.lines) {
|
|
150
|
+
const prefix = line.op === 'add' ? '+' : line.op === 'del' ? '-' : ' ';
|
|
151
|
+
const isAnchored = comment.anchor.side === 'old'
|
|
152
|
+
? line.oldLine === comment.anchor.line
|
|
153
|
+
: line.newLine === comment.anchor.line;
|
|
154
|
+
const marker = isAnchored ? '>' : ' ';
|
|
155
|
+
ctx.printLine(` ${marker} old:${humanLineNumber(ctx, line.oldLine)} ` +
|
|
156
|
+
`new:${humanLineNumber(ctx, line.newLine)} | ` +
|
|
157
|
+
`${prefix}${sanitizeHumanInlineText(ctx.sanitizeTerminalText, line.text)}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
export async function diffCommentsCommand(ctx) {
|
|
163
|
+
const params = buildParams(ctx);
|
|
164
|
+
const response = await ctx.authedFetch(`/api/cli/diff-comments?${params.toString()}`);
|
|
165
|
+
if (!response.ok) {
|
|
166
|
+
const message = await extractDiffCommentsErrorMessage(response);
|
|
167
|
+
throw new Error(`Failed to fetch diff comments: ${sanitizeHumanInlineText(ctx.sanitizeTerminalText, message)}`);
|
|
168
|
+
}
|
|
169
|
+
const payload = await ctx.parseJsonResponse(response, 'Diff comments');
|
|
170
|
+
if (ctx.hasJsonFlag()) {
|
|
171
|
+
ctx.printJson(payload);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
printDiffComments(ctx, payload);
|
|
175
|
+
}
|
package/dist/egress-policy.js
CHANGED
|
@@ -117,6 +117,17 @@ export function baseEgressDomains(opts = {}) {
|
|
|
117
117
|
}
|
|
118
118
|
for (const d of GIT_HOST_DOMAINS)
|
|
119
119
|
domains.add(d);
|
|
120
|
+
for (const d of opts.repositoryHosts ?? []) {
|
|
121
|
+
const normalized = normalizeEgressDomain(d);
|
|
122
|
+
// Repository hosts come from a validated remote and must remain exact:
|
|
123
|
+
// no URL normalization, leading wildcard, path, port, or case drift is
|
|
124
|
+
// accepted at this final firewall seam.
|
|
125
|
+
if (normalized &&
|
|
126
|
+
!normalized.startsWith('*.') &&
|
|
127
|
+
normalized === d.trim().toLowerCase()) {
|
|
128
|
+
domains.add(normalized);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
120
131
|
return [...domains];
|
|
121
132
|
}
|
|
122
133
|
/**
|
|
@@ -136,6 +147,7 @@ export function buildEgressPolicy(opts = {}) {
|
|
|
136
147
|
orizuBaseUrl: opts.orizuBaseUrl,
|
|
137
148
|
modelHost: brokerHost,
|
|
138
149
|
modelHosts: opts.modelHosts,
|
|
150
|
+
repositoryHosts: opts.repositoryHosts,
|
|
139
151
|
});
|
|
140
152
|
const allow = {};
|
|
141
153
|
for (const domain of base)
|
package/dist/error-response.js
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
|
-
|
|
1
|
+
import { sanitizeTerminalText } from './json-response.js';
|
|
2
|
+
const ERROR_BODY_PREVIEW_LIMIT = 180;
|
|
3
|
+
export function isJsonErrorResponsePayload(response, payload) {
|
|
2
4
|
const contentType = response.headers.get('content-type') || '';
|
|
5
|
+
return (contentType.includes('application/json') &&
|
|
6
|
+
payload !== null &&
|
|
7
|
+
typeof payload === 'object' &&
|
|
8
|
+
!Array.isArray(payload) &&
|
|
9
|
+
typeof payload.error === 'string');
|
|
10
|
+
}
|
|
11
|
+
function boundedTerminalPreview(value) {
|
|
12
|
+
return sanitizeTerminalText(value).slice(0, ERROR_BODY_PREVIEW_LIMIT);
|
|
13
|
+
}
|
|
14
|
+
export async function extractErrorMessage(response) {
|
|
3
15
|
const rawBody = await response.text();
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
typeof payload === 'object' &&
|
|
9
|
-
!Array.isArray(payload) &&
|
|
10
|
-
typeof payload.error === 'string' &&
|
|
11
|
-
payload.error.length > 0) {
|
|
12
|
-
return payload.error;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
catch {
|
|
16
|
-
// Fall through to raw body
|
|
16
|
+
try {
|
|
17
|
+
const payload = JSON.parse(rawBody);
|
|
18
|
+
if (isJsonErrorResponsePayload(response, payload) && payload.error.length > 0) {
|
|
19
|
+
return boundedTerminalPreview(payload.error);
|
|
17
20
|
}
|
|
18
21
|
}
|
|
19
|
-
|
|
22
|
+
catch {
|
|
23
|
+
// Fall through to raw body
|
|
24
|
+
}
|
|
25
|
+
return boundedTerminalPreview(rawBody);
|
|
20
26
|
}
|