osborn 0.9.13 → 0.9.15

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 (2) hide show
  1. package/dist/index.js +38 -16
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -15,6 +15,8 @@ import { dirname, join } from 'node:path';
15
15
  import { fileURLToPath } from 'node:url';
16
16
  import { spawn } from 'node:child_process';
17
17
  import { homedir, tmpdir } from 'node:os';
18
+ import { PassThrough } from 'node:stream';
19
+ import { createGunzip } from 'node:zlib';
18
20
  // Resolve __dirname for this ESM module so we can find sibling files (e.g.
19
21
  // meeting-output.html) relative to the compiled JS location, NOT process.cwd().
20
22
  // In production cwd is the user's workspace; the static file lives next to dist/index.js.
@@ -376,8 +378,26 @@ function startApiServer(workingDir, port) {
376
378
  const targetWorkDir = url.searchParams.get('targetWorkDir');
377
379
  const tmpDir = mkdtempSync(join(tmpdir(), 'osborn-import-'));
378
380
  const tarProc = spawn('tar', ['-xf', '-', '-C', tmpDir]);
379
- // Streaming: pipe request body directly to tar stdin no buffering
380
- req.pipe(tarProc.stdin);
381
+ // Stream-sniff the first chunk to detect gzip magic bytes (0x1f 0x8b).
382
+ // Then route through createGunzip() if gzip, otherwise pipe raw to tar.
383
+ // This avoids any reliance on Content-Type or Content-Encoding headers.
384
+ const passthrough = new PassThrough();
385
+ let sniffDone = false;
386
+ req.once('data', (firstChunk) => {
387
+ sniffDone = true;
388
+ const isGzip = firstChunk[0] === 0x1f && firstChunk[1] === 0x8b;
389
+ passthrough.write(firstChunk);
390
+ req.pipe(passthrough);
391
+ const source = isGzip ? passthrough.pipe(createGunzip()) : passthrough;
392
+ source.pipe(tarProc.stdin);
393
+ });
394
+ req.once('end', () => {
395
+ if (!sniffDone) {
396
+ // Empty body — just end tar stdin
397
+ passthrough.end();
398
+ tarProc.stdin.end();
399
+ }
400
+ });
381
401
  tarProc.stdin.on('error', (err) => {
382
402
  console.error('[import] tar stdin error', err);
383
403
  tarProc.kill('SIGTERM');
@@ -522,22 +542,24 @@ function startApiServer(workingDir, port) {
522
542
  }
523
543
  const tmpExtractDir = mkdtempSync(join(tmpdir(), 'osborn-import-'));
524
544
  try {
525
- // Reassemble chunks into a single stream and pipe to tar
545
+ // Reassemble all chunks into a combined buffer, then sniff first 2 bytes
546
+ // to detect gzip magic (0x1f 0x8b). Route through createGunzip() if gzip,
547
+ // otherwise pass raw bytes — always using tar -xf (no -z flag).
548
+ const chunkBuffers = [];
549
+ for (const chunkFile of expectedChunks) {
550
+ chunkBuffers.push(readFileSync(join(uploadDir, chunkFile)));
551
+ }
552
+ const combined = Buffer.concat(chunkBuffers);
553
+ const isGzip = combined[0] === 0x1f && combined[1] === 0x8b;
526
554
  const tarProc = spawn('tar', ['-xf', '-', '-C', tmpExtractDir]);
527
- // Stream chunks in order to tar stdin
555
+ // Feed combined buffer through gunzip (if needed) then into tar stdin
556
+ const feedStream = new PassThrough();
557
+ const tarInput = isGzip ? feedStream.pipe(createGunzip()) : feedStream;
558
+ tarInput.pipe(tarProc.stdin);
559
+ feedStream.end(combined);
528
560
  const streamChunks = async () => {
529
- for (const chunkFile of expectedChunks) {
530
- const chunkData = readFileSync(join(uploadDir, chunkFile));
531
- await new Promise((resolve, reject) => {
532
- tarProc.stdin.write(chunkData, (err) => {
533
- if (err)
534
- reject(err);
535
- else
536
- resolve();
537
- });
538
- });
539
- }
540
- tarProc.stdin.end();
561
+ // feeding is already initiated above; just return a resolved promise
562
+ await Promise.resolve();
541
563
  };
542
564
  streamChunks().catch(err => {
543
565
  console.error('[import-finalize] chunk stream error', err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "osborn",
3
- "version": "0.9.13",
3
+ "version": "0.9.15",
4
4
  "description": "Voice AI coding assistant - local agent that connects to Osborn frontend",
5
5
  "type": "module",
6
6
  "bin": {