orizu 0.5.22 → 0.5.24

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 (144) hide show
  1. package/README.md +57 -21
  2. package/dist/child-output-tee.d.ts +48 -0
  3. package/dist/child-output-tee.d.ts.map +1 -0
  4. package/dist/child-output-tee.js +512 -0
  5. package/dist/cleanup.d.ts +6 -0
  6. package/dist/cleanup.d.ts.map +1 -0
  7. package/dist/cleanup.js +27 -0
  8. package/dist/cli-auth-browser-page.d.ts +2 -0
  9. package/dist/cli-auth-browser-page.d.ts.map +1 -0
  10. package/dist/cli-auth-browser-page.js +107 -0
  11. package/dist/github-setup.js +8 -8
  12. package/dist/headless-login.d.ts +17 -0
  13. package/dist/headless-login.d.ts.map +1 -0
  14. package/dist/headless-login.js +105 -0
  15. package/dist/help.d.ts.map +1 -1
  16. package/dist/help.js +32 -24
  17. package/dist/hosted-boot.js +3 -3
  18. package/dist/hosted-bootstrap.js +2 -2
  19. package/dist/hosted-commands.d.ts +15 -0
  20. package/dist/hosted-commands.d.ts.map +1 -0
  21. package/dist/hosted-commands.js +21 -0
  22. package/dist/hosted-environment.d.ts +3 -0
  23. package/dist/hosted-environment.d.ts.map +1 -0
  24. package/dist/hosted-environment.js +2 -0
  25. package/dist/hosted-gepa-cli.d.ts +14 -0
  26. package/dist/hosted-gepa-cli.d.ts.map +1 -0
  27. package/dist/hosted-gepa-cli.js +197 -0
  28. package/dist/hosted-gepa-numeric-values.d.ts +3 -0
  29. package/dist/hosted-gepa-numeric-values.d.ts.map +1 -0
  30. package/dist/hosted-gepa-numeric-values.js +49 -0
  31. package/dist/hosted-loop.d.ts +1 -1
  32. package/dist/hosted-loop.d.ts.map +1 -1
  33. package/dist/hosted-loop.js +2 -1
  34. package/dist/hosted-optimization-cli.d.ts +7 -0
  35. package/dist/hosted-optimization-cli.d.ts.map +1 -0
  36. package/dist/hosted-optimization-cli.js +218 -0
  37. package/dist/hosted-optimization-refusals.d.ts +88 -0
  38. package/dist/hosted-optimization-refusals.d.ts.map +1 -0
  39. package/dist/hosted-optimization-refusals.js +86 -0
  40. package/dist/hosted-optimization.d.ts +6 -0
  41. package/dist/hosted-optimization.d.ts.map +1 -0
  42. package/dist/hosted-optimization.js +436 -0
  43. package/dist/hosted-provider-settings.d.ts +6 -0
  44. package/dist/hosted-provider-settings.d.ts.map +1 -0
  45. package/dist/hosted-provider-settings.js +78 -0
  46. package/dist/hosted-session-cli.d.ts.map +1 -1
  47. package/dist/hosted-session-cli.js +11 -1
  48. package/dist/hosted-skill-staging.d.ts +9 -9
  49. package/dist/hosted-skill-staging.d.ts.map +1 -1
  50. package/dist/hosted-skill-staging.js +13 -10
  51. package/dist/http.d.ts +1 -0
  52. package/dist/http.d.ts.map +1 -1
  53. package/dist/http.js +7 -0
  54. package/dist/index.d.ts +4 -4
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +387 -384
  57. package/dist/optimizations-promote-cli.d.ts +3 -0
  58. package/dist/optimizations-promote-cli.d.ts.map +1 -1
  59. package/dist/optimizations-promote-cli.js +75 -7
  60. package/dist/report-comments-cli.d.ts +1 -0
  61. package/dist/report-comments-cli.d.ts.map +1 -1
  62. package/dist/report-comments-cli.js +70 -4
  63. package/dist/runner-dir-verify.d.ts +5 -0
  64. package/dist/runner-dir-verify.d.ts.map +1 -1
  65. package/dist/runner-dir-verify.js +44 -15
  66. package/dist/runner-version-materialization.d.ts +6 -0
  67. package/dist/runner-version-materialization.d.ts.map +1 -0
  68. package/dist/runner-version-materialization.js +64 -0
  69. package/dist/setup-onboarding.d.ts +16 -0
  70. package/dist/setup-onboarding.d.ts.map +1 -0
  71. package/dist/setup-onboarding.js +59 -0
  72. package/dist/skill-installer.d.ts +3 -3
  73. package/dist/skill-installer.d.ts.map +1 -1
  74. package/dist/skill-installer.js +39 -3
  75. package/dist/skilled-proposer-bake-report.d.ts +2 -0
  76. package/dist/skilled-proposer-bake-report.d.ts.map +1 -0
  77. package/dist/skilled-proposer-bake-report.js +1 -0
  78. package/dist/skilled-proposer-launch.d.ts +27 -1
  79. package/dist/skilled-proposer-launch.d.ts.map +1 -1
  80. package/dist/skilled-proposer-launch.js +66 -11
  81. package/dist/skilled-proposer-wire.d.ts +2 -0
  82. package/dist/skilled-proposer-wire.d.ts.map +1 -0
  83. package/dist/skilled-proposer-wire.js +25 -0
  84. package/dist/workspace.d.ts +14 -0
  85. package/dist/workspace.d.ts.map +1 -1
  86. package/dist/workspace.js +63 -4
  87. package/package.json +1 -1
  88. package/scripts/vendor-gepa-python.mjs +81 -12
  89. package/scripts/vendor-orizu-skill.mjs +3 -3
  90. package/src/child-output-tee.ts +587 -0
  91. package/src/cleanup.ts +37 -0
  92. package/src/cli-auth-browser-page.ts +108 -0
  93. package/src/github-setup.ts +8 -8
  94. package/src/headless-login.ts +178 -0
  95. package/src/help.ts +32 -24
  96. package/src/hosted-boot.ts +3 -3
  97. package/src/hosted-bootstrap.ts +2 -2
  98. package/src/hosted-commands.ts +20 -0
  99. package/src/hosted-environment.ts +2 -0
  100. package/src/hosted-gepa-cli.ts +215 -0
  101. package/src/hosted-gepa-numeric-values.ts +60 -0
  102. package/src/hosted-loop.ts +2 -1
  103. package/src/hosted-optimization-cli.ts +215 -0
  104. package/src/hosted-optimization-refusals.ts +101 -0
  105. package/src/hosted-optimization.ts +483 -0
  106. package/src/hosted-provider-settings.ts +81 -0
  107. package/src/hosted-session-cli.ts +9 -1
  108. package/src/hosted-skill-staging.ts +17 -14
  109. package/src/http.ts +8 -0
  110. package/src/index.ts +370 -301
  111. package/src/optimizations-promote-cli.ts +85 -7
  112. package/src/report-comments-cli.ts +83 -5
  113. package/src/runner-dir-verify.ts +85 -15
  114. package/src/runner-version-materialization.ts +56 -0
  115. package/src/setup-onboarding.ts +64 -0
  116. package/src/skill-installer.ts +39 -3
  117. package/src/skilled-proposer-bake-report.ts +1 -0
  118. package/src/skilled-proposer-launch.ts +74 -12
  119. package/src/skilled-proposer-venv-manager.mjs +44 -27
  120. package/src/skilled-proposer-wire.ts +22 -0
  121. package/src/workspace.ts +65 -4
  122. package/vendor/orizu-gepa/src/orizu_gepa_connector/runtime.py +63 -23
  123. package/vendor/orizu-gepa-python/src/orizu_gepa/cli.py +1 -0
  124. package/vendor/orizu-gepa-python/src/orizu_gepa/client.py +91 -13
  125. package/vendor/orizu-gepa-python/src/orizu_gepa/optimizer.py +16 -2
  126. package/vendor/skills/orizu/SKILL.md +138 -0
  127. package/vendor/skills/orizu/agents/openai.yaml +4 -0
  128. package/vendor/skills/orizu/references/assess-and-plan.md +107 -0
  129. package/vendor/skills/{orizu-cli → orizu}/references/building-apps.md +1 -1
  130. package/vendor/skills/orizu/references/building-judges.md +498 -0
  131. package/vendor/skills/{orizu-cli → orizu}/references/cli-reference.md +24 -23
  132. package/vendor/skills/orizu/references/dataset-design.md +100 -0
  133. package/vendor/skills/orizu/references/eval-strategy.md +64 -0
  134. package/vendor/skills/{orizu-cli → orizu}/references/instructions-after-prompts.md +12 -17
  135. package/vendor/skills/{orizu-cli → orizu}/references/migrate-existing-gepa-setup.md +79 -71
  136. package/vendor/skills/orizu/references/optimization-reports.md +266 -0
  137. package/vendor/skills/{orizu-cli → orizu}/references/optimization-with-gepa.md +56 -23
  138. package/vendor/skills/{orizu-cli → orizu}/references/primer.md +25 -16
  139. package/vendor/skills/{orizu-cli → orizu}/references/prompt-control-plane.md +94 -73
  140. package/vendor/skills/orizu-cli/SKILL.md +0 -242
  141. package/vendor/skills/orizu-cli/agents/openai.yaml +0 -4
  142. package/vendor/skills/orizu-cli/references/building-judges.md +0 -236
  143. package/vendor/skills/orizu-cli/references/optimization-reports.md +0 -179
  144. /package/vendor/skills/{orizu-cli → orizu}/scripts/test-app.mjs +0 -0
