@volter/supercode-teams 0.3.155 → 0.3.156

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 CHANGED
@@ -52,6 +52,12 @@ Settled read bodies expire after 30 days, with explicit placeholders and retaine
52
52
  its originals. Expired read headers are capped at 1,000,000 and carrier/terminal event log tables at 10,000 rows each. Read the
53
53
  [dated qualification scope](../../docs/evidence/sqlite-mail-store-2026-10-08.md) before inferring platform support.
54
54
 
55
+ Native mail-history reads process the opened file's initial extent in 64 KiB blocks,
56
+ holding one unfinished record instead of the whole transcript and line array. Returned
57
+ mail history and individual records can still be large. The
58
+ [allocation evidence](../../docs/evidence/mail-transcript-stream-2026-10-08.md) names
59
+ the source boundary and the remaining runtime qualification.
60
+
55
61
  `supercode teams rpc mail.statistics` reads local storage counts and import/retention receipts. It is refused remotely
56
62
  and through the app door. Writer groups count stored envelope copies, not call attempts or quotas; database page
57
63
  counts exclude physical WAL size. Successful imports record their actual source reads, bytes, batch sizes and time.
@@ -1,17 +1,48 @@
1
1
  // What a session's transcript says about its mail (crates/harness/src/mail_transcript.rs, ported line for line): the
2
2
  // lines its own user typed, the lines it took from its harness's own channels, its answers, and when each filed message
3
- // first reached the model. The mailbox records a message as sent; only the transcript records it delivered. A transcript
4
- // is read only when a call names the message or the session it is about (one named read, never a sweep).
3
+ // first reached the model. The mailbox records a message as sent; only the transcript records it delivered. Named mail
4
+ // reads use this reader; agent upkeep also reads changed Claude transcripts to copy the person's typed lines.
5
5
  import { readdirSync, statSync } from 'node:fs';
6
- import { readFile } from 'node:fs/promises';
6
+ import { open } from 'node:fs/promises';
7
7
  import { join } from 'node:path';
8
8
  import { blake3Hex } from './envelope.mjs';
9
9
 
10
- // A transcript is read off the daemon's event loop (its file read awaited) and parsed in turns of YIELD_LINES lines: one
11
- // large session's history never holds every other door while it is read.
10
+ // Read one native record at a time. Reading the whole file and splitting it before the first yield retained every
11
+ // tool result while parsing unrelated mail. The read buffer is bounded; one record and the returned mail remain.
12
+ const READ_BYTES = 64 * 1024;
12
13
  const YIELD_LINES = 2000;
13
14
  const yieldTurn = () => new Promise((resolve) => setImmediate(resolve));
14
15
 
16
+ async function* transcriptLines(path) {
17
+ const file = await open(path, 'r');
18
+ try {
19
+ // This pass reads the opened file's initial extent; an appended turn belongs to the next pass.
20
+ const { size } = await file.stat();
21
+ let position = 0, fragments = [];
22
+ while (position < size) {
23
+ const buffer = Buffer.allocUnsafe(Math.min(READ_BYTES, size - position));
24
+ const { bytesRead } = await file.read(buffer, 0, buffer.length, position);
25
+ if (!bytesRead) break;
26
+ position += bytesRead;
27
+ const chunk = buffer.subarray(0, bytesRead);
28
+ let start = 0;
29
+ for (let end = chunk.indexOf(10); end !== -1; end = chunk.indexOf(10, start)) {
30
+ const line = fragments.length
31
+ ? Buffer.concat([...fragments, chunk.subarray(start, end)]).toString('utf8')
32
+ : chunk.toString('utf8', start, end);
33
+ fragments = [];
34
+ start = end + 1;
35
+ yield line;
36
+ }
37
+ if (start < bytesRead) fragments.push(chunk.subarray(start));
38
+ // Yield even across a long record or many ignored lines, before another read/parse block.
39
+ await yieldTurn();
40
+ }
41
+ // Preserve the former split's final non-newline record, including its JSON-parse refusal if incomplete.
42
+ if (fragments.length) yield Buffer.concat(fragments).toString('utf8');
43
+ } finally { await file.close(); }
44
+ }
45
+
15
46
  export const TYPED_ID_PREFIX = 'u-';
16
47
  const CHANNEL_ID_PREFIX = 'c-';
17
48
  const ANSWER_ID_PREFIX = 'a-';
@@ -88,10 +119,9 @@ export async function readClaude(path) {
88
119
  const takenIn = new Map(); // words → [times]
89
120
  const early = []; // [words, ids, index, at]
90
121
  const deliver = (id, at) => { if (!mail.delivered.has(id)) mail.delivered.set(id, at); };
91
- const lines = (await readFile(path, 'utf8')).split('\n');
92
- for (let index = 0; index < lines.length; index++) {
93
- if (index && index % YIELD_LINES === 0) await yieldTurn();
94
- const line = lines[index];
122
+ let lineIndex = 0;
123
+ for await (const line of transcriptLines(path)) {
124
+ if (++lineIndex % YIELD_LINES === 0) await yieldTurn();
95
125
  const prompt = line.includes('"promptSource"') || line.includes('"type":"user"');
96
126
  const queue = line.includes('"queue-operation"');
97
127
  const attachment = line.includes('"queued_command"');
@@ -214,10 +244,9 @@ function codexRenderedMailIds(line) {
214
244
  /** Final answers and delivered mail from a Codex session's own transcript (`read_codex_answers`). */
215
245
  export async function readCodex(path) {
216
246
  const mail = { typed: [], answers: [], channel: [], delivered: new Map() };
217
- const lines = (await readFile(path, 'utf8')).split('\n');
218
- for (let index = 0; index < lines.length; index++) {
219
- if (index && index % YIELD_LINES === 0) await yieldTurn();
220
- const line = lines[index];
247
+ let lineIndex = 0;
248
+ for await (const line of transcriptLines(path)) {
249
+ if (++lineIndex % YIELD_LINES === 0) await yieldTurn();
221
250
  const rendered = line.includes('cross-session-message id=');
222
251
  if (!rendered && !line.includes('"final_answer"')) continue;
223
252
  let record; try { record = JSON.parse(line); } catch { continue; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-teams",
3
- "version": "0.3.155",
3
+ "version": "0.3.156",
4
4
  "type": "module",
5
5
  "description": "A machine daemon serving this machine's doors, and a local or remote Teams server for shared session discovery, OpenTelemetry ingestion and access to enrolled machines.",
6
6
  "license": "MIT",