osborn 0.9.12 → 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.
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);
@@ -13,6 +13,8 @@ Include:
13
13
  - **Test results**: What was tried, what it showed, what it ruled out
14
14
 
15
15
  Keep this under 800 characters. This stays in the compact summary — it is NOT written to disk.
16
+ - **ANNOUNCE**: End HANDOFF_STATE with one line formatted as:
17
+ `ANNOUNCE: <natural-language sentence for Osborn to speak aloud when resuming — mention that session memory was crystallized and how many skills/decisions were updated. Keep it to one sentence, conversational tone, like a colleague mentioning it in passing.>`
16
18
 
17
19
  ---
18
20
 
@@ -10,6 +10,8 @@ Session workspace: ${workspacePath}
10
10
  · You CAN write other files to the workspace (detailed findings, diffs, notes, code samples) that the user sees in a files panel
11
11
 
12
12
  Working principle: SPEAK the thinking, WRITE the details.
13
+
14
+ COMPACT RESUME BEHAVIOR: When this conversation has been compacted, the compact summary may contain a line beginning with "ANNOUNCE:". If it does, speak that line aloud as your first response before picking up any pending task. This is NOT recapping the summary — it is delivering a brief memory-crystallization update that the user is expecting. After speaking it, continue normally.
13
15
  </context>
14
16
 
15
17
  <objective>
package/dist/prompts.js CHANGED
@@ -554,6 +554,8 @@ Quick lookups (1-2 calls) you can do directly. Everything else goes to an agent.
554
554
  You do NOT produce findings from training data alone. You use tools to confirm every specific fact — file names, version numbers, function signatures, configuration values, URLs. If a tool is not available to verify a claim, you say so.
555
555
 
556
556
  IF INTERRUPTED OR RESTARTED: Check ~/.claude/projects/ subagents folder for recent sub-agent JSONL files. Read the last entries to understand what was completed. Resume from that point.
557
+
558
+ Exception: if the compact summary contains a line beginning with "ANNOUNCE:", speak that line to the user as your first response before picking up the task. This is NOT recapping the summary — it is delivering a brief memory-crystallization update that the user is expecting.
557
559
  </role>
558
560
 
559
561
  <write-rules>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "osborn",
3
- "version": "0.9.12",
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": {