@volter/supercode-teams 0.3.197 → 0.3.198

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
@@ -61,8 +61,8 @@ Before the first SQLite start, `supercode teams mail migration-check --home /abs
61
61
  reads that guard locally without a daemon connection or migration. Admission is not proof that the import will finish.
62
62
  The [source boundary and pending qualification](../../docs/evidence/legacy-wrapper-migration-2026-10-08.md)
63
63
  name the trusted legacy ABI, loss of later legacy diagnostics and existing retirement recovery.
64
- Settled read bodies expire after 30 days, with explicit placeholders and retained thread headers; pending mail keeps
65
- its originals. Expired read headers are capped at 1,000,000 and carrier/terminal event log tables at 10,000 rows each. Read the
64
+ Mail bodies are kept like their headers. Read headers whose body is not kept show an explicit placeholder and are
65
+ capped at 1,000,000; carrier/terminal event log tables keep 10,000 rows each. Read the
66
66
  [dated qualification scope](../../docs/evidence/sqlite-mail-store-2026-10-08.md) before inferring platform support.
67
67
 
68
68
  Native mail-history reads process the opened file's initial extent in 64 KiB blocks,
package/machine.mjs CHANGED
@@ -84,7 +84,7 @@ import { WorkspaceContexts, fromContext } from '@volter/teams/context';
84
84
  import { TeamsClient } from '@volter/teams/http-client';
85
85
  import { supercodeHome } from '@volter/teams/home';
86
86
  import { readMailHistory, machineName } from './mail-history.mjs';
87
- import { randomBytes, createHash, randomUUID } from 'node:crypto';
87
+ import { randomBytes, createHash } from 'node:crypto';
88
88
  import { WebSocket } from 'ws';
89
89
  import { TmuxTerminalHost, classifyAttentionSignal, composerHoldsDraft, composerReadyForInput } from '@volter/supercode-terminal';
90
90
  // The ConPTY host is read from the namespace, where a terminal package too old to have it gives undefined rather
@@ -909,30 +909,16 @@ export class TeamsMachine extends MachineCore {
909
909
  const reading = await this.#serveRequest('harness.v1.sessions.activity_under', { pids: [session.rootPid] }, 4_000);
910
910
  const native = reading?.activities?.find(row => row.pid === session.rootPid)?.activity;
911
911
  if (native?.harness !== expected.harness || native.session_id !== expected.session) return { delivered: false, reason: 'the pane no longer runs the addressed native session; no input submitted' };
912
- // A lifecycle wake belongs to the board's owned session, not the /btw
913
- // composer. Dismiss only that identified panel; never answer a question,
914
- // approve a permission, clear a draft or interrupt the underlying turn.
912
+ // A board wake is never typed into a Claude /btw panel, and the daemon sends
913
+ // no keys to close it: the wake waits unread and the board reports the
914
+ // session's `dialog open` turn to the manager.
915
915
  const boardWake = messageIds?.length && messageIds.every(id => {
916
916
  const stored = this.mailStore.findIn(address, id);
917
917
  const envelope = stored ? this.mailStore.body(stored) : null;
918
918
  return envelope?.kind === 'notice' && parseAddress(envelope.from)?.harness === 'board';
919
919
  });
920
- if (boardWake && claudeSideQuestionPanel(await this.host.capture(session.id, { lines: 200, preserveEscapes: true }), native)) {
921
- const owner = randomUUID();
922
- if (await this.host.acquireInput?.(session.id, owner)) {
923
- try {
924
- await new Promise(resolve => setTimeout(resolve, COMPOSER_RECHECK_MS));
925
- const latest = await this.#serveRequest('harness.v1.sessions.activity_under', { pids: [session.rootPid] }, 4_000);
926
- const current = latest?.activities?.find(row => row.pid === session.rootPid)?.activity;
927
- const screen = await this.host.capture(session.id, { lines: 200, preserveEscapes: true });
928
- if (!this.stopping && current?.session_id === expected.session && claudeSideQuestionPanel(screen, current)) {
929
- await this.host.sendInput(session.id, '\u001b', { submitMode: 'single', inputOwner: owner, deadlineMs: Date.now() + 5_000 });
930
- this.log.append({ principal: null, cap: 'control', target: `pane:${pane}`, outcome: 'board-side-question-dismissed', detail: JSON.stringify({ address, message_ids: messageIds }) });
931
- await new Promise(resolve => setTimeout(resolve, COMPOSER_RECHECK_MS));
932
- }
933
- } finally { await this.host.releaseInput?.(session.id, owner); }
934
- }
935
- }
920
+ if (boardWake && claudeSideQuestionPanel(await this.host.capture(session.id, { lines: 200, preserveEscapes: true }), native))
921
+ return { delivered: false, reason: 'a Claude /btw panel is open on the pane; the board wake waits unread and no keys were sent' };
936
922
  const result = await this.#paneTurn(session, pane, text, { messageIds, harness });
937
923
  if (result.refused) throw new Error(result.refused.reason);
938
924
  return result.waits ?? result;
package/mail/sqlite.mjs CHANGED
@@ -10,12 +10,12 @@ import { RECORD_SCHEMA } from '../record-state.mjs';
10
10
 
11
11
  export const MAIL_SCHEMA_VERSION = 3;
12
12
  export const MAIL_RETENTION = Object.freeze({
13
- bodyDays: 30,
13
+ relayTextDays: 30,
14
14
  maxReadHeaders: 1_000_000,
15
15
  maxCarrierRows: 10_000,
16
16
  maxTerminalLogRows: 10_000,
17
17
  });
18
- export const RETAINED_BODY = `[this message's body was let go after ${MAIL_RETENTION.bodyDays} days (mail retention); its header and thread are kept]`;
18
+ export const RETAINED_BODY = `[this message's body was not kept; its header and thread are kept]`;
19
19
 
20
20
  const SCHEMA = `
21
21
  CREATE TABLE IF NOT EXISTS mail_metadata (
@@ -331,51 +331,24 @@ export class MailDatabase extends EventEmitter {
331
331
  });
332
332
  }
333
333
 
334
- /** Only settled read mail is eligible. Pending deliveries, subscriptions and
335
- * claim holders protect their originals; an unresolved message never loses its body. */
334
+ /** Mail bodies are kept. Retention removes old relay scratch copies (their
335
+ * originals stay in the mailbox) and caps headers whose body is already gone. */
336
336
  retain(now = Date.now()) {
337
- const cutoff = now - this.retention.bodyDays * 86_400_000;
337
+ const cutoff = now - this.retention.relayTextDays * 86_400_000;
338
338
  return this.transaction(() => {
339
- const expired = this.statement(`UPDATE messages SET body_digest = coalesce(body_digest, supercode_sha256(body)), body = NULL, first_line = '', body_expired_at_ms = ?
340
- WHERE state = 'cur' AND created_at_ms < ? AND body IS NOT NULL
341
- AND NOT EXISTS (SELECT 1 FROM deliveries d WHERE d.mailbox = messages.mailbox AND d.message = messages.id)
342
- AND NOT EXISTS (SELECT 1 FROM subscriptions s WHERE s.mailbox = messages.mailbox AND s.message = messages.id)`)
343
- .run(now, cutoff).changes;
344
- // Completed native questions duplicate an envelope and its answer input.
345
- // Keep the claim/binding/header so a repeat cannot submit another answer,
346
- // but let go of those text copies after the mailbox copies have expired.
347
- const questions = this.statement(`UPDATE mail_records AS r SET record = json_set(
348
- json_remove(record, '$.native', '$.answers'), '$.envelope.body', ?, '$.body_expired_at_ms', ?)
349
- WHERE collection = 'native_questions' AND json_type(record, '$.envelope') = 'object'
350
- AND json_type(record, '$.body_expired_at_ms') IS NULL
351
- AND json_extract(record, '$.envelope.created_at_ms') < ?
352
- AND EXISTS (SELECT 1 FROM mail_records d WHERE d.collection = 'native_questions'
353
- AND d.key = substr(r.key, 1, instr(r.key, '.') - 1) || '.delivered' AND CAST(d.record AS INTEGER) < ?)
354
- AND NOT EXISTS (SELECT 1 FROM mail_records w WHERE w.collection = 'native_questions'
355
- AND w.key = substr(r.key, 1, instr(r.key, '.') - 1) || '.waiting')
356
- AND NOT EXISTS (SELECT 1 FROM messages m WHERE m.id = json_extract(r.record, '$.envelope.id') AND m.body IS NOT NULL)`)
357
- .run(RETAINED_BODY, now, cutoff, cutoff).changes;
358
339
  const relayRecords = this.statement(`DELETE FROM mail_records AS r WHERE collection = 'relays'
359
340
  AND (key LIKE '%/turn.txt' OR key LIKE '%/receipt.json') AND updated_at_ms < ?
360
341
  AND NOT EXISTS (SELECT 1 FROM mail_records q WHERE q.collection = 'relays'
361
342
  AND q.key = substr(r.key, 1, instr(r.key, '/')) || 'queue.json')`).run(cutoff).changes;
362
- const launchInputs = this.statement(`UPDATE mail_records SET record = json_set(
363
- json_remove(record, '$.initialInput', '$.claudeInput.text'), '$.input_body_expired_at_ms', ?)
364
- WHERE collection = 'native_launches' AND json_type(record, '$.input_body_expired_at_ms') IS NULL
365
- AND json_type(record, '$.input_settled_at_ms') = 'integer'
366
- AND json_extract(record, '$.input_settled_at_ms') < ?
367
- AND (json_type(record, '$.initialInput') = 'text' OR json_type(record, '$.claudeInput.text') = 'text')`)
368
- .run(now, cutoff).changes;
369
343
  const removed = this.statement(`DELETE FROM messages WHERE (mailbox, id) IN (
370
344
  SELECT mailbox, id FROM messages m WHERE state = 'cur' AND body_expired_at_ms IS NOT NULL
371
345
  AND NOT EXISTS (SELECT 1 FROM deliveries d WHERE d.mailbox = m.mailbox AND d.message = m.id)
372
346
  AND NOT EXISTS (SELECT 1 FROM subscriptions s WHERE s.mailbox = m.mailbox AND s.message = m.id)
373
347
  ORDER BY created_at_ms DESC, id DESC LIMIT -1 OFFSET ?)`)
374
348
  .run(this.retention.maxReadHeaders).changes;
375
- const result = { at_ms: now, expired_bodies: expired, expired_question_records: questions,
376
- removed_relay_text_records: relayRecords, expired_launch_inputs: launchInputs, removed_headers: removed, policy: this.retention };
349
+ const result = { at_ms: now, removed_relay_text_records: relayRecords, removed_headers: removed, policy: this.retention };
377
350
  this.setMetadata('retention', result);
378
- if (expired || questions || relayRecords || launchInputs || removed) this.changed('retention', result);
351
+ if (relayRecords || removed) this.changed('retention', result);
379
352
  return result;
380
353
  });
381
354
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-teams",
3
- "version": "0.3.197",
3
+ "version": "0.3.198",
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",