@volter/supercode-teams 0.3.192 → 0.3.194

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-teams",
3
- "version": "0.3.192",
3
+ "version": "0.3.194",
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",
@@ -84,7 +84,7 @@
84
84
  "@volter/supercode-frontend": "^0.2.2",
85
85
  "@volter/supercode-harness-sdk": "^0.3.58",
86
86
  "@volter/supercode-health": "^0.1.0",
87
- "@volter/supercode-interchange": "0.1.9",
87
+ "@volter/supercode-interchange": "0.1.10",
88
88
  "@volter/supercode-terminal": "^0.2.54",
89
89
  "@volter/supercode-trace": "^0.1.0",
90
90
  "@volter/teams": "0.2.46",
@@ -3,7 +3,7 @@ import * as zlib from 'node:zlib';
3
3
  import { basename, dirname, join, relative, resolve, sep } from 'node:path';
4
4
  import {
5
5
  HARNESSES, resolveHarnessHomes, selectedHarnesses, FILE_HARNESSES, fileRoot,
6
- normalizedSourcePath, pathUnder, sourceStat, sourceEntries, sameFingerprint,
6
+ normalizedSourcePath, pathUnder, sourceStat, sourceEntries, sameFingerprint, fileFingerprint,
7
7
  listSessionParts, sessionPartFor, readFileMetadata, verifySources,
8
8
  selectStoreFiles, storeRegistryDirectories, storeFingerprints,
9
9
  readForeignStore, readHermesStore, readOrchestratorStore, readGeminiProjects,
@@ -29,7 +29,7 @@ export class SessionIndexOwner extends EventEmitter {
29
29
  constructor({ database, env = process.env, log = () => {} }) {
30
30
  super(); requireSessionIndexRuntime(); this.database = database; this.store = new SessionIndexStore(database);
31
31
  this.env = env; this.log = log; this.sources = new Map(); this.pending = new Map();
32
- this.fileCaches = new Map(); this.projections = new Map();
32
+ this.fileCaches = new Map(); this.projections = new Map(); this.notSessions = new Set();
33
33
  this.metrics = {}; this.closed = false; this.running = null;
34
34
  this.watches = new SessionIndexWatches({
35
35
  changed: (id, event) => this.#event(id, event),
@@ -425,8 +425,9 @@ export class SessionIndexOwner extends EventEmitter {
425
425
 
426
426
  async #file(source, path) {
427
427
  const old = source.parts.get(path);
428
+ let info = null;
428
429
  try {
429
- const info = await sourceStat(path);
430
+ info = await sourceStat(path);
430
431
  if (this.closed) return;
431
432
  // Saved Supercode can retain a metadata row after both content files
432
433
  // disappear. Other formats require their native transcript file.
@@ -459,7 +460,24 @@ export class SessionIndexOwner extends EventEmitter {
459
460
  source.parts.set(path, { path, kind: 'file', state, stamps });
460
461
  this.metrics.changed_rows = (this.metrics.changed_rows ?? 0) + changed;
461
462
  } catch (error) {
462
- if (!this.closed) this.store.fail(source.id, path, 'file', error);
463
+ if (this.closed) return;
464
+ if (error?.code === 'not_a_session' && info) return this.#notSession(source, path, old, info, error);
465
+ this.store.fail(source.id, path, 'file', error);
466
+ }
467
+ }
468
+
469
+ /** A file in a harness's folder that is not one of its sessions (no session header) holds no rows and is no
470
+ * failure: it never makes the index, or the machine, unread. Its fingerprint, taken before the read, keeps it from
471
+ * being read again until it changes; the daemon log names it once. */
472
+ #notSession(source, path, old, info, error) {
473
+ const stamps = [{ path, fingerprint: fileFingerprint(info) }];
474
+ const state = { not_session: true, ...(source.harness === 'gemini' ? { projectsStamp: source.projectsStamp } : {}) };
475
+ this.store.commitPart(source.id, path, { kind: 'file', state, stamps, rows: [], invalidate: !sameStamps(stamps, old?.stamps) });
476
+ this.#apply(source, path, []);
477
+ source.parts.set(path, { path, kind: 'file', state, stamps });
478
+ if (!this.notSessions.has(path)) {
479
+ this.notSessions.add(path); this.metrics.not_sessions = this.notSessions.size;
480
+ this.log(`session index: left out ${path}: ${error.message} (${this.notSessions.size} file${this.notSessions.size === 1 ? '' : 's'} left out)`);
463
481
  }
464
482
  }
465
483
 
package/terminal-mail.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  // Per-recipient terminal mailbox. A retained paste is never repeated: retries
2
- // press Enter and look for the exact user message in the native transcript.
2
+ // press Enter and look for the exact message in the native transcript (a user row, or the queued_command
3
+ // attachment a running Claude Code turn absorbs it as).
3
4
  import { statSync, openSync, readSync, closeSync } from 'node:fs';
4
5
  import { createHash, randomUUID } from 'node:crypto';
5
6
  import { spawnSync } from 'node:child_process';
@@ -52,7 +53,16 @@ function claudeHolds(batch, shown, owned = false) {
52
53
  return batch.placeholder === placeholder[0];
53
54
  }
54
55
 
55
- function confirmed(batch) {
56
+ /** Whether Claude Code recorded `text` as the prompt `wanted`. Claude records a large paste in a native
57
+ * `<pasted_content>` envelope; its whole body is compared, with line-ending and outer-whitespace normalization. */
58
+ function claudeSame(text, wanted) {
59
+ const native = normalized(text), want = normalized(wanted);
60
+ if (native === want) return true;
61
+ const pasted = /^<pasted_content id="([0-9a-f]{4})">\n([\s\S]*)\n<\/pasted_content id="\1">$/u.exec(native);
62
+ return Boolean(pasted) && normalized(pasted[2]) === want;
63
+ }
64
+
65
+ export function confirmed(batch) {
56
66
  let fd;
57
67
  try { fd = openSync(batch.path, 'r'); } catch (error) { if (error.code === 'ENOENT') return false; throw error; }
58
68
  try {
@@ -68,6 +78,12 @@ function confirmed(batch) {
68
78
  while ((end = buffered.indexOf('\n')) >= 0) {
69
79
  const line = buffered.slice(0, end); buffered = buffered.slice(end + 1);
70
80
  let row; try { row = JSON.parse(line); } catch { continue; }
81
+ // A prompt submitted while a turn runs is queued, and reaches the model as a `queued_command` attachment when
82
+ // the turn absorbs it (queue-operation remove, reason absorbed_mid_turn); no `user` row is written for it.
83
+ // That attachment is its delivery. An enqueue alone is not: a queued prompt can still be withdrawn.
84
+ if (batch.kind === 'claude-code' && row.type === 'attachment' && row.attachment?.type === 'queued_command'
85
+ && row.isSidechain !== true && (!batch.sessionId || row.sessionId === batch.sessionId)
86
+ && typeof row.attachment.prompt === 'string' && claudeSame(row.attachment.prompt, batch.text)) return { fragments: 1 };
71
87
  const content = batch.kind === 'codex' && row.type === 'response_item' && row.payload?.type === 'message' && row.payload.role === 'user'
72
88
  ? row.payload.content : batch.kind === 'claude-code' && row.type === 'user' && !row.isMeta && (!batch.sessionId || row.sessionId === batch.sessionId)
73
89
  ? row.message?.content : batch.kind === 'grok' && row.type === 'user' ? row.content : null;
@@ -75,13 +91,7 @@ function confirmed(batch) {
75
91
  if (text === null || text.length === 0) continue;
76
92
  if (text === batch.text) return { fragments: 1 };
77
93
  if (batch.kind === 'claude-code') {
78
- const native = normalized(text), wanted = normalized(batch.text);
79
- if (native === wanted) return { fragments: 1 };
80
- // Claude records a large paste in this native envelope. Compare its
81
- // whole body with matching identifiers, using the existing line-ending
82
- // and outer-whitespace normalization.
83
- const pasted = /^<pasted_content id="([0-9a-f]{4})">\n([\s\S]*)\n<\/pasted_content id="\1">$/u.exec(native);
84
- if (pasted && normalized(pasted[2]) === wanted) return { fragments: 1 };
94
+ if (claudeSame(text, batch.text)) return { fragments: 1 };
85
95
  continue;
86
96
  }
87
97
  // A Windows Codex is pasted its text with line ends as `\n` (codexWindowsPastes).