@volter/supercode-teams 0.3.101 → 0.3.103
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/machine.mjs +5 -4
- package/mail/send.mjs +24 -10
- package/mail/store.mjs +6 -0
- package/native-codex-pane.mjs +22 -3
- package/package.json +1 -1
package/machine.mjs
CHANGED
|
@@ -302,8 +302,8 @@ function instructionsFile({ harness, scope = 'user', cwd } = {}) {
|
|
|
302
302
|
const BOARD_ANSWER_PIECE = 512 * 1024;
|
|
303
303
|
// What an inbox answer carries at most: the channel's frame is 8 MiB (channel.mjs MAX_FRAME), and an answer holds each
|
|
304
304
|
// message three ways (rendered, plain, its row). A mailbox's whole history did not fit (yuerans, 2026-10-06: 6,496 read
|
|
305
|
-
// envelopes, a 43 MB frame refused); the newest that fit are listed, its
|
|
306
|
-
//
|
|
305
|
+
// envelopes, a 43 MB frame refused); the newest that fit are listed, its unread ones always, and a first line says how
|
|
306
|
+
// many older read ones are not.
|
|
307
307
|
const INBOX_ANSWER_BYTES = 4 * 1024 * 1024;
|
|
308
308
|
|
|
309
309
|
/** The indexes of `count` messages kept in an answer, newest first until `sizeOf` passes the bound; `always` kept. */
|
|
@@ -317,7 +317,7 @@ function inboxKept(count, sizeOf, always = () => false) {
|
|
|
317
317
|
}
|
|
318
318
|
return kept.reverse();
|
|
319
319
|
}
|
|
320
|
-
const inboxNote = (left) => `(${left} older message${left === 1 ? ' is' : 's are'} not listed: an answer holds at most ${INBOX_ANSWER_BYTES / 1024 / 1024} MiB. --recent <N> lists the newest N.)`;
|
|
320
|
+
const inboxNote = (left) => `(${left} older read message${left === 1 ? ' is' : 's are'} not listed: an answer holds at most ${INBOX_ANSWER_BYTES / 1024 / 1024} MiB. --recent <N> lists the newest N.)`;
|
|
321
321
|
|
|
322
322
|
|
|
323
323
|
export class TeamsMachine extends MachineCore {
|
|
@@ -3492,7 +3492,8 @@ export class TeamsMachine extends MachineCore {
|
|
|
3492
3492
|
const machine = address.split(':')[1];
|
|
3493
3493
|
if (machine === machineName(this.env)) {
|
|
3494
3494
|
const rows = await this.mail.inboxRows(address, last);
|
|
3495
|
-
|
|
3495
|
+
// an unread message to it is always listed, as its own inbox lists its claimed ones
|
|
3496
|
+
const kept = inboxKept(rows.length, (index) => Buffer.byteLength(JSON.stringify(rows[index])), (index) => rows[index].direction === 'in' && rows[index].state === 'unread');
|
|
3496
3497
|
const left = rows.length - kept.length;
|
|
3497
3498
|
return { code: 0, address, rows: [...(left ? [{ direction: 'note', omitted: left, rendered: inboxNote(left) }] : []), ...kept.map((index) => rows[index])] };
|
|
3498
3499
|
}
|
package/mail/send.mjs
CHANGED
|
@@ -335,6 +335,12 @@ export class MailSend {
|
|
|
335
335
|
throw Object.assign(new Error(relayed.detail), { unanswered: true });
|
|
336
336
|
}
|
|
337
337
|
if (!relayed.delivered && !received) { if (filedHere) { try { unlinkSync(filed); } catch { /* gone */ } } throw new Error(relayed.detail); }
|
|
338
|
+
// relayed: a copy filed unread before this delivery (an earlier unanswered or queued one, held for the receiver's
|
|
339
|
+
// own read) is moved to cur/, so that read never hands it over a second time
|
|
340
|
+
if (relayed.delivered && !received) {
|
|
341
|
+
const now = this.store.findIn(to, handed.id);
|
|
342
|
+
if (now?.state === 'unread') { try { this.store.markRead(to, now.path); } catch { /* moved meanwhile */ } }
|
|
343
|
+
}
|
|
338
344
|
delivered = busy ? 'native_busy' : 'native_idle';
|
|
339
345
|
} else if (door.kind === 'hook') {
|
|
340
346
|
this.store.deliver(to, sent);
|
|
@@ -368,7 +374,8 @@ export class MailSend {
|
|
|
368
374
|
|
|
369
375
|
async #deliverPlanned(caller, sent, plan, { queue, notify_when_idle }) {
|
|
370
376
|
const reached = [], copied = [], failed = [];
|
|
371
|
-
let recorded = false
|
|
377
|
+
let recorded = false;
|
|
378
|
+
const held = []; // recipients whose own copy is held unread with its wake (an unanswered relay)
|
|
372
379
|
if (plan.agent) {
|
|
373
380
|
// the agent's own mailbox keeps the record of what was addressed to it
|
|
374
381
|
try { this.store.deliverRead(plan.agent, sent); recorded = true; } catch (error) { failed.push(`the agent's mailbox (${error.message})`); }
|
|
@@ -383,8 +390,12 @@ export class MailSend {
|
|
|
383
390
|
}
|
|
384
391
|
try { reached.push(`${label} (${await this.#deliverWoken(sent, recipient.address, sessions, queue, notify_when_idle)})`); }
|
|
385
392
|
catch (error) {
|
|
386
|
-
held
|
|
387
|
-
|
|
393
|
+
// held only when its copy is here, unread: its wake hands it over, or (queued, no wake) the receiver's own read
|
|
394
|
+
// does. An unanswered request to another machine's door holds nothing here, so it is a failure, and the agent's
|
|
395
|
+
// own copy waits with its wake (filing there again is filing the same id, which files nothing new)
|
|
396
|
+
const kept = this.store.findIn(recipient.address, sent.id);
|
|
397
|
+
if (error.unanswered && kept?.state === 'unread') held.push(`${label} (unanswered: ${error.message}; ${this.store.hasWake(recipient.address, sent.id) ? 'its wake hands it over' : 'queued: its own mailbox read gives it'})`);
|
|
398
|
+
else failed.push(error.unanswered ? `${label} (unanswered, whether it arrived is unknown: ${error.message})` : `${label} (${error.message})`);
|
|
388
399
|
}
|
|
389
400
|
}
|
|
390
401
|
const thread = plan.thread ? ` Thread ${shortId(plan.thread.id)}.` : '';
|
|
@@ -393,10 +404,11 @@ export class MailSend {
|
|
|
393
404
|
// recipient has it or the wake expires. Not when a recipient's own copy is held unread with its wake (an unanswered
|
|
394
405
|
// relay), which hands it over itself: two wakes would deliver it twice
|
|
395
406
|
let waiting = false;
|
|
396
|
-
if (recorded && (reached.length || copied.length)) {
|
|
397
|
-
// reached: its record is read (one a retry handed
|
|
407
|
+
if (recorded && (reached.length || copied.length || held.length)) {
|
|
408
|
+
// reached, or held by a recipient's own wake (which hands it over, once): its record is read (one a retry handed
|
|
409
|
+
// over was unread), so its own wake, if any, is settled and never hands it over beside the recipient's
|
|
398
410
|
try { const record = this.store.findIn(plan.agent, sent.id); if (record?.state === 'unread') this.store.markRead(plan.agent, record.path); } catch { /* kept unread */ }
|
|
399
|
-
} else if (recorded
|
|
411
|
+
} else if (recorded) {
|
|
400
412
|
try {
|
|
401
413
|
const record = this.store.findIn(plan.agent, sent.id);
|
|
402
414
|
if (record?.state === 'read') this.store.markUnread(plan.agent, record.path);
|
|
@@ -405,12 +417,14 @@ export class MailSend {
|
|
|
405
417
|
} catch (error) { failed.push(`its retry (${error.message})`); }
|
|
406
418
|
}
|
|
407
419
|
let text;
|
|
420
|
+
// a held copy is not said not sent: it is handed over by its wake, so a sender that sent it again would duplicate it
|
|
421
|
+
const heldText = held.length ? ` Held, unanswered, unread in ${held.length === 1 ? 'its receiver\'s' : 'their receivers\''} mailbox, which gives it to the receiver once (don't send it again): ${held.join('; ')}.` : '';
|
|
408
422
|
if (waiting) text = `not sent yet: nobody in its thread could be reached, so it waits unread in ${plan.agent}'s mailbox and is handed over again when one can be. Message id ${shortId(sent.id)}.${thread} Don't poll; carry on.`;
|
|
409
|
-
else if (!reached.length && !copied.length) text = `Not sent: nobody in its thread could be reached.${thread}`;
|
|
410
|
-
else text = `sent to ${reached.length ? reached.join(', ') : 'no one woken'}.${copied.length ? ` CC (filed, not woken): ${copied.join(', ')}.` : ''} Message id ${shortId(sent.id)}.${thread} Don't poll; carry on.`;
|
|
423
|
+
else if (!reached.length && !copied.length && !held.length) text = `Not sent: nobody in its thread could be reached.${thread}`;
|
|
424
|
+
else text = `sent to ${reached.length ? reached.join(', ') : 'no one woken yet'}.${copied.length ? ` CC (filed, not woken): ${copied.join(', ')}.` : ''}${heldText} Message id ${shortId(sent.id)}.${thread} Don't poll; carry on.`;
|
|
411
425
|
if (failed.length) text += ` Not delivered to: ${failed.join('; ')}.`;
|
|
412
|
-
const code = reached.length || copied.length || waiting ? 0 : EXIT_FAILED;
|
|
413
|
-
return { ...outcome(code, text, code === 0 ? { delivered: true, message_ids: [sent.id], receipt: 'agent-mailbox', ...(waiting ? { waiting } : {}) } : null), waiting };
|
|
426
|
+
const code = reached.length || copied.length || held.length || waiting ? 0 : EXIT_FAILED;
|
|
427
|
+
return { ...outcome(code, text, code === 0 ? { delivered: true, message_ids: [sent.id], receipt: 'agent-mailbox', ...(waiting ? { waiting } : {}), ...(held.length && !reached.length && !copied.length ? { unanswered: true } : {}) } : null), waiting };
|
|
414
428
|
}
|
|
415
429
|
|
|
416
430
|
/** `sent` filed unread in `address`'s mailbox on this machine's mail, without waking it (mail_agent.rs `file_unread`):
|
package/mail/store.mjs
CHANGED
|
@@ -413,6 +413,12 @@ export class MailStore extends EventEmitter {
|
|
|
413
413
|
catch (error) { if (!keep || error.code !== 'EEXIST') throw error; }
|
|
414
414
|
}
|
|
415
415
|
|
|
416
|
+
/** Whether a wake is on file for `id` in `address`'s mailbox. */
|
|
417
|
+
hasWake(address, id) {
|
|
418
|
+
if (!/^[A-Za-z0-9_-]+$/.test(id)) return false;
|
|
419
|
+
return existsSync(join(this.open(address).dir, 'wake', id));
|
|
420
|
+
}
|
|
421
|
+
|
|
416
422
|
/** A wake's delivery so far (mailbox.rs `wake_state`): an empty or absent file is a fresh wake. */
|
|
417
423
|
wakeState(address, id) {
|
|
418
424
|
const box = this.open(address);
|
package/native-codex-pane.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Supercode hosts neither the agent nor its run loop; it can attach to native
|
|
3
3
|
// question requests while the stock TUI remains the session's UI.
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
|
-
import { readFileSync, writeFileSync, renameSync, mkdtempSync, existsSync, rmSync } from 'node:fs';
|
|
5
|
+
import { readFileSync, writeFileSync, renameSync, mkdtempSync, existsSync, rmSync, appendFileSync } from 'node:fs';
|
|
6
6
|
import { tmpdir } from 'node:os';
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
|
|
@@ -42,7 +42,23 @@ for (let index = 0; index < seed.arguments.length; index += 1) {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
if(writableRoots.length)config.push('-c',`sandbox_workspace_write.writable_roots=${JSON.stringify(writableRoots)}`);
|
|
45
|
-
|
|
45
|
+
// The app-server's stderr is never the pane's: the pane is the TUI's screen, and a record the server writes there lands
|
|
46
|
+
// on the TUI's composer row, which the mail wake then reads as a person's draft and never types over (yuerans,
|
|
47
|
+
// 2026-10-06: `ERROR codex_models_manager::manager: failed to refresh available models` held every wake of the box
|
|
48
|
+
// maintainer off for three hours). It is kept beside the pane's binding instead, at most LOG_BYTES of it.
|
|
49
|
+
const LOG_BYTES = 8 * 1024 * 1024;
|
|
50
|
+
const serverLog = `${path.replace(/\.json$/, '')}.app-server.log`;
|
|
51
|
+
let logged = 0;
|
|
52
|
+
const server = spawn(seed.program, [...config, 'app-server', '--listen', endpoint], { stdio: ['ignore', 'ignore', 'pipe'] });
|
|
53
|
+
server.stderr.on('data', (chunk) => {
|
|
54
|
+
if (logged >= LOG_BYTES) return;
|
|
55
|
+
const kept = chunk.subarray(0, LOG_BYTES - logged);
|
|
56
|
+
logged += kept.length;
|
|
57
|
+
try {
|
|
58
|
+
appendFileSync(serverLog, kept, { mode: 0o600 });
|
|
59
|
+
if (logged >= LOG_BYTES) appendFileSync(serverLog, `\nsupercode: the app-server's stderr past ${LOG_BYTES} bytes is not kept\n`);
|
|
60
|
+
} catch { /* its log is best kept; the pane never gets it */ }
|
|
61
|
+
});
|
|
46
62
|
let tui;
|
|
47
63
|
let ended = false;
|
|
48
64
|
let serverError;
|
|
@@ -67,7 +83,10 @@ try {
|
|
|
67
83
|
tui = spawn(seed.program, args, { stdio: 'inherit' });
|
|
68
84
|
process.exitCode = await new Promise((resolve, reject) => { tui.once('exit', (code) => resolve(code ?? 1)); tui.once('error', reject); });
|
|
69
85
|
} catch (error) {
|
|
70
|
-
|
|
86
|
+
// a server that failed says why in its log, which the pane shows only now, as the pane's own failure
|
|
87
|
+
let why = '';
|
|
88
|
+
try { why = readFileSync(serverLog, 'utf8').slice(-2000).trim(); } catch { /* it wrote nothing */ }
|
|
89
|
+
process.stderr.write(`supercode: ${error.message}\n${why ? `its app-server's stderr (${serverLog}):\n${why}\n` : ''}`);
|
|
71
90
|
process.exitCode = 1;
|
|
72
91
|
} finally {
|
|
73
92
|
clearInterval(guard);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-teams",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.103",
|
|
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",
|