@@ -0,0 +1,512 @@
1
+ import { spawn, spawnSync } from 'node:child_process';
2
+ import { closeSync, constants, mkdtempSync, openSync, read as readFileDescriptor, rmSync, } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { Readable, Writable } from 'node:stream';
6
+ const CHILD_OUTPUT_PREFETCH_MAX_BYTES = 64 * 1024;
7
+ const CHILD_OUTPUT_READ_BUFFER_BYTES = 16 * 1024;
8
+ const EMPTY_FIFO_INITIAL_RETRY_MS = 4;
9
+ const EMPTY_FIFO_MAX_RETRY_MS = 64;
10
+ const FORWARDING_FAILURE_TERMINATION_TIMEOUT_MS = 250, LIVE_OUTPUT_STALL_TIMEOUT_MS = 1_500;
11
+ const LIVE_OUTPUT_RELAY_SCRIPT = [
12
+ "const { writeSync } = require('node:fs')",
13
+ 'process.stdout._handle?.setBlocking(true)',
14
+ 'let buffered = Buffer.alloc(0)',
15
+ 'process.stdin.on(\'data\', chunk => {',
16
+ ' buffered = Buffer.concat([buffered, chunk])',
17
+ ' while (buffered.length >= 4) {',
18
+ ' const length = buffered.readUInt32BE(0)',
19
+ ' if (buffered.length < length + 4) return',
20
+ ' const payload = buffered.subarray(4, length + 4)',
21
+ ' buffered = buffered.subarray(length + 4)',
22
+ ' for (let offset = 0; offset < payload.length; )',
23
+ ' offset += writeSync(1, payload, offset, payload.length - offset)',
24
+ " writeSync(3, '1')",
25
+ ' }',
26
+ '})',
27
+ "writeSync(3, 'R')",
28
+ ].join('\n');
29
+ class FileDescriptorSource extends Readable {
30
+ descriptor;
31
+ readBuffer = Buffer.allocUnsafe(CHILD_OUTPUT_READ_BUFFER_BYTES);
32
+ emptyRetryMilliseconds = EMPTY_FIFO_INITIAL_RETRY_MS;
33
+ isReadPending = false;
34
+ retryTimer;
35
+ pendingDestroy;
36
+ constructor(descriptor) {
37
+ super({ highWaterMark: CHILD_OUTPUT_PREFETCH_MAX_BYTES });
38
+ this.descriptor = descriptor;
39
+ }
40
+ _read() {
41
+ if (this.isReadPending || this.retryTimer || this.descriptor === undefined)
42
+ return;
43
+ this.isReadPending = true;
44
+ readFileDescriptor(this.descriptor, this.readBuffer, 0, this.readBuffer.byteLength, null, (error, bytesRead) => {
45
+ this.isReadPending = false;
46
+ if (this.pendingDestroy) {
47
+ this.finishDestroy();
48
+ return;
49
+ }
50
+ if (error) {
51
+ if (error.code === 'EINTR') {
52
+ this._read();
53
+ return;
54
+ }
55
+ if (['EAGAIN', 'EWOULDBLOCK'].includes(error.code ?? '')) {
56
+ const delay = this.emptyRetryMilliseconds;
57
+ this.emptyRetryMilliseconds = Math.min(EMPTY_FIFO_MAX_RETRY_MS, this.emptyRetryMilliseconds * 2);
58
+ this.retryTimer = setTimeout(() => {
59
+ this.retryTimer = undefined;
60
+ this._read();
61
+ }, delay);
62
+ return;
63
+ }
64
+ this.destroy(error);
65
+ return;
66
+ }
67
+ if (bytesRead === 0) {
68
+ this.push(null);
69
+ return;
70
+ }
71
+ this.emptyRetryMilliseconds = EMPTY_FIFO_INITIAL_RETRY_MS;
72
+ this.push(Buffer.from(this.readBuffer.subarray(0, bytesRead)));
73
+ });
74
+ }
75
+ _destroy(error, callback) {
76
+ if (this.retryTimer)
77
+ clearTimeout(this.retryTimer);
78
+ this.retryTimer = undefined;
79
+ this.pendingDestroy = { error, callback };
80
+ if (!this.isReadPending)
81
+ this.finishDestroy();
82
+ }
83
+ finishDestroy() {
84
+ const pendingDestroy = this.pendingDestroy;
85
+ if (!pendingDestroy)
86
+ return;
87
+ this.pendingDestroy = undefined;
88
+ const descriptor = this.descriptor;
89
+ this.descriptor = undefined;
90
+ try {
91
+ if (descriptor !== undefined)
92
+ closeSync(descriptor);
93
+ pendingDestroy.callback(pendingDestroy.error);
94
+ }
95
+ catch (error) {
96
+ pendingDestroy.callback(error instanceof Error ? error : new Error(String(error)));
97
+ }
98
+ }
99
+ }
100
+ class FileDescriptorSink extends Writable {
101
+ descriptor;
102
+ activeWriteCancellations = new Set();
103
+ isLiveOutputAvailable = true;
104
+ relay;
105
+ constructor(descriptor) {
106
+ super({ highWaterMark: 16 * 1024 });
107
+ this.descriptor = descriptor;
108
+ }
109
+ _write(chunk, _encoding, callback) {
110
+ if (!this.isLiveOutputAvailable) {
111
+ callback();
112
+ return;
113
+ }
114
+ let relay;
115
+ let hasSettled = false;
116
+ const settle = (error) => {
117
+ if (hasSettled)
118
+ return;
119
+ hasSettled = true;
120
+ if (error)
121
+ this.isLiveOutputAvailable = false;
122
+ this.activeWriteCancellations.delete(cancel);
123
+ relay?.acknowledgements.off('data', acknowledge);
124
+ relay?.child.off('exit', relayExited);
125
+ callback(error);
126
+ };
127
+ const acknowledge = () => settle();
128
+ const relayExited = () => settle(new Error('live output relay exited before write acknowledgement'));
129
+ const cancel = () => settle();
130
+ this.activeWriteCancellations.add(cancel);
131
+ try {
132
+ relay = this.relayProcess();
133
+ }
134
+ catch (error) {
135
+ settle(errorFromUnknown(error));
136
+ return;
137
+ }
138
+ void relay.ready.then(() => {
139
+ if (hasSettled)
140
+ return;
141
+ relay.acknowledgements.once('data', acknowledge);
142
+ relay.child.once('exit', relayExited);
143
+ const frame = Buffer.allocUnsafe(chunk.byteLength + 4);
144
+ frame.writeUInt32BE(chunk.byteLength);
145
+ chunk.copy(frame, 4);
146
+ relay.input.write(frame, error => { if (error)
147
+ settle(error); });
148
+ }).catch(error => settle(errorFromUnknown(error)));
149
+ }
150
+ async prepare() {
151
+ if (!this.isLiveOutputAvailable)
152
+ return;
153
+ await this.relayProcess().ready;
154
+ }
155
+ cancelPendingWrites() {
156
+ this.isLiveOutputAvailable = false;
157
+ const relay = this.relay;
158
+ this.relay = undefined;
159
+ relay?.input.destroy();
160
+ relay?.child.kill('SIGKILL');
161
+ for (const cancel of [...this.activeWriteCancellations])
162
+ cancel();
163
+ }
164
+ relayProcess() {
165
+ if (this.relay)
166
+ return this.relay;
167
+ const child = spawn('node', ['-e', LIVE_OUTPUT_RELAY_SCRIPT], {
168
+ stdio: ['pipe', this.descriptor, 'ignore', 'pipe'],
169
+ });
170
+ const input = child.stdin;
171
+ const acknowledgements = child.stdio[3];
172
+ if (!input || !acknowledgements)
173
+ throw new Error('live output relay has no control pipes');
174
+ child.on('error', () => { });
175
+ input.on('error', () => { });
176
+ acknowledgements.on('error', () => { });
177
+ const ready = new Promise((resolve, reject) => {
178
+ acknowledgements.once('data', chunk => {
179
+ if (chunk.includes('R'.charCodeAt(0)))
180
+ resolve();
181
+ else
182
+ reject(new Error('live output relay sent an invalid readiness acknowledgement'));
183
+ });
184
+ child.once('exit', () => reject(new Error('live output relay exited before readiness')));
185
+ child.once('error', reject);
186
+ });
187
+ void ready.catch(() => { });
188
+ child.unref();
189
+ void ready.then(() => {
190
+ input.unref?.();
191
+ acknowledgements.unref?.();
192
+ }, () => { });
193
+ const relay = { child, input, acknowledgements, ready };
194
+ child.once('exit', () => {
195
+ if (this.relay?.child !== child)
196
+ return;
197
+ this.relay = undefined;
198
+ });
199
+ this.relay = relay;
200
+ return relay;
201
+ }
202
+ }
203
+ export const childOutputSinks = {
204
+ stdout: new FileDescriptorSink(1),
205
+ stderr: new FileDescriptorSink(2),
206
+ };
207
+ const POST_LEADER_CAPTURE_DRAIN_TIMEOUT_MS = 250;
208
+ function closeDescriptor(descriptor) {
209
+ if (descriptor === undefined)
210
+ return;
211
+ try {
212
+ closeSync(descriptor);
213
+ }
214
+ catch {
215
+ // Best-effort cleanup after another operation has already failed.
216
+ }
217
+ }
218
+ function createOutputFifo(name) {
219
+ const root = mkdtempSync(join(tmpdir(), 'orizu-child-output-'));
220
+ const fifoPath = join(root, name);
221
+ let anchorDescriptor;
222
+ let readDescriptor;
223
+ let writeDescriptor;
224
+ try {
225
+ const creation = spawnSync('mkfifo', [fifoPath], { stdio: 'ignore' });
226
+ if (creation.error)
227
+ throw creation.error;
228
+ if (creation.status !== 0) {
229
+ throw new Error(`mkfifo exited with status ${creation.status ?? 'unknown'}`);
230
+ }
231
+ // The temporary read/write anchor prevents either open from waiting for
232
+ // its peer. The child inherits the blocking write end; the parent polls a
233
+ // nonblocking read end so descendant-held descriptors remain cancellable.
234
+ anchorDescriptor = openSync(fifoPath, constants.O_RDWR | constants.O_NONBLOCK);
235
+ readDescriptor = openSync(fifoPath, constants.O_RDONLY | constants.O_NONBLOCK);
236
+ writeDescriptor = openSync(fifoPath, constants.O_WRONLY);
237
+ closeDescriptor(anchorDescriptor);
238
+ anchorDescriptor = undefined;
239
+ const source = new FileDescriptorSource(readDescriptor);
240
+ readDescriptor = undefined;
241
+ rmSync(root, { force: true, recursive: true });
242
+ return { source, writeDescriptor };
243
+ }
244
+ catch (error) {
245
+ closeDescriptor(writeDescriptor);
246
+ closeDescriptor(readDescriptor);
247
+ closeDescriptor(anchorDescriptor);
248
+ rmSync(root, { force: true, recursive: true });
249
+ throw error;
250
+ }
251
+ }
252
+ export async function spawnPipedChild(command, args, environment) {
253
+ const stdout = createOutputFifo('stdout');
254
+ let stderr;
255
+ try {
256
+ stderr = createOutputFifo('stderr');
257
+ }
258
+ catch (error) {
259
+ stdout.source.destroy();
260
+ closeDescriptor(stdout.writeDescriptor);
261
+ throw error;
262
+ }
263
+ let child;
264
+ try {
265
+ // Begin the bounded parent-side drain before an immediate child crash can
266
+ // strand bytes in the child's userland stdout/stderr buffers.
267
+ stdout.source.read(0);
268
+ stderr.source.read(0);
269
+ child = spawn(command, args, {
270
+ env: environment,
271
+ stdio: ['inherit', stdout.writeDescriptor, stderr.writeDescriptor],
272
+ });
273
+ }
274
+ catch (error) {
275
+ stdout.source.destroy();
276
+ stderr.source.destroy();
277
+ throw error;
278
+ }
279
+ finally {
280
+ closeDescriptor(stdout.writeDescriptor);
281
+ closeDescriptor(stderr.writeDescriptor);
282
+ }
283
+ // `close` also waits for descendant-held stdio descriptors. Observe the
284
+ // leader's exit so completePipedChild can enforce its bounded EOF grace.
285
+ const exited = new Promise(resolve => child.once('exit', resolve));
286
+ try {
287
+ await new Promise((resolve, reject) => {
288
+ child.once('spawn', resolve);
289
+ child.once('error', reject);
290
+ });
291
+ }
292
+ catch (error) {
293
+ stdout.source.destroy();
294
+ stderr.source.destroy();
295
+ throw error;
296
+ }
297
+ return {
298
+ stdout: stdout.source,
299
+ stderr: stderr.source,
300
+ exited,
301
+ get signalCode() { return child.signalCode; },
302
+ kill: signal => { child.kill(signal); },
303
+ };
304
+ }
305
+ const liveSinkStates = new WeakMap();
306
+ function liveSinkState(destination) {
307
+ const existing = liveSinkStates.get(destination);
308
+ if (existing)
309
+ return existing;
310
+ const state = { hasFailed: false };
311
+ destination.on('error', () => { state.hasFailed = true; });
312
+ liveSinkStates.set(destination, state);
313
+ return state;
314
+ }
315
+ async function writeLiveOutput(destination, state, text) {
316
+ if (state.hasFailed)
317
+ return;
318
+ await new Promise(resolve => {
319
+ try {
320
+ destination.write(text, error => {
321
+ if (error)
322
+ state.hasFailed = true;
323
+ resolve();
324
+ });
325
+ }
326
+ catch {
327
+ state.hasFailed = true;
328
+ resolve();
329
+ }
330
+ });
331
+ }
332
+ export async function writeChildStderr(text) {
333
+ const sink = childOutputSinks.stderr;
334
+ let timer;
335
+ const didForward = await Promise.race([
336
+ writeLiveOutput(sink, liveSinkState(sink), `${text}\n`).then(() => true),
337
+ new Promise(resolve => {
338
+ timer = setTimeout(() => resolve(false), LIVE_OUTPUT_STALL_TIMEOUT_MS);
339
+ }),
340
+ ]);
341
+ if (timer)
342
+ clearTimeout(timer);
343
+ if (!didForward)
344
+ sink.cancelPendingWrites();
345
+ }
346
+ function errorFromUnknown(cause) {
347
+ return cause instanceof Error ? cause : new Error(String(cause));
348
+ }
349
+ async function waitForChildExit(child, timeoutMilliseconds) {
350
+ let timer;
351
+ const didExit = await Promise.race([
352
+ child.exited.then(() => true),
353
+ new Promise(resolve => {
354
+ timer = setTimeout(() => resolve(false), timeoutMilliseconds);
355
+ }),
356
+ ]);
357
+ if (timer)
358
+ clearTimeout(timer);
359
+ return didExit;
360
+ }
361
+ async function terminateAfterForwardingFailure(child, forwarding, captureDrain, error) {
362
+ child.kill('SIGTERM');
363
+ for (const stream of forwarding)
364
+ stream.stopLiveOutput();
365
+ child.stdout.destroy();
366
+ child.stderr.destroy();
367
+ await captureDrain;
368
+ if (!await waitForChildExit(child, FORWARDING_FAILURE_TERMINATION_TIMEOUT_MS)) {
369
+ child.kill('SIGKILL');
370
+ await waitForChildExit(child, FORWARDING_FAILURE_TERMINATION_TIMEOUT_MS);
371
+ }
372
+ throw error;
373
+ }
374
+ export async function completePipedChild(child, forwarding) {
375
+ const captureDrain = Promise.allSettled(forwarding.map(stream => stream.completed));
376
+ const firstFailure = Promise.race(forwarding.map(stream => stream.failure));
377
+ const firstOutcome = await Promise.race([
378
+ child.exited.then(status => ({ kind: 'exit', status })),
379
+ firstFailure.then(error => ({ kind: 'failure', error })),
380
+ ]);
381
+ if (firstOutcome.kind === 'failure') {
382
+ return terminateAfterForwardingFailure(child, forwarding, captureDrain, firstOutcome.error);
383
+ }
384
+ const status = firstOutcome.status;
385
+ const waitForCaptureDrain = async () => {
386
+ let timer;
387
+ const didDrain = await Promise.race([
388
+ captureDrain.then(() => true),
389
+ new Promise(resolve => {
390
+ timer = setTimeout(() => resolve(false), POST_LEADER_CAPTURE_DRAIN_TIMEOUT_MS);
391
+ }),
392
+ ]);
393
+ if (timer)
394
+ clearTimeout(timer);
395
+ return didDrain;
396
+ };
397
+ const throwIfCaptureFailed = async () => {
398
+ await captureDrain;
399
+ const failure = forwarding.map(stream => stream.failureReason()).find(Boolean);
400
+ if (failure)
401
+ throw failure;
402
+ };
403
+ // Preserve ordinary live forwarding when EOF is already on its way. If a
404
+ // blocked sink or descendant-held descriptor prevents that bounded drain,
405
+ // switch to capture-only before enforcing the same bounded EOF grace.
406
+ if (await waitForCaptureDrain()) {
407
+ await throwIfCaptureFailed();
408
+ return status;
409
+ }
410
+ for (const stream of forwarding)
411
+ stream.stopLiveOutput();
412
+ if (await waitForCaptureDrain()) {
413
+ await throwIfCaptureFailed();
414
+ return status;
415
+ }
416
+ for (const stream of forwarding)
417
+ stream.expectSourceClose();
418
+ child.stdout.destroy();
419
+ child.stderr.destroy();
420
+ await throwIfCaptureFailed();
421
+ return status;
422
+ }
423
+ export async function runPipedChild(command, args, environment, onSpawn, capture, observeChild) {
424
+ await Promise.all([childOutputSinks.stdout.prepare(), childOutputSinks.stderr.prepare()]);
425
+ const child = await spawnPipedChild(command, args, environment);
426
+ observeChild?.(child);
427
+ const forwarding = [
428
+ teeChildOutput(child.stdout, childOutputSinks.stdout, capture.stdout),
429
+ teeChildOutput(child.stderr, childOutputSinks.stderr, capture.stderr),
430
+ ];
431
+ const completion = completePipedChild(child, forwarding);
432
+ const firstForwardingFailure = Promise.race(forwarding.map(stream => stream.failure)).then(error => { throw error; });
433
+ try {
434
+ await Promise.race([onSpawn(), firstForwardingFailure]);
435
+ const exitCode = await completion;
436
+ return { status: child.signalCode ? null : exitCode };
437
+ }
438
+ catch (cause) {
439
+ child.kill('SIGTERM');
440
+ await completion.catch(() => undefined);
441
+ throw cause;
442
+ }
443
+ }
444
+ /**
445
+ * Forward a child stream live while retaining a bounded caller-owned copy.
446
+ * Transient sink slowness pushes back; a stall degrades safely to capture-only.
447
+ */
448
+ export function teeChildOutput(source, destination, capture) {
449
+ let isLiveOutputEnabled = true;
450
+ let isSourceCloseExpected = false;
451
+ let unexpectedFailure;
452
+ let remainingLiveOutputWaitMilliseconds = LIVE_OUTPUT_STALL_TIMEOUT_MS;
453
+ let liveOutputWaitFinishedAt = performance.now();
454
+ let resolveLiveOutputStopped = () => { };
455
+ const liveOutputStopped = new Promise(resolve => { resolveLiveOutputStopped = resolve; });
456
+ const stopLiveOutput = () => {
457
+ if (!isLiveOutputEnabled)
458
+ return;
459
+ isLiveOutputEnabled = false;
460
+ if (destination instanceof FileDescriptorSink)
461
+ destination.cancelPendingWrites();
462
+ resolveLiveOutputStopped();
463
+ };
464
+ const sinkState = liveSinkState(destination);
465
+ const forward = async (text) => {
466
+ capture(text);
467
+ if (!isLiveOutputEnabled)
468
+ return;
469
+ const waitStartedAt = performance.now();
470
+ remainingLiveOutputWaitMilliseconds = Math.min(LIVE_OUTPUT_STALL_TIMEOUT_MS, remainingLiveOutputWaitMilliseconds + waitStartedAt - liveOutputWaitFinishedAt);
471
+ let stallTimer;
472
+ const wasForwarded = await Promise.race([
473
+ writeLiveOutput(destination, sinkState, text).then(() => true),
474
+ liveOutputStopped.then(() => false),
475
+ new Promise(resolve => {
476
+ stallTimer = setTimeout(() => resolve(false), remainingLiveOutputWaitMilliseconds);
477
+ }),
478
+ ]);
479
+ if (stallTimer)
480
+ clearTimeout(stallTimer);
481
+ liveOutputWaitFinishedAt = performance.now();
482
+ remainingLiveOutputWaitMilliseconds -= liveOutputWaitFinishedAt - waitStartedAt;
483
+ if (!wasForwarded || remainingLiveOutputWaitMilliseconds <= 0)
484
+ stopLiveOutput();
485
+ };
486
+ const completed = (async () => {
487
+ const decoder = new TextDecoder();
488
+ for await (const chunk of source) {
489
+ const text = decoder.decode(chunk, { stream: true });
490
+ if (text)
491
+ await forward(text);
492
+ }
493
+ const remainder = decoder.decode();
494
+ if (remainder)
495
+ await forward(remainder);
496
+ })();
497
+ let resolveFailure = () => { };
498
+ const failure = new Promise(resolve => { resolveFailure = resolve; });
499
+ void completed.catch(cause => {
500
+ if (isSourceCloseExpected)
501
+ return;
502
+ unexpectedFailure = errorFromUnknown(cause);
503
+ resolveFailure(unexpectedFailure);
504
+ });
505
+ return {
506
+ completed,
507
+ failure,
508
+ failureReason: () => unexpectedFailure,
509
+ expectSourceClose: () => { isSourceCloseExpected = true; },
510
+ stopLiveOutput,
511
+ };
512
+ }
@@ -0,0 +1,6 @@
1
+ export type Cleanup = () => void;
2
+ /** Attempt every disposer even when an earlier cleanup fails. */
3
+ export declare function cleanupAll(cleanups: readonly Cleanup[], message: string): void;
4
+ /** Preserve the operation failure together with every cleanup failure. */
5
+ export declare function throwWithCleanup(operationError: unknown, cleanups: readonly Cleanup[], message: string): never;
6
+ //# sourceMappingURL=cleanup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cleanup.d.ts","sourceRoot":"","sources":["../src/cleanup.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,OAAO,GAAG,MAAM,IAAI,CAAA;AAchC,iEAAiE;AACjE,wBAAgB,UAAU,CACxB,QAAQ,EAAE,SAAS,OAAO,EAAE,EAC5B,OAAO,EAAE,MAAM,GACd,IAAI,CAKN;AAED,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAC9B,cAAc,EAAE,OAAO,EACvB,QAAQ,EAAE,SAAS,OAAO,EAAE,EAC5B,OAAO,EAAE,MAAM,GACd,KAAK,CAMP"}
@@ -0,0 +1,27 @@
1
+ function collectCleanupFailures(cleanups) {
2
+ const failures = [];
3
+ for (const cleanup of cleanups) {
4
+ try {
5
+ cleanup();
6
+ }
7
+ catch (error) {
8
+ failures.push(error);
9
+ }
10
+ }
11
+ return failures;
12
+ }
13
+ /** Attempt every disposer even when an earlier cleanup fails. */
14
+ export function cleanupAll(cleanups, message) {
15
+ const failures = collectCleanupFailures(cleanups);
16
+ if (failures.length > 0) {
17
+ throw new AggregateError(failures, message);
18
+ }
19
+ }
20
+ /** Preserve the operation failure together with every cleanup failure. */
21
+ export function throwWithCleanup(operationError, cleanups, message) {
22
+ const cleanupFailures = collectCleanupFailures(cleanups);
23
+ if (cleanupFailures.length > 0) {
24
+ throw new AggregateError([operationError, ...cleanupFailures], message);
25
+ }
26
+ throw operationError;
27
+ }
@@ -0,0 +1,2 @@
1
+ export declare function renderCliAuthBrowserPage(status: 'success' | 'error'): string;
2
+ //# sourceMappingURL=cli-auth-browser-page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-auth-browser-page.d.ts","sourceRoot":"","sources":["../src/cli-auth-browser-page.ts"],"names":[],"mappings":"AAAA,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,MAAM,CA2G5E"}
@@ -0,0 +1,107 @@
1
+ export function renderCliAuthBrowserPage(status) {
2
+ const isSuccess = status === 'success';
3
+ const eyebrow = isSuccess ? '// cli login complete' : '// cli login';
4
+ const title = isSuccess ? 'Browser authorization complete' : 'Missing authorization code';
5
+ const detail = isSuccess
6
+ ? 'Your terminal will finish connecting the Orizu CLI. You can close this tab.'
7
+ : 'Close this tab and run orizu login again to start a fresh browser request.';
8
+ const accent = isSuccess ? '#E8923C' : '#C8442A';
9
+ return `<!doctype html>
10
+ <html lang="en">
11
+ <head>
12
+ <meta charset="utf-8">
13
+ <meta name="viewport" content="width=device-width, initial-scale=1">
14
+ <title>Orizu CLI Login</title>
15
+ <style>
16
+ @import url('https://fonts.googleapis.com/css2?family=Geist+Mono:wght@400;500;700&display=swap');
17
+ :root {
18
+ color-scheme: light;
19
+ --paper: #F4EFE3;
20
+ --border: #E6DFCE;
21
+ --ink: #353535;
22
+ --muted: #6B6358;
23
+ }
24
+ * { box-sizing: border-box; }
25
+ body {
26
+ margin: 0;
27
+ min-height: 100vh;
28
+ background: #FFFFFF;
29
+ color: #4B4B4B;
30
+ font-family: 'Geist Mono', Menlo, Monaco, Consolas, monospace;
31
+ letter-spacing: -0.025em;
32
+ }
33
+ main {
34
+ min-height: 100vh;
35
+ display: flex;
36
+ align-items: center;
37
+ justify-content: center;
38
+ padding: 48px 24px;
39
+ }
40
+ section {
41
+ width: min(100%, 520px);
42
+ text-align: center;
43
+ }
44
+ .brand {
45
+ display: flex;
46
+ flex-direction: column;
47
+ align-items: center;
48
+ gap: 12px;
49
+ margin-bottom: 28px;
50
+ }
51
+ .brand svg {
52
+ width: 42px;
53
+ height: 42px;
54
+ color: #202124;
55
+ }
56
+ .brand span {
57
+ color: #000000;
58
+ font-size: 16px;
59
+ font-weight: 700;
60
+ }
61
+ .eyebrow {
62
+ margin: 0 0 12px;
63
+ color: ${accent};
64
+ font-size: 12px;
65
+ text-transform: lowercase;
66
+ }
67
+ h1 {
68
+ margin: 0 0 8px;
69
+ color: var(--ink);
70
+ font-size: 24px;
71
+ line-height: 1.15;
72
+ letter-spacing: -0.05em;
73
+ }
74
+ .detail {
75
+ margin: 0;
76
+ color: var(--muted);
77
+ font-size: 13px;
78
+ line-height: 1.6;
79
+ }
80
+ </style>
81
+ </head>
82
+ <body>
83
+ <main>
84
+ <section>
85
+ <div class="brand" aria-hidden="true">
86
+ <svg viewBox="0 0 2048 2048" xmlns="http://www.w3.org/2000/svg">
87
+ <g fill="currentColor">
88
+ <polygon points="1545,879 787,1195 1189,1577"></polygon>
89
+ <polygon points="992,624 444,1104 688,1338"></polygon>
90
+ <polygon points="602,1593 1180,1594 768,1202"></polygon>
91
+ <polygon points="1650,1195 1398,1203 1204,1594 1229,1593"></polygon>
92
+ <polygon points="807,413 751,509 823,744 980,608"></polygon>
93
+ <polygon points="441,1127 592,1572 681,1356"></polygon>
94
+ <polygon points="793,399 611,457 604,471 658,602"></polygon>
95
+ <polygon points="593,493 498,667 636,600"></polygon>
96
+ </g>
97
+ </svg>
98
+ <span>orizu</span>
99
+ </div>
100
+ <p class="eyebrow">${eyebrow}</p>
101
+ <h1>${title}</h1>
102
+ <p class="detail">${detail}</p>
103
+ </section>
104
+ </main>
105
+ </body>
106
+ </html>`;
107
+ }