@cluesmith/codev 3.3.1 → 3.3.2
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/dashboard-dist/assets/index-D3XaKpZW.js.map +1 -1
- package/dist/agent-farm/cli.d.ts.map +1 -1
- package/dist/agent-farm/cli.js +52 -1
- package/dist/agent-farm/cli.js.map +1 -1
- package/dist/agent-farm/commands/reset/constants.d.ts +118 -0
- package/dist/agent-farm/commands/reset/constants.d.ts.map +1 -1
- package/dist/agent-farm/commands/reset/constants.js +118 -0
- package/dist/agent-farm/commands/reset/constants.js.map +1 -1
- package/dist/agent-farm/commands/reset/context.d.ts +18 -0
- package/dist/agent-farm/commands/reset/context.d.ts.map +1 -1
- package/dist/agent-farm/commands/reset/context.js +41 -0
- package/dist/agent-farm/commands/reset/context.js.map +1 -1
- package/dist/agent-farm/commands/reset/receipt.d.ts.map +1 -1
- package/dist/agent-farm/commands/reset/receipt.js +16 -5
- package/dist/agent-farm/commands/reset/receipt.js.map +1 -1
- package/dist/agent-farm/commands/reset/reorient.d.ts +1 -1
- package/dist/agent-farm/commands/reset/reorient.d.ts.map +1 -1
- package/dist/agent-farm/commands/reset/reorient.js +8 -1
- package/dist/agent-farm/commands/reset/reorient.js.map +1 -1
- package/dist/agent-farm/commands/reset/self.d.ts +335 -0
- package/dist/agent-farm/commands/reset/self.d.ts.map +1 -0
- package/dist/agent-farm/commands/reset/self.js +636 -0
- package/dist/agent-farm/commands/reset/self.js.map +1 -0
- package/dist/agent-farm/commands/reset.d.ts.map +1 -1
- package/dist/agent-farm/commands/reset.js +23 -18
- package/dist/agent-farm/commands/reset.js.map +1 -1
- package/dist/agent-farm/commands/self-refresh.d.ts +60 -0
- package/dist/agent-farm/commands/self-refresh.d.ts.map +1 -0
- package/dist/agent-farm/commands/self-refresh.js +506 -0
- package/dist/agent-farm/commands/self-refresh.js.map +1 -0
- package/dist/agent-farm/commands/send.d.ts.map +1 -1
- package/dist/agent-farm/commands/send.js +27 -2
- package/dist/agent-farm/commands/send.js.map +1 -1
- package/dist/agent-farm/commands/spawn.d.ts +7 -0
- package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn.js +13 -1
- package/dist/agent-farm/commands/spawn.js.map +1 -1
- package/dist/agent-farm/lib/tower-client.d.ts +12 -0
- package/dist/agent-farm/lib/tower-client.d.ts.map +1 -1
- package/dist/agent-farm/lib/tower-client.js +12 -0
- package/dist/agent-farm/lib/tower-client.js.map +1 -1
- package/dist/agent-farm/servers/mailbox-delivery.d.ts +141 -13
- package/dist/agent-farm/servers/mailbox-delivery.d.ts.map +1 -1
- package/dist/agent-farm/servers/mailbox-delivery.js +172 -16
- package/dist/agent-farm/servers/mailbox-delivery.js.map +1 -1
- package/dist/agent-farm/servers/mailbox-wiring.d.ts +23 -1
- package/dist/agent-farm/servers/mailbox-wiring.d.ts.map +1 -1
- package/dist/agent-farm/servers/mailbox-wiring.js +91 -31
- package/dist/agent-farm/servers/mailbox-wiring.js.map +1 -1
- package/dist/agent-farm/servers/message-write.d.ts +58 -18
- package/dist/agent-farm/servers/message-write.d.ts.map +1 -1
- package/dist/agent-farm/servers/message-write.js +73 -28
- package/dist/agent-farm/servers/message-write.js.map +1 -1
- package/dist/agent-farm/servers/render-gate.d.ts +13 -0
- package/dist/agent-farm/servers/render-gate.d.ts.map +1 -1
- package/dist/agent-farm/servers/render-gate.js +21 -0
- package/dist/agent-farm/servers/render-gate.js.map +1 -1
- package/dist/agent-farm/servers/session-submit.d.ts +220 -27
- package/dist/agent-farm/servers/session-submit.d.ts.map +1 -1
- package/dist/agent-farm/servers/session-submit.js +333 -42
- package/dist/agent-farm/servers/session-submit.js.map +1 -1
- package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-routes.js +114 -25
- package/dist/agent-farm/servers/tower-routes.js.map +1 -1
- package/dist/agent-farm/servers/tower-tunnel.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-tunnel.js +14 -0
- package/dist/agent-farm/servers/tower-tunnel.js.map +1 -1
- package/dist/agent-farm/types.d.ts +36 -1
- package/dist/agent-farm/types.d.ts.map +1 -1
- package/dist/agent-farm/utils/message-format.d.ts +60 -4
- package/dist/agent-farm/utils/message-format.d.ts.map +1 -1
- package/dist/agent-farm/utils/message-format.js +115 -22
- package/dist/agent-farm/utils/message-format.js.map +1 -1
- package/dist/agent-farm/utils/server-utils.d.ts +8 -2
- package/dist/agent-farm/utils/server-utils.d.ts.map +1 -1
- package/dist/agent-farm/utils/server-utils.js +19 -2
- package/dist/agent-farm/utils/server-utils.js.map +1 -1
- package/dist/commands/porch/context-refresh.d.ts +181 -0
- package/dist/commands/porch/context-refresh.d.ts.map +1 -0
- package/dist/commands/porch/context-refresh.js +270 -0
- package/dist/commands/porch/context-refresh.js.map +1 -0
- package/dist/commands/porch/index.d.ts +6 -0
- package/dist/commands/porch/index.d.ts.map +1 -1
- package/dist/commands/porch/index.js +46 -0
- package/dist/commands/porch/index.js.map +1 -1
- package/dist/commands/porch/next.d.ts +0 -9
- package/dist/commands/porch/next.d.ts.map +1 -1
- package/dist/commands/porch/next.js +133 -4
- package/dist/commands/porch/next.js.map +1 -1
- package/dist/commands/porch/protocol.d.ts.map +1 -1
- package/dist/commands/porch/protocol.js +102 -0
- package/dist/commands/porch/protocol.js.map +1 -1
- package/dist/commands/porch/types.d.ts +61 -0
- package/dist/commands/porch/types.d.ts.map +1 -1
- package/dist/lib/self-refresh-invocation.d.ts +60 -0
- package/dist/lib/self-refresh-invocation.d.ts.map +1 -0
- package/dist/lib/self-refresh-invocation.js +64 -0
- package/dist/lib/self-refresh-invocation.js.map +1 -0
- package/dist/lib/test-env.d.ts +50 -0
- package/dist/lib/test-env.d.ts.map +1 -1
- package/dist/lib/test-env.js +76 -0
- package/dist/lib/test-env.js.map +1 -1
- package/package.json +4 -4
- package/skeleton/.claude/skills/arch-save/SKILL.md +8 -2
- package/skeleton/.claude/skills/builder-refresh/SKILL.md +138 -0
- package/skeleton/.codex/skills/arch-save/SKILL.md +8 -2
- package/skeleton/.codex/skills/builder-refresh/SKILL.md +138 -0
- package/skeleton/protocol-schema.json +17 -0
- package/skeleton/protocols/air/protocol.json +1 -1
- package/skeleton/protocols/aspir/protocol.json +5 -1
- package/skeleton/protocols/bugfix/protocol.json +1 -1
- package/skeleton/protocols/experiment/protocol.json +1 -1
- package/skeleton/protocols/maintain/protocol.json +1 -1
- package/skeleton/protocols/pir/protocol.json +1 -1
- package/skeleton/protocols/protocol-schema.json +17 -0
- package/skeleton/protocols/research/protocol.json +1 -1
- package/skeleton/protocols/spike/protocol.json +1 -1
- package/skeleton/protocols/spir/protocol.json +5 -1
|
@@ -35,16 +35,17 @@ import { fetchTeamGitHubData } from '../../lib/team-github.js';
|
|
|
35
35
|
import { resolveTarget, resolveAgentInRegistry, broadcastMessage, isResolveError } from './tower-messages.js';
|
|
36
36
|
import { handleCommandRoute, COMMAND_ROUTE } from './command-relay.js';
|
|
37
37
|
import { handleCanvasRoute, CANVAS_ROUTE_PREFIX } from './canvas-relay.js';
|
|
38
|
-
import { formatArchitectMessage, formatBuilderMessage, formatUserViaVsCodeMessage } from '../utils/message-format.js';
|
|
38
|
+
import { formatArchitectMessage, formatArchitectToBuilderMessage, formatBuilderMessage, formatUserViaVsCodeMessage, messageTooLargeError, MAX_MESSAGE_BYTES, } from '../utils/message-format.js';
|
|
39
39
|
import { writeMessageToSession, writeEscapeToSession } from './message-write.js';
|
|
40
40
|
import { makeDeliveryPorts, getMailboxDrainer } from './mailbox-wiring.js';
|
|
41
41
|
import { deliverAgentMailSerialized } from './mailbox-delivery.js';
|
|
42
42
|
import { deliverCronMail, CRON_SENDER } from './cron-delivery.js';
|
|
43
43
|
import { enqueue as enqueueMailbox, getById as getMailboxById, markDelivered as markMailboxDelivered, listHeld as listHeldMailbox, dismiss as dismissMailbox, } from '../db/mailbox.js';
|
|
44
|
-
// Spec 1273 per-terminal submission lock
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
|
|
44
|
+
// Spec 1273 per-terminal submission lock. Originally the anti-fusion lock for the two
|
|
45
|
+
// explicit human-bypass paths (escape + interrupt); since Issue #1365 the gated mailbox
|
|
46
|
+
// delivery path takes it too, so it now serializes the whole write edge of a terminal.
|
|
47
|
+
// The operator paths pass a wait ceiling — see OPERATOR_SUBMIT_WAIT_CEILING_MS.
|
|
48
|
+
import { submitToSession, OPERATOR_SUBMIT_WAIT_CEILING_MS } from './session-submit.js';
|
|
48
49
|
// Spec 1307 `--delay` — Tower-side deferred delivery, re-homed onto the Spec 1313
|
|
49
50
|
// mailbox (the merge that carried this feature was flattened by a later rebase, so it
|
|
50
51
|
// is grafted here explicitly): the due-time callback enqueues to the mailbox and
|
|
@@ -1274,17 +1275,24 @@ function liveTargetIdentity(result) {
|
|
|
1274
1275
|
const archName = architectNameForTerminal(result.workspacePath, result.terminalId);
|
|
1275
1276
|
return { toAgent: archName ?? result.agent, isArchitectTarget: archName !== null };
|
|
1276
1277
|
}
|
|
1277
|
-
/**
|
|
1278
|
-
|
|
1278
|
+
/**
|
|
1279
|
+
* Format a message per sender/target — preserves the pre-1313 formatting rules.
|
|
1280
|
+
*
|
|
1281
|
+
* #1574: `toAgent` is the CANONICAL recipient the mailbox row is keyed on (the
|
|
1282
|
+
* architect reverse-map for architect targets, the resolved agent id otherwise),
|
|
1283
|
+
* so the header a recipient reads names the same identity the row was addressed
|
|
1284
|
+
* to. Passing anything else here would make the frame attest to a fiction.
|
|
1285
|
+
*/
|
|
1286
|
+
function formatMessageForTarget(isArchitectTarget, from, toAgent, message, raw) {
|
|
1279
1287
|
// #1494: a human's approval relayed by the VS Code extension gets its own
|
|
1280
1288
|
// header, so the architect can tell it from a peer-architect instruction.
|
|
1281
1289
|
if (isArchitectTarget && from === VSCODE_USER_SENDER)
|
|
1282
|
-
return formatUserViaVsCodeMessage(message, undefined, raw);
|
|
1290
|
+
return formatUserViaVsCodeMessage(toAgent, message, undefined, raw);
|
|
1283
1291
|
if (isArchitectTarget && from)
|
|
1284
|
-
return formatBuilderMessage(from, message, undefined, raw); // builder → architect
|
|
1292
|
+
return formatBuilderMessage(from, toAgent, message, undefined, raw); // builder → architect
|
|
1285
1293
|
if (!isArchitectTarget)
|
|
1286
|
-
return
|
|
1287
|
-
return raw ? message : formatArchitectMessage(message, undefined, false); // unknown → architect
|
|
1294
|
+
return formatArchitectToBuilderMessage(toAgent, message, undefined, raw); // any → builder
|
|
1295
|
+
return raw ? message : formatArchitectMessage(toAgent, message, undefined, false); // unknown → architect
|
|
1288
1296
|
}
|
|
1289
1297
|
/**
|
|
1290
1298
|
* Route a cron notification through the Spec 1313 mailbox + gate — the Tower-wired
|
|
@@ -1304,9 +1312,12 @@ export async function deliverCronMessage(task, message, log) {
|
|
|
1304
1312
|
const ports = makeDeliveryPorts(log);
|
|
1305
1313
|
// Preserve the pre-1313 cron framing: a message FROM the `af-cron` pseudo-builder,
|
|
1306
1314
|
// regardless of whether the target is an architect or a builder.
|
|
1315
|
+
//
|
|
1316
|
+
// #1574: the frame names its recipient, which is only known AFTER resolution —
|
|
1317
|
+
// so it is built per-branch rather than once up front. `base` carries everything
|
|
1318
|
+
// that does not depend on the target.
|
|
1307
1319
|
const base = {
|
|
1308
1320
|
body: message,
|
|
1309
|
-
formattedMessage: formatBuilderMessage(CRON_SENDER, message),
|
|
1310
1321
|
supersedeKey: task.name,
|
|
1311
1322
|
};
|
|
1312
1323
|
const live = resolveTarget(task.target, task.workspacePath);
|
|
@@ -1314,6 +1325,7 @@ export async function deliverCronMessage(task, message, log) {
|
|
|
1314
1325
|
const { toAgent } = liveTargetIdentity(live);
|
|
1315
1326
|
return deliverCronMail(ports, db, {
|
|
1316
1327
|
...base,
|
|
1328
|
+
formattedMessage: formatBuilderMessage(CRON_SENDER, toAgent, message),
|
|
1317
1329
|
workspacePath: live.workspacePath,
|
|
1318
1330
|
toAgent,
|
|
1319
1331
|
terminalId: live.terminalId,
|
|
@@ -1327,6 +1339,7 @@ export async function deliverCronMessage(task, message, log) {
|
|
|
1327
1339
|
if (!isResolveError(reg)) {
|
|
1328
1340
|
return deliverCronMail(ports, db, {
|
|
1329
1341
|
...base,
|
|
1342
|
+
formattedMessage: formatBuilderMessage(CRON_SENDER, reg.agent, message),
|
|
1330
1343
|
workspacePath: reg.workspacePath,
|
|
1331
1344
|
toAgent: reg.agent,
|
|
1332
1345
|
terminalId: null,
|
|
@@ -1356,8 +1369,26 @@ function holdAndRespond(res, ctx, input, reason) {
|
|
|
1356
1369
|
held: true,
|
|
1357
1370
|
reason,
|
|
1358
1371
|
mailboxId: row.id,
|
|
1372
|
+
bodyLength: Buffer.byteLength(input.body, 'utf8'),
|
|
1359
1373
|
});
|
|
1360
1374
|
}
|
|
1375
|
+
/** Machine-readable reason paired with `degraded: true` on an operator send response. */
|
|
1376
|
+
const DEGRADED_SUBMIT_REASON = 'submit-wait-ceiling-expired';
|
|
1377
|
+
/**
|
|
1378
|
+
* Announce that an operator submission gave up waiting for the per-terminal lock and wrote
|
|
1379
|
+
* unserialized (Issue #1365).
|
|
1380
|
+
*
|
|
1381
|
+
* This is the degraded path, and it is deliberately loud. The ceiling arms only against a
|
|
1382
|
+
* DELIVERY write (behind another operator the wait stays unbounded, as before #1365), so what
|
|
1383
|
+
* expiry falls back to is exactly the pre-#1365 operator-vs-delivery behaviour: two disjoint
|
|
1384
|
+
* locks, no serialization. That one pair is therefore no worse than the old status quo — but
|
|
1385
|
+
* it used to be invisible, and now it is not, here and in the `degraded` flag on the response.
|
|
1386
|
+
*/
|
|
1387
|
+
function logCeilingExpired(ctx, action, toAgent, terminalId, waitedMs) {
|
|
1388
|
+
ctx.log('WARN', `${action} → ${toAgent} (terminal ${terminalId.slice(0, 8)}...) waited ${waitedMs}ms for an in-flight ` +
|
|
1389
|
+
`write and proceeded UNSERIALIZED — it may interleave with that write. A message long enough to ` +
|
|
1390
|
+
`hold the line this long is the usual cause.`);
|
|
1391
|
+
}
|
|
1361
1392
|
/**
|
|
1362
1393
|
* Handle a delayed (`--delay`) send (Spec 1307, re-homed onto the Spec 1313 mailbox; round 3
|
|
1363
1394
|
* durable rework). The row is RESOLVED, authorized (the builder-spoofing check inside
|
|
@@ -1420,7 +1451,7 @@ function handleDelayedSend(res, ctx, db, params) {
|
|
|
1420
1451
|
sendJson(res, statusCode, { error: errorCode, message: live.message });
|
|
1421
1452
|
return;
|
|
1422
1453
|
}
|
|
1423
|
-
const formattedMessage = formatMessageForTarget(isArchitectTarget, from, message, raw);
|
|
1454
|
+
const formattedMessage = formatMessageForTarget(isArchitectTarget, from, toAgent, message, raw);
|
|
1424
1455
|
// Persist NOW, at request time, with the due time. reason=null → SCHEDULED, not stuck.
|
|
1425
1456
|
const row = enqueueMailbox(db, {
|
|
1426
1457
|
workspacePath,
|
|
@@ -1462,6 +1493,17 @@ function handleDelayedSend(res, ctx, db, params) {
|
|
|
1462
1493
|
live.write('\x03'); // Ctrl+C only — end the turn; body follows via the gate
|
|
1463
1494
|
fired = true;
|
|
1464
1495
|
return 0; // no body, no Enter on this path
|
|
1496
|
+
}, undefined,
|
|
1497
|
+
// The same operator ceiling as the immediate paths (Issue #1365). This one fires
|
|
1498
|
+
// UNATTENDED, so it is the writer most likely to meet a gated delivery mid-pace —
|
|
1499
|
+
// which is precisely why the delayed `^C` must take this lock at all.
|
|
1500
|
+
{
|
|
1501
|
+
waitCeilingMs: OPERATOR_SUBMIT_WAIT_CEILING_MS,
|
|
1502
|
+
onCeilingExpired: (waitedMs) => logCeilingExpired(ctx, 'delayed interrupt ^C', toAgent, terminalId, waitedMs),
|
|
1503
|
+
// This is the one operator path whose write can be a NO-OP (the liveness re-check
|
|
1504
|
+
// above). A degraded no-op raced nobody, so it must not bump the bypass counter —
|
|
1505
|
+
// that would make a concurrent delivery hold and re-deliver for nothing.
|
|
1506
|
+
wroteBytes: () => fired,
|
|
1465
1507
|
})
|
|
1466
1508
|
.then(() => ctx.log('INFO', fired
|
|
1467
1509
|
? `Delayed interrupt ^C fired → ${toAgent} (terminal ${terminalId.slice(0, 8)}...); body delivers via the gate`
|
|
@@ -1488,6 +1530,7 @@ function handleDelayedSend(res, ctx, db, params) {
|
|
|
1488
1530
|
deliverAfter,
|
|
1489
1531
|
mailboxId: row.id,
|
|
1490
1532
|
notBefore,
|
|
1533
|
+
bodyLength: Buffer.byteLength(params.message, 'utf8'),
|
|
1491
1534
|
});
|
|
1492
1535
|
}
|
|
1493
1536
|
async function handleSend(req, res, ctx) {
|
|
@@ -1505,6 +1548,17 @@ async function handleSend(req, res, ctx) {
|
|
|
1505
1548
|
res.end(JSON.stringify({ error: 'INVALID_PARAMS', message: 'Missing or empty "message" field' }));
|
|
1506
1549
|
return;
|
|
1507
1550
|
}
|
|
1551
|
+
// Issue #1573: refuse an over-large body LOUDLY, here at the route rather than only at the
|
|
1552
|
+
// CLI — `/api/send` is a public local route, and the CLI is not its only caller. Nothing
|
|
1553
|
+
// downstream bounded message size, so an oversized body used to be paced onto the line one
|
|
1554
|
+
// line at a time and reported delivered whatever the composer made of it. Rejecting is the
|
|
1555
|
+
// only honest option: truncating would reproduce #1564 on purpose.
|
|
1556
|
+
const bodyLength = Buffer.byteLength(message, 'utf8');
|
|
1557
|
+
if (bodyLength > MAX_MESSAGE_BYTES) {
|
|
1558
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1559
|
+
res.end(JSON.stringify({ error: 'MESSAGE_TOO_LARGE', message: messageTooLargeError(bodyLength) }));
|
|
1560
|
+
return;
|
|
1561
|
+
}
|
|
1508
1562
|
// Optional fields
|
|
1509
1563
|
const from = typeof body.from === 'string' ? body.from : undefined;
|
|
1510
1564
|
const workspace = typeof body.workspace === 'string' ? body.workspace : undefined;
|
|
@@ -1571,7 +1625,7 @@ async function handleSend(req, res, ctx) {
|
|
|
1571
1625
|
workspacePath: reg.workspacePath,
|
|
1572
1626
|
toAgent: reg.agent,
|
|
1573
1627
|
body: message,
|
|
1574
|
-
formattedMessage: formatMessageForTarget(reg.kind === 'architect', from, message, raw),
|
|
1628
|
+
formattedMessage: formatMessageForTarget(reg.kind === 'architect', from, reg.agent, message, raw),
|
|
1575
1629
|
fromAgent: from ?? null,
|
|
1576
1630
|
fromWorkspace: senderWorkspace,
|
|
1577
1631
|
noEnter,
|
|
@@ -1609,7 +1663,7 @@ async function handleSend(req, res, ctx) {
|
|
|
1609
1663
|
workspacePath: result.workspacePath,
|
|
1610
1664
|
toAgent,
|
|
1611
1665
|
body: message,
|
|
1612
|
-
formattedMessage: formatMessageForTarget(isArchitectTarget, from, message, raw),
|
|
1666
|
+
formattedMessage: formatMessageForTarget(isArchitectTarget, from, toAgent, message, raw),
|
|
1613
1667
|
fromAgent: from ?? null,
|
|
1614
1668
|
fromWorkspace: senderWorkspace,
|
|
1615
1669
|
noEnter,
|
|
@@ -1634,7 +1688,7 @@ async function handleSend(req, res, ctx) {
|
|
|
1634
1688
|
workspacePath: result.workspacePath,
|
|
1635
1689
|
toAgent,
|
|
1636
1690
|
body: message,
|
|
1637
|
-
formattedMessage: formatMessageForTarget(isArchitectTarget, from, message, raw),
|
|
1691
|
+
formattedMessage: formatMessageForTarget(isArchitectTarget, from, toAgent, message, raw),
|
|
1638
1692
|
fromAgent: from ?? null,
|
|
1639
1693
|
fromWorkspace: senderWorkspace,
|
|
1640
1694
|
noEnter,
|
|
@@ -1650,7 +1704,17 @@ async function handleSend(req, res, ctx) {
|
|
|
1650
1704
|
if (escape) {
|
|
1651
1705
|
// Awaited: the response must not claim delivery before the ESC and its
|
|
1652
1706
|
// Enter have actually been written (Spec 1273 verify).
|
|
1653
|
-
|
|
1707
|
+
// Issue #1365: escape is an operator submission — it blocks behind an in-flight write
|
|
1708
|
+
// (including a gated mailbox delivery, which now takes this same lock) so its ESC and
|
|
1709
|
+
// Enter can no longer truncate a delivery mid-pace, but only up to the wait ceiling.
|
|
1710
|
+
let escapeDegraded = false;
|
|
1711
|
+
await submitToSession(result.terminalId, () => writeEscapeToSession(session, noEnter), undefined, {
|
|
1712
|
+
waitCeilingMs: OPERATOR_SUBMIT_WAIT_CEILING_MS,
|
|
1713
|
+
onCeilingExpired: (waitedMs) => {
|
|
1714
|
+
escapeDegraded = true;
|
|
1715
|
+
logCeilingExpired(ctx, 'ESC', toAgent, result.terminalId, waitedMs);
|
|
1716
|
+
},
|
|
1717
|
+
});
|
|
1654
1718
|
broadcastMessage({
|
|
1655
1719
|
type: 'message',
|
|
1656
1720
|
from: { project: path.basename(senderWorkspace), agent: from ?? 'unknown' },
|
|
@@ -1660,10 +1724,16 @@ async function handleSend(req, res, ctx) {
|
|
|
1660
1724
|
timestamp: new Date().toISOString(),
|
|
1661
1725
|
});
|
|
1662
1726
|
ctx.log('INFO', `Interrupt (ESC) sent: ${from ?? 'unknown'} → ${toAgent} (terminal ${result.terminalId.slice(0, 8)}...)`);
|
|
1663
|
-
sendJson(res, 200, {
|
|
1727
|
+
sendJson(res, 200, {
|
|
1728
|
+
ok: true,
|
|
1729
|
+
terminalId: result.terminalId,
|
|
1730
|
+
resolvedTo: toAgent,
|
|
1731
|
+
deferred: false,
|
|
1732
|
+
...(escapeDegraded ? { degraded: true, degradedReason: DEGRADED_SUBMIT_REASON } : {}),
|
|
1733
|
+
});
|
|
1664
1734
|
return;
|
|
1665
1735
|
}
|
|
1666
|
-
const formattedMessage = formatMessageForTarget(isArchitectTarget, from, message, raw);
|
|
1736
|
+
const formattedMessage = formatMessageForTarget(isArchitectTarget, from, toAgent, message, raw);
|
|
1667
1737
|
// NB: `--delay` (deliverAfter) is handled earlier by handleDelayedSend, before this
|
|
1668
1738
|
// immediate-path block — a delayed send is resolved + persisted at request time and does
|
|
1669
1739
|
// not fall through here.
|
|
@@ -1701,15 +1771,29 @@ async function handleSend(req, res, ctx) {
|
|
|
1701
1771
|
// during the 100 ms gap. `writeMessageToSession(..., 100)` schedules the text 100 ms after
|
|
1702
1772
|
// the ^C (the settle) and returns the completion offset, so the lock is held until the
|
|
1703
1773
|
// whole interrupt is on the wire; uncontended, it runs at once.
|
|
1704
|
-
// Scope of the guarantee
|
|
1705
|
-
//
|
|
1706
|
-
// concurrent
|
|
1707
|
-
//
|
|
1708
|
-
//
|
|
1709
|
-
//
|
|
1774
|
+
// Scope of the guarantee (Issue #1365 — this used to end at "interrupt vs interrupt/escape"):
|
|
1775
|
+
// the gated mailbox delivery path now takes this SAME per-terminal lock, so an interrupt is
|
|
1776
|
+
// serialized against a concurrent delivery too. That closes the case where a `^C` landed
|
|
1777
|
+
// inside a delivery's own text→Enter window, clearing the composer while every byte still
|
|
1778
|
+
// reported success — the row read `delivered` for a message the agent never saw. The lock
|
|
1779
|
+
// order is per-agent → per-terminal (the delivery takes this one as a leaf), never the
|
|
1780
|
+
// reverse, so there is no cycle. See session-submit.ts for the full boundary.
|
|
1781
|
+
// Whether this interrupt gave up waiting and wrote unserialized. It must reach the SENDER,
|
|
1782
|
+
// not just the Tower log (codex review of PR #1492): the row above was claimed `delivered`
|
|
1783
|
+
// before the write, so a degraded interrupt would otherwise report an unqualified success
|
|
1784
|
+
// for a body that may have interleaved with the delivery it skipped. We keep the
|
|
1785
|
+
// claim-first tradeoff (un-claiming risks a double delivery — see above) and tell the
|
|
1786
|
+
// truth about it instead.
|
|
1787
|
+
let degraded = false;
|
|
1710
1788
|
await submitToSession(result.terminalId, () => {
|
|
1711
1789
|
session.write('\x03'); // Ctrl+C
|
|
1712
1790
|
return writeMessageToSession(session, formattedMessage, noEnter, 100);
|
|
1791
|
+
}, undefined, {
|
|
1792
|
+
waitCeilingMs: OPERATOR_SUBMIT_WAIT_CEILING_MS,
|
|
1793
|
+
onCeilingExpired: (waitedMs) => {
|
|
1794
|
+
degraded = true;
|
|
1795
|
+
logCeilingExpired(ctx, 'interrupt', toAgent, result.terminalId, waitedMs);
|
|
1796
|
+
},
|
|
1713
1797
|
});
|
|
1714
1798
|
broadcastMessage({
|
|
1715
1799
|
type: 'message',
|
|
@@ -1729,6 +1813,9 @@ async function handleSend(req, res, ctx) {
|
|
|
1729
1813
|
held: false,
|
|
1730
1814
|
mailboxId: row.id,
|
|
1731
1815
|
reason: null,
|
|
1816
|
+
bodyLength,
|
|
1817
|
+
// Additive and present only when it happened, so older clients are unaffected.
|
|
1818
|
+
...(degraded ? { degraded: true, degradedReason: DEGRADED_SUBMIT_REASON } : {}),
|
|
1732
1819
|
});
|
|
1733
1820
|
return;
|
|
1734
1821
|
}
|
|
@@ -1775,6 +1862,7 @@ async function handleSend(req, res, ctx) {
|
|
|
1775
1862
|
held: false,
|
|
1776
1863
|
mailboxId: row.id,
|
|
1777
1864
|
reason: null,
|
|
1865
|
+
bodyLength,
|
|
1778
1866
|
});
|
|
1779
1867
|
return;
|
|
1780
1868
|
}
|
|
@@ -1793,6 +1881,7 @@ async function handleSend(req, res, ctx) {
|
|
|
1793
1881
|
held: true,
|
|
1794
1882
|
reason,
|
|
1795
1883
|
mailboxId: row.id,
|
|
1884
|
+
bodyLength,
|
|
1796
1885
|
});
|
|
1797
1886
|
}
|
|
1798
1887
|
/**
|