@alook/daemon 0.1.28 → 0.1.30
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 +7 -5
- package/dist/cli/index.js +753 -201
- package/dist/index.js +600 -124
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -119,6 +119,9 @@ function createDefaultAgentDriverHost(options = {}) {
|
|
|
119
119
|
};
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
// agent-driver/dist/adapters/claude/index.js
|
|
123
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
124
|
+
|
|
122
125
|
// agent-driver/dist/controller/process-host.js
|
|
123
126
|
import { EventEmitter } from "events";
|
|
124
127
|
|
|
@@ -698,6 +701,7 @@ class ProcessLane {
|
|
|
698
701
|
admissionSequence = 0;
|
|
699
702
|
activeTerminalReceipt;
|
|
700
703
|
stopPromise;
|
|
704
|
+
pendingInterrupt;
|
|
701
705
|
pendingPromptAdmission;
|
|
702
706
|
constructor(driver, ctx, opts = {}) {
|
|
703
707
|
this.driver = driver;
|
|
@@ -737,12 +741,14 @@ class ProcessLane {
|
|
|
737
741
|
try {
|
|
738
742
|
spawned = await this.driver.spawn(launchCtx);
|
|
739
743
|
} catch (error) {
|
|
744
|
+
this.settlePendingInterrupt(false);
|
|
740
745
|
this.settlePendingPrompt({ ok: false, reason: "runtime_error", error: String(error) });
|
|
741
746
|
throw error;
|
|
742
747
|
}
|
|
743
748
|
const { process: proc } = spawned;
|
|
744
749
|
this.process = proc;
|
|
745
750
|
this.attachProcess(proc);
|
|
751
|
+
this.dispatchPendingInterrupt(proc);
|
|
746
752
|
this.armPendingPromptTimeout();
|
|
747
753
|
if (this.driver.execution.lifetime === "session" && !this.writableStdin(proc)) {
|
|
748
754
|
const rejection = this.stdinUnavailableAdmission();
|
|
@@ -841,6 +847,7 @@ class ProcessLane {
|
|
|
841
847
|
pending.resolve(admission);
|
|
842
848
|
}
|
|
843
849
|
stop(opts = {}) {
|
|
850
|
+
this.settlePendingInterrupt(false);
|
|
844
851
|
this.settlePendingPrompt({ ok: false, reason: "closed", error: "runtime lane stopped before command admission" });
|
|
845
852
|
if (!this.process)
|
|
846
853
|
return Promise.resolve();
|
|
@@ -859,11 +866,39 @@ class ProcessLane {
|
|
|
859
866
|
proc.kill(opts?.signal ?? "SIGTERM");
|
|
860
867
|
}
|
|
861
868
|
}
|
|
862
|
-
async interrupt() {
|
|
869
|
+
async interrupt(input) {
|
|
863
870
|
const proc = this.process;
|
|
864
|
-
if (!proc
|
|
871
|
+
if (!proc) {
|
|
872
|
+
if (!this.started)
|
|
873
|
+
return false;
|
|
874
|
+
if (this.pendingInterrupt)
|
|
875
|
+
return this.pendingInterrupt.promise;
|
|
876
|
+
let resolve;
|
|
877
|
+
let reject;
|
|
878
|
+
const promise = new Promise((res, rej) => {
|
|
879
|
+
resolve = res;
|
|
880
|
+
reject = rej;
|
|
881
|
+
});
|
|
882
|
+
this.pendingInterrupt = { input, promise, resolve, reject };
|
|
883
|
+
return promise;
|
|
884
|
+
}
|
|
885
|
+
if (this.closed)
|
|
865
886
|
return false;
|
|
866
|
-
return
|
|
887
|
+
return this.driver.interrupt(input, proc);
|
|
888
|
+
}
|
|
889
|
+
dispatchPendingInterrupt(proc) {
|
|
890
|
+
const pending = this.pendingInterrupt;
|
|
891
|
+
if (!pending)
|
|
892
|
+
return;
|
|
893
|
+
this.pendingInterrupt = undefined;
|
|
894
|
+
this.driver.interrupt(pending.input, proc).then(pending.resolve, pending.reject);
|
|
895
|
+
}
|
|
896
|
+
settlePendingInterrupt(accepted) {
|
|
897
|
+
const pending = this.pendingInterrupt;
|
|
898
|
+
if (!pending)
|
|
899
|
+
return;
|
|
900
|
+
this.pendingInterrupt = undefined;
|
|
901
|
+
pending.resolve(accepted);
|
|
867
902
|
}
|
|
868
903
|
updateSettings(input) {
|
|
869
904
|
return this.driver.updateSettings?.(input) ?? Promise.resolve({ status: "unsupported" });
|
|
@@ -1200,11 +1235,40 @@ function tryParseJsonLine(line) {
|
|
|
1200
1235
|
|
|
1201
1236
|
// agent-driver/dist/adapters/claude/normalizer.js
|
|
1202
1237
|
var API_ERROR_RE = /API Error:.*(?:Connection error|\b[45]\d{2}\b)/i;
|
|
1238
|
+
var CLAUDE_RESULT_ERROR_CODES = new Map([
|
|
1239
|
+
["error_max_turns", "claude.error_max_turns"],
|
|
1240
|
+
["error_during_execution", "claude.error_during_execution"],
|
|
1241
|
+
["error_max_budget_usd", "claude.error_max_budget_usd"],
|
|
1242
|
+
["error_max_structured_output_retries", "claude.error_max_structured_output_retries"]
|
|
1243
|
+
]);
|
|
1244
|
+
var CLAUDE_INTERRUPT_TERMINAL_REASONS = new Set(["aborted_streaming", "aborted_tools"]);
|
|
1245
|
+
var CLAUDE_USAGE_COMPONENTS = [
|
|
1246
|
+
"inputTokens",
|
|
1247
|
+
"outputTokens",
|
|
1248
|
+
"cacheReadInputTokens",
|
|
1249
|
+
"cacheCreationInputTokens"
|
|
1250
|
+
];
|
|
1251
|
+
function safeCount(value) {
|
|
1252
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
1253
|
+
}
|
|
1254
|
+
function safeMoney(value) {
|
|
1255
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
|
|
1256
|
+
}
|
|
1257
|
+
function safeAdd(left, right) {
|
|
1258
|
+
const total = left + right;
|
|
1259
|
+
return Number.isSafeInteger(total) ? total : null;
|
|
1260
|
+
}
|
|
1261
|
+
function equalCost(left, right) {
|
|
1262
|
+
return Math.abs(left - right) <= Math.max(0.000000000001, Math.abs(right) * 0.000000001);
|
|
1263
|
+
}
|
|
1203
1264
|
|
|
1204
1265
|
class ClaudeEventNormalizer {
|
|
1205
1266
|
turnProtocol;
|
|
1206
1267
|
currentSession = null;
|
|
1207
1268
|
usageProjector = new SettledUsageProjector;
|
|
1269
|
+
claudeUsageSession = null;
|
|
1270
|
+
claudeModelUsageHighWater = new Map;
|
|
1271
|
+
claudeUsageMode = "unknown";
|
|
1208
1272
|
constructor(turnProtocol) {
|
|
1209
1273
|
this.turnProtocol = turnProtocol;
|
|
1210
1274
|
}
|
|
@@ -1271,9 +1335,9 @@ class ClaudeEventNormalizer {
|
|
|
1271
1335
|
out.push({ kind: "assistant_reasoning_completed", text: block.thinking ?? "" });
|
|
1272
1336
|
} else if (block?.type === "text") {
|
|
1273
1337
|
const text = block.text ?? "";
|
|
1274
|
-
if (API_ERROR_RE.test(text))
|
|
1275
|
-
out.push({ kind: "error", message: text });
|
|
1276
|
-
else
|
|
1338
|
+
if (API_ERROR_RE.test(text)) {
|
|
1339
|
+
out.push({ kind: "error", code: "claude.api_error", message: text });
|
|
1340
|
+
} else
|
|
1277
1341
|
completedText.push(text);
|
|
1278
1342
|
} else if (block?.type === "tool_use") {
|
|
1279
1343
|
out.push({ kind: "tool_call", name: block.name ?? "unknown_tool", input: block.input });
|
|
@@ -1299,14 +1363,19 @@ class ClaudeEventNormalizer {
|
|
|
1299
1363
|
}
|
|
1300
1364
|
handleResult(event, out) {
|
|
1301
1365
|
const rawOwner = typeof event.user_message_uuid === "string" && event.user_message_uuid.length > 0 ? event.user_message_uuid : null;
|
|
1302
|
-
const
|
|
1366
|
+
const interruptedTerminal = Boolean(this.turnProtocol && !rawOwner && event.subtype === "error_during_execution" && CLAUDE_INTERRUPT_TERMINAL_REASONS.has(event.terminal_reason));
|
|
1367
|
+
const turnOwner = rawOwner ? this.turnProtocol?.claimResult(rawOwner) ?? (this.turnProtocol ? null : `claude:${rawOwner}`) : interruptedTerminal ? this.turnProtocol.claimInterruptedResult() : null;
|
|
1303
1368
|
if (this.turnProtocol && !turnOwner)
|
|
1304
1369
|
return;
|
|
1305
1370
|
const usage = this.buildUsageTelemetry(event, rawOwner);
|
|
1306
1371
|
if (usage)
|
|
1307
1372
|
out.push(usage);
|
|
1308
|
-
if (event.is_error || event.subtype === "error_during_execution") {
|
|
1309
|
-
out.push({
|
|
1373
|
+
if (!interruptedTerminal && (event.is_error || event.subtype === "error_during_execution")) {
|
|
1374
|
+
out.push({
|
|
1375
|
+
kind: "error",
|
|
1376
|
+
code: CLAUDE_RESULT_ERROR_CODES.get(event.subtype) ?? "claude.result_error",
|
|
1377
|
+
message: String(event.result ?? "Claude runtime error")
|
|
1378
|
+
});
|
|
1310
1379
|
}
|
|
1311
1380
|
out.push({
|
|
1312
1381
|
kind: "turn_end",
|
|
@@ -1318,14 +1387,39 @@ class ClaudeEventNormalizer {
|
|
|
1318
1387
|
return this.turnProtocol?.acceptsTurnWork() ?? true;
|
|
1319
1388
|
}
|
|
1320
1389
|
buildUsageTelemetry(event, rootRequestId) {
|
|
1321
|
-
const u = event?.usage;
|
|
1322
|
-
if (!u)
|
|
1323
|
-
return null;
|
|
1324
1390
|
const backendSessionId = event.session_id ?? this.currentSession;
|
|
1325
1391
|
if (typeof backendSessionId !== "string" || !backendSessionId)
|
|
1326
1392
|
return null;
|
|
1327
1393
|
const providerRecordId = rootRequestId ?? (typeof event.request_id === "string" ? event.request_id : "invocation-result");
|
|
1328
|
-
|
|
1394
|
+
const cumulative = this.prepareClaudeModelUsage(event, backendSessionId);
|
|
1395
|
+
if (cumulative === null)
|
|
1396
|
+
return null;
|
|
1397
|
+
if (cumulative) {
|
|
1398
|
+
const hasDelta = cumulative.input > 0 || cumulative.output > 0 || cumulative.cacheRead > 0 || cumulative.cacheWrite > 0;
|
|
1399
|
+
if (!hasDelta) {
|
|
1400
|
+
this.commitClaudeModelUsage(backendSessionId, cumulative);
|
|
1401
|
+
return null;
|
|
1402
|
+
}
|
|
1403
|
+
const projected2 = this.usageProjector.project({
|
|
1404
|
+
runtime: "claude",
|
|
1405
|
+
backendSessionId,
|
|
1406
|
+
providerRecordId,
|
|
1407
|
+
source: "claude_result_model_usage",
|
|
1408
|
+
input: cumulative.input,
|
|
1409
|
+
output: cumulative.output,
|
|
1410
|
+
cacheRead: cumulative.cacheRead,
|
|
1411
|
+
cacheWrite: cumulative.cacheWrite,
|
|
1412
|
+
inputIncludesCache: false,
|
|
1413
|
+
outputIncludesReasoning: true
|
|
1414
|
+
});
|
|
1415
|
+
if (projected2)
|
|
1416
|
+
this.commitClaudeModelUsage(backendSessionId, cumulative);
|
|
1417
|
+
return projected2;
|
|
1418
|
+
}
|
|
1419
|
+
const u = event?.usage;
|
|
1420
|
+
if (!u)
|
|
1421
|
+
return null;
|
|
1422
|
+
const projected = this.usageProjector.project({
|
|
1329
1423
|
runtime: "claude",
|
|
1330
1424
|
backendSessionId,
|
|
1331
1425
|
providerRecordId,
|
|
@@ -1337,6 +1431,102 @@ class ClaudeEventNormalizer {
|
|
|
1337
1431
|
inputIncludesCache: false,
|
|
1338
1432
|
outputIncludesReasoning: true
|
|
1339
1433
|
});
|
|
1434
|
+
if (projected)
|
|
1435
|
+
this.claudeUsageMode = "legacy";
|
|
1436
|
+
return projected;
|
|
1437
|
+
}
|
|
1438
|
+
prepareClaudeModelUsage(event, backendSessionId) {
|
|
1439
|
+
if (this.claudeUsageMode === "invalid")
|
|
1440
|
+
return null;
|
|
1441
|
+
if (!Object.hasOwn(event ?? {}, "modelUsage")) {
|
|
1442
|
+
if (this.claudeUsageMode !== "model_usage")
|
|
1443
|
+
return;
|
|
1444
|
+
return event?.usage ? this.lockClaudeUsageMode() : null;
|
|
1445
|
+
}
|
|
1446
|
+
if (this.claudeUsageMode === "legacy")
|
|
1447
|
+
return this.lockClaudeUsageMode();
|
|
1448
|
+
if (this.claudeUsageMode === "unknown")
|
|
1449
|
+
this.claudeUsageMode = "model_usage";
|
|
1450
|
+
this.claudeUsageSession ??= backendSessionId;
|
|
1451
|
+
if (this.claudeUsageSession !== null && this.claudeUsageSession !== backendSessionId) {
|
|
1452
|
+
return this.lockClaudeUsageMode();
|
|
1453
|
+
}
|
|
1454
|
+
if (!event.modelUsage || typeof event.modelUsage !== "object" || Array.isArray(event.modelUsage)) {
|
|
1455
|
+
return null;
|
|
1456
|
+
}
|
|
1457
|
+
const entries = Object.entries(event.modelUsage);
|
|
1458
|
+
if (entries.length === 0)
|
|
1459
|
+
return null;
|
|
1460
|
+
const highWater = new Map;
|
|
1461
|
+
let input = 0;
|
|
1462
|
+
let output = 0;
|
|
1463
|
+
let cacheRead = 0;
|
|
1464
|
+
let cacheWrite = 0;
|
|
1465
|
+
let snapshotCost = 0;
|
|
1466
|
+
for (const [model, rawValue] of entries) {
|
|
1467
|
+
if (!model.trim() || !rawValue || typeof rawValue !== "object" || Array.isArray(rawValue)) {
|
|
1468
|
+
return null;
|
|
1469
|
+
}
|
|
1470
|
+
const raw = rawValue;
|
|
1471
|
+
const current = {
|
|
1472
|
+
inputTokens: safeCount(raw.inputTokens),
|
|
1473
|
+
outputTokens: safeCount(raw.outputTokens),
|
|
1474
|
+
cacheReadInputTokens: safeCount(raw.cacheReadInputTokens),
|
|
1475
|
+
cacheCreationInputTokens: safeCount(raw.cacheCreationInputTokens),
|
|
1476
|
+
costUSD: safeMoney(raw.costUSD)
|
|
1477
|
+
};
|
|
1478
|
+
if (Object.values(current).some((value) => value === null)) {
|
|
1479
|
+
return null;
|
|
1480
|
+
}
|
|
1481
|
+
const snapshot = current;
|
|
1482
|
+
const prior = this.claudeModelUsageHighWater.get(model) ?? {
|
|
1483
|
+
inputTokens: 0,
|
|
1484
|
+
outputTokens: 0,
|
|
1485
|
+
cacheReadInputTokens: 0,
|
|
1486
|
+
cacheCreationInputTokens: 0,
|
|
1487
|
+
costUSD: 0
|
|
1488
|
+
};
|
|
1489
|
+
for (const component of CLAUDE_USAGE_COMPONENTS) {
|
|
1490
|
+
if (snapshot[component] < prior[component])
|
|
1491
|
+
return null;
|
|
1492
|
+
}
|
|
1493
|
+
if (snapshot.costUSD < prior.costUSD)
|
|
1494
|
+
return null;
|
|
1495
|
+
const nextInput = safeAdd(input, snapshot.inputTokens - prior.inputTokens);
|
|
1496
|
+
const nextOutput = safeAdd(output, snapshot.outputTokens - prior.outputTokens);
|
|
1497
|
+
const nextCacheRead = safeAdd(cacheRead, snapshot.cacheReadInputTokens - prior.cacheReadInputTokens);
|
|
1498
|
+
const nextCacheWrite = safeAdd(cacheWrite, snapshot.cacheCreationInputTokens - prior.cacheCreationInputTokens);
|
|
1499
|
+
if (nextInput === null || nextOutput === null || nextCacheRead === null || nextCacheWrite === null) {
|
|
1500
|
+
return null;
|
|
1501
|
+
}
|
|
1502
|
+
input = nextInput;
|
|
1503
|
+
output = nextOutput;
|
|
1504
|
+
cacheRead = nextCacheRead;
|
|
1505
|
+
cacheWrite = nextCacheWrite;
|
|
1506
|
+
snapshotCost += snapshot.costUSD;
|
|
1507
|
+
if (!Number.isFinite(snapshotCost))
|
|
1508
|
+
return null;
|
|
1509
|
+
highWater.set(model, snapshot);
|
|
1510
|
+
}
|
|
1511
|
+
for (const model of this.claudeModelUsageHighWater.keys()) {
|
|
1512
|
+
if (!highWater.has(model))
|
|
1513
|
+
return null;
|
|
1514
|
+
}
|
|
1515
|
+
if (Object.hasOwn(event, "total_cost_usd")) {
|
|
1516
|
+
const totalCost = safeMoney(event.total_cost_usd);
|
|
1517
|
+
if (totalCost === null || !equalCost(snapshotCost, totalCost))
|
|
1518
|
+
return null;
|
|
1519
|
+
}
|
|
1520
|
+
return { highWater, input, output, cacheRead, cacheWrite };
|
|
1521
|
+
}
|
|
1522
|
+
commitClaudeModelUsage(backendSessionId, projection) {
|
|
1523
|
+
this.claudeUsageSession ??= backendSessionId;
|
|
1524
|
+
this.claudeModelUsageHighWater = projection.highWater;
|
|
1525
|
+
this.claudeUsageMode = "model_usage";
|
|
1526
|
+
}
|
|
1527
|
+
lockClaudeUsageMode() {
|
|
1528
|
+
this.claudeUsageMode = "invalid";
|
|
1529
|
+
return null;
|
|
1340
1530
|
}
|
|
1341
1531
|
}
|
|
1342
1532
|
|
|
@@ -1463,6 +1653,7 @@ class ClaudeTurnProtocol {
|
|
|
1463
1653
|
segmentOwnerUuid = null;
|
|
1464
1654
|
delivered = new Map;
|
|
1465
1655
|
awaitingFollowOn = false;
|
|
1656
|
+
interruptRequested = false;
|
|
1466
1657
|
finalized = false;
|
|
1467
1658
|
beginTurn() {
|
|
1468
1659
|
const uuid = randomUUID4();
|
|
@@ -1471,6 +1662,7 @@ class ClaudeTurnProtocol {
|
|
|
1471
1662
|
this.delivered.clear();
|
|
1472
1663
|
this.delivered.set(uuid, { acknowledged: false, followOn: false });
|
|
1473
1664
|
this.awaitingFollowOn = false;
|
|
1665
|
+
this.interruptRequested = false;
|
|
1474
1666
|
this.finalized = false;
|
|
1475
1667
|
return this.receipt(uuid);
|
|
1476
1668
|
}
|
|
@@ -1497,6 +1689,13 @@ class ClaudeTurnProtocol {
|
|
|
1497
1689
|
return false;
|
|
1498
1690
|
return this.delivered.get(this.rootUuid)?.acknowledged === true;
|
|
1499
1691
|
}
|
|
1692
|
+
hasActiveTurn() {
|
|
1693
|
+
return this.rootUuid !== null && !this.finalized;
|
|
1694
|
+
}
|
|
1695
|
+
noteInterruptRequested() {
|
|
1696
|
+
if (this.hasActiveTurn())
|
|
1697
|
+
this.interruptRequested = true;
|
|
1698
|
+
}
|
|
1500
1699
|
claimResult(userMessageUuid) {
|
|
1501
1700
|
if (this.finalized || !this.rootUuid || userMessageUuid !== this.segmentOwnerUuid)
|
|
1502
1701
|
return null;
|
|
@@ -1513,6 +1712,19 @@ class ClaudeTurnProtocol {
|
|
|
1513
1712
|
this.finalized = true;
|
|
1514
1713
|
return this.receipt(this.rootUuid);
|
|
1515
1714
|
}
|
|
1715
|
+
claimInterruptedResult() {
|
|
1716
|
+
if (this.finalized || !this.rootUuid || !this.interruptRequested)
|
|
1717
|
+
return null;
|
|
1718
|
+
const unacknowledged = [...this.delivered.values()].filter((input) => !input.acknowledged);
|
|
1719
|
+
if (unacknowledged.length > 0) {
|
|
1720
|
+
this.awaitingFollowOn = true;
|
|
1721
|
+
for (const input of unacknowledged)
|
|
1722
|
+
input.followOn = true;
|
|
1723
|
+
return null;
|
|
1724
|
+
}
|
|
1725
|
+
this.finalized = true;
|
|
1726
|
+
return this.receipt(this.rootUuid);
|
|
1727
|
+
}
|
|
1516
1728
|
receipt(uuid) {
|
|
1517
1729
|
return `claude:${uuid}`;
|
|
1518
1730
|
}
|
|
@@ -1538,7 +1750,9 @@ class ClaudeDriver {
|
|
|
1538
1750
|
};
|
|
1539
1751
|
turnProtocol = new ClaudeTurnProtocol;
|
|
1540
1752
|
eventNormalizer = new ClaudeEventNormalizer(this.turnProtocol);
|
|
1753
|
+
pendingInterrupt;
|
|
1541
1754
|
beginTurn() {
|
|
1755
|
+
this.pendingInterrupt = undefined;
|
|
1542
1756
|
const receipt = this.turnProtocol.beginTurn();
|
|
1543
1757
|
this.eventNormalizer.beginTurn();
|
|
1544
1758
|
return receipt;
|
|
@@ -1577,11 +1791,45 @@ class ClaudeDriver {
|
|
|
1577
1791
|
return { process: proc };
|
|
1578
1792
|
}
|
|
1579
1793
|
normalizeLine(line) {
|
|
1580
|
-
|
|
1794
|
+
const events = this.eventNormalizer.normalizeLine(line);
|
|
1795
|
+
this.flushPendingInterrupt();
|
|
1796
|
+
return events;
|
|
1581
1797
|
}
|
|
1582
1798
|
get currentSessionId() {
|
|
1583
1799
|
return this.eventNormalizer.currentSessionId;
|
|
1584
1800
|
}
|
|
1801
|
+
async interrupt(input, process2) {
|
|
1802
|
+
const stdin = process2.stdin;
|
|
1803
|
+
if (!this.turnProtocol.hasActiveTurn() || !stdin || stdin.destroyed || stdin.writableEnded || stdin.writable === false)
|
|
1804
|
+
return false;
|
|
1805
|
+
this.turnProtocol.noteInterruptRequested();
|
|
1806
|
+
if (!this.turnProtocol.acceptsTurnWork()) {
|
|
1807
|
+
this.pendingInterrupt ??= { input, process: process2 };
|
|
1808
|
+
return true;
|
|
1809
|
+
}
|
|
1810
|
+
this.writeInterrupt(input, process2);
|
|
1811
|
+
return true;
|
|
1812
|
+
}
|
|
1813
|
+
writeInterrupt(input, process2) {
|
|
1814
|
+
process2.stdin.write(JSON.stringify({
|
|
1815
|
+
type: "control_request",
|
|
1816
|
+
request_id: input.requestId ?? randomUUID5(),
|
|
1817
|
+
request: { subtype: "interrupt" }
|
|
1818
|
+
}) + `
|
|
1819
|
+
`);
|
|
1820
|
+
}
|
|
1821
|
+
flushPendingInterrupt() {
|
|
1822
|
+
const pending = this.pendingInterrupt;
|
|
1823
|
+
if (!pending || !this.turnProtocol.acceptsTurnWork())
|
|
1824
|
+
return;
|
|
1825
|
+
this.pendingInterrupt = undefined;
|
|
1826
|
+
const stdin = pending.process.stdin;
|
|
1827
|
+
if (!stdin || stdin.destroyed || stdin.writableEnded || stdin.writable === false)
|
|
1828
|
+
return;
|
|
1829
|
+
try {
|
|
1830
|
+
this.writeInterrupt(pending.input, pending.process);
|
|
1831
|
+
} catch {}
|
|
1832
|
+
}
|
|
1585
1833
|
encodeMessage(text, sessionId, opts) {
|
|
1586
1834
|
const uuid = opts?.mode === "idle" ? this.turnProtocol.rootInputUuid() : this.turnProtocol.steeringInputUuid();
|
|
1587
1835
|
return JSON.stringify({
|
|
@@ -1713,6 +1961,35 @@ import { randomBytes } from "node:crypto";
|
|
|
1713
1961
|
var codexQuotaSourceEpoch = randomBytes(16).toString("base64url");
|
|
1714
1962
|
var codexQuotaSourceGeneration = 0;
|
|
1715
1963
|
var codexAccountFingerprint = null;
|
|
1964
|
+
var CODEX_ERROR_INFO_CODES = new Set([
|
|
1965
|
+
"contextWindowExceeded",
|
|
1966
|
+
"sessionBudgetExceeded",
|
|
1967
|
+
"usageLimitExceeded",
|
|
1968
|
+
"serverOverloaded",
|
|
1969
|
+
"cyberPolicy",
|
|
1970
|
+
"misalignmentPolicyViolation",
|
|
1971
|
+
"internalServerError",
|
|
1972
|
+
"unauthorized",
|
|
1973
|
+
"badRequest",
|
|
1974
|
+
"threadRollbackFailed",
|
|
1975
|
+
"sandboxError",
|
|
1976
|
+
"other",
|
|
1977
|
+
"httpConnectionFailed",
|
|
1978
|
+
"responseStreamConnectionFailed",
|
|
1979
|
+
"responseStreamDisconnected",
|
|
1980
|
+
"responseTooManyFailedAttempts",
|
|
1981
|
+
"activeTurnNotSteerable"
|
|
1982
|
+
]);
|
|
1983
|
+
function snakeCase(value) {
|
|
1984
|
+
return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
|
|
1985
|
+
}
|
|
1986
|
+
function codexErrorInfoCode(value, fallback) {
|
|
1987
|
+
const label = typeof value === "string" ? value : value && typeof value === "object" && !Array.isArray(value) ? Object.keys(value)[0] : undefined;
|
|
1988
|
+
return label && CODEX_ERROR_INFO_CODES.has(label) ? `codex.${snakeCase(label)}` : fallback;
|
|
1989
|
+
}
|
|
1990
|
+
function codexRpcErrorCode(value) {
|
|
1991
|
+
return typeof value === "number" && Number.isSafeInteger(value) ? `codex.rpc.${value}` : "codex.rpc_error";
|
|
1992
|
+
}
|
|
1716
1993
|
function rotateCodexQuotaSource() {
|
|
1717
1994
|
codexQuotaSourceEpoch = randomBytes(16).toString("base64url");
|
|
1718
1995
|
codexQuotaSourceGeneration += 1;
|
|
@@ -1870,7 +2147,11 @@ class CodexEventNormalizer {
|
|
|
1870
2147
|
return this.replaceQuotaSnapshots(msg.result ?? {});
|
|
1871
2148
|
}
|
|
1872
2149
|
if (msg?.error && msg.id !== undefined) {
|
|
1873
|
-
return [{
|
|
2150
|
+
return [{
|
|
2151
|
+
kind: "error",
|
|
2152
|
+
code: codexRpcErrorCode(msg.error?.code),
|
|
2153
|
+
message: msg.error?.message ?? "Codex RPC error"
|
|
2154
|
+
}];
|
|
1874
2155
|
}
|
|
1875
2156
|
if (msg?.result?.thread?.id) {
|
|
1876
2157
|
return this.adoptAndInit(msg.result.thread.id);
|
|
@@ -1939,20 +2220,26 @@ class CodexEventNormalizer {
|
|
|
1939
2220
|
return [];
|
|
1940
2221
|
this.releaseUsageForTurn(params.threadId, params.turn.id);
|
|
1941
2222
|
if (params.turn.status === "failed") {
|
|
2223
|
+
const error = params.turn.error;
|
|
1942
2224
|
return [
|
|
1943
|
-
{
|
|
2225
|
+
{
|
|
2226
|
+
kind: "error",
|
|
2227
|
+
code: codexErrorInfoCode(error?.codexErrorInfo, "codex.turn_failed"),
|
|
2228
|
+
message: error?.message ?? "Codex turn failed"
|
|
2229
|
+
},
|
|
1944
2230
|
{ kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }
|
|
1945
2231
|
];
|
|
1946
2232
|
}
|
|
1947
|
-
if (params.turn.status === "interrupted") {
|
|
1948
|
-
return [{ kind: "error", message: "Codex turn interrupted" }, { kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }];
|
|
1949
|
-
}
|
|
1950
2233
|
return [{ kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }];
|
|
1951
2234
|
case "error":
|
|
1952
2235
|
if (params?.willRetry === true) {
|
|
1953
2236
|
return [{ kind: "runtime_recovery", stage: "retrying", source: "codex_stream" }];
|
|
1954
2237
|
}
|
|
1955
|
-
return [{
|
|
2238
|
+
return [{
|
|
2239
|
+
kind: "error",
|
|
2240
|
+
code: codexErrorInfoCode(params?.error?.codexErrorInfo, "codex.error"),
|
|
2241
|
+
message: params?.error?.message ?? params?.message ?? "Codex error"
|
|
2242
|
+
}];
|
|
1956
2243
|
case "thread/tokenUsage/updated":
|
|
1957
2244
|
return [];
|
|
1958
2245
|
case "account/rateLimits/updated":
|
|
@@ -2049,42 +2336,46 @@ class CodexEventNormalizer {
|
|
|
2049
2336
|
}
|
|
2050
2337
|
handleItemStarted(params) {
|
|
2051
2338
|
const t = params?.item?.type ?? params?.type;
|
|
2339
|
+
const callId = this.itemCallId(params);
|
|
2340
|
+
const identity = callId ? { callId } : {};
|
|
2052
2341
|
switch (t) {
|
|
2053
2342
|
case "commandExecution":
|
|
2054
|
-
return [{ kind: "tool_call", name: "shell", input: params?.item }];
|
|
2343
|
+
return [{ kind: "tool_call", ...identity, name: "shell", input: params?.item }];
|
|
2055
2344
|
case "contextCompaction":
|
|
2056
2345
|
return [{ kind: "compaction_started" }];
|
|
2057
2346
|
case "enteredReviewMode":
|
|
2058
2347
|
return [{ kind: "review_started" }];
|
|
2059
2348
|
case "fileChange":
|
|
2060
|
-
return [{ kind: "tool_call", name: "file_change", input: normalizeFileChangeInput(params?.item) }];
|
|
2349
|
+
return [{ kind: "tool_call", ...identity, name: "file_change", input: normalizeFileChangeInput(params?.item) }];
|
|
2061
2350
|
case "mcpToolCall":
|
|
2062
|
-
return [{ kind: "tool_call", name: `mcp_${params?.item?.name ?? "tool"}`, input: params?.item }];
|
|
2351
|
+
return [{ kind: "tool_call", ...identity, name: `mcp_${params?.item?.tool ?? params?.item?.name ?? "tool"}`, input: params?.item }];
|
|
2063
2352
|
case "webSearch":
|
|
2064
|
-
return [{ kind: "tool_call", name: "web_search", input: params?.item }];
|
|
2353
|
+
return [{ kind: "tool_call", ...identity, name: "web_search", input: params?.item }];
|
|
2065
2354
|
case "collabAgentToolCall":
|
|
2066
|
-
return [{ kind: "tool_call", name: "collab_tool_call", input: params?.item }];
|
|
2355
|
+
return [{ kind: "tool_call", ...identity, name: "collab_tool_call", input: params?.item }];
|
|
2067
2356
|
default:
|
|
2068
2357
|
return [];
|
|
2069
2358
|
}
|
|
2070
2359
|
}
|
|
2071
2360
|
handleItemCompleted(params) {
|
|
2072
2361
|
const t = params?.item?.type ?? params?.type;
|
|
2362
|
+
const callId = this.itemCallId(params);
|
|
2363
|
+
const identity = callId ? { callId } : {};
|
|
2073
2364
|
switch (t) {
|
|
2074
2365
|
case "commandExecution":
|
|
2075
|
-
return [{ kind: "tool_output", name: "shell" }];
|
|
2366
|
+
return [{ kind: "tool_output", ...identity, name: "shell" }];
|
|
2076
2367
|
case "contextCompaction":
|
|
2077
2368
|
return [{ kind: "compaction_finished" }];
|
|
2078
2369
|
case "exitedReviewMode":
|
|
2079
2370
|
return [{ kind: "review_finished" }];
|
|
2080
2371
|
case "fileChange":
|
|
2081
|
-
return [{ kind: "tool_output", name: "file_change" }];
|
|
2372
|
+
return [{ kind: "tool_output", ...identity, name: "file_change" }];
|
|
2082
2373
|
case "mcpToolCall":
|
|
2083
|
-
return [{ kind: "tool_output", name: `mcp_${params?.item?.name ?? "tool"}` }];
|
|
2374
|
+
return [{ kind: "tool_output", ...identity, name: `mcp_${params?.item?.tool ?? params?.item?.name ?? "tool"}` }];
|
|
2084
2375
|
case "webSearch":
|
|
2085
|
-
return [{ kind: "tool_output", name: "web_search" }];
|
|
2376
|
+
return [{ kind: "tool_output", ...identity, name: "web_search" }];
|
|
2086
2377
|
case "collabAgentToolCall":
|
|
2087
|
-
return [{ kind: "tool_output", name: "collab_tool_call" }];
|
|
2378
|
+
return [{ kind: "tool_output", ...identity, name: "collab_tool_call" }];
|
|
2088
2379
|
case "agentMessage":
|
|
2089
2380
|
return [{ kind: "assistant_message_completed", text: params?.item?.text ?? "" }];
|
|
2090
2381
|
case "reasoning":
|
|
@@ -2093,6 +2384,10 @@ class CodexEventNormalizer {
|
|
|
2093
2384
|
return [];
|
|
2094
2385
|
}
|
|
2095
2386
|
}
|
|
2387
|
+
itemCallId(params) {
|
|
2388
|
+
const value = params?.item?.id ?? params?.itemId;
|
|
2389
|
+
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
2390
|
+
}
|
|
2096
2391
|
}
|
|
2097
2392
|
|
|
2098
2393
|
// agent-driver/dist/adapters/codex/home.js
|
|
@@ -2563,6 +2858,16 @@ class CodexDriver {
|
|
|
2563
2858
|
get currentSessionId() {
|
|
2564
2859
|
return this.eventNormalizer.currentSessionId;
|
|
2565
2860
|
}
|
|
2861
|
+
async interrupt(_input, process2) {
|
|
2862
|
+
const threadId = this.eventNormalizer.currentSessionId;
|
|
2863
|
+
const turnId = this.eventNormalizer.currentTurnId;
|
|
2864
|
+
const stdin = process2.stdin;
|
|
2865
|
+
if (!threadId || !turnId || !stdin || stdin.destroyed || stdin.writableEnded || stdin.writable === false)
|
|
2866
|
+
return false;
|
|
2867
|
+
stdin.write(jsonRpcRequest("turn/interrupt", { threadId, turnId }, this.nextRequestId()) + `
|
|
2868
|
+
`);
|
|
2869
|
+
return true;
|
|
2870
|
+
}
|
|
2566
2871
|
buildTurnStart(threadId, text) {
|
|
2567
2872
|
return jsonRpcRequest("turn/start", { threadId, input: [{ type: "text", text }] }, this.nextRequestId());
|
|
2568
2873
|
}
|
|
@@ -2818,7 +3123,8 @@ class CursorAcpLane {
|
|
|
2818
3123
|
started = false;
|
|
2819
3124
|
ready = false;
|
|
2820
3125
|
sessionId = null;
|
|
2821
|
-
|
|
3126
|
+
currentPromptRequestId = null;
|
|
3127
|
+
terminalOwner = null;
|
|
2822
3128
|
requestedStopReason;
|
|
2823
3129
|
spawnPromise;
|
|
2824
3130
|
stopPromise;
|
|
@@ -2890,16 +3196,19 @@ class CursorAcpLane {
|
|
|
2890
3196
|
async send(input) {
|
|
2891
3197
|
if (!this.ready || !this.process || this.isClosed())
|
|
2892
3198
|
return { ok: false, reason: "closed" };
|
|
2893
|
-
if (input.mode
|
|
2894
|
-
|
|
3199
|
+
if (input.mode === "busy") {
|
|
3200
|
+
if (this.currentPromptRequestId === null) {
|
|
3201
|
+
return { ok: false, reason: "runtime_busy", error: "Cursor ACP has no active prompt to steer" };
|
|
3202
|
+
}
|
|
3203
|
+
return this.admitPrompt(input.text, "steer");
|
|
2895
3204
|
}
|
|
2896
|
-
if (this.
|
|
3205
|
+
if (this.currentPromptRequestId !== null) {
|
|
2897
3206
|
return { ok: false, reason: "runtime_busy", error: "Cursor ACP prompt is still active" };
|
|
2898
3207
|
}
|
|
2899
3208
|
return this.admitPrompt(input.text);
|
|
2900
3209
|
}
|
|
2901
3210
|
async interrupt(_input) {
|
|
2902
|
-
if (!this.ready || !this.sessionId ||
|
|
3211
|
+
if (!this.ready || !this.sessionId || this.currentPromptRequestId === null || this.isClosed())
|
|
2903
3212
|
return false;
|
|
2904
3213
|
this.write({ jsonrpc: "2.0", method: "session/cancel", params: { sessionId: this.sessionId } });
|
|
2905
3214
|
return true;
|
|
@@ -2907,7 +3216,8 @@ class CursorAcpLane {
|
|
|
2907
3216
|
stop(input = {}) {
|
|
2908
3217
|
this.requestedStopReason ??= input.reason ?? "requested_stop";
|
|
2909
3218
|
this.ready = false;
|
|
2910
|
-
this.
|
|
3219
|
+
this.currentPromptRequestId = null;
|
|
3220
|
+
this.terminalOwner = null;
|
|
2911
3221
|
this.openToolCalls.clear();
|
|
2912
3222
|
this.rejectAllPending(new Error("Cursor ACP lane stopped"));
|
|
2913
3223
|
this.stopPromise ??= this.stopPhysicalOnce(input);
|
|
@@ -3013,19 +3323,22 @@ class CursorAcpLane {
|
|
|
3013
3323
|
throw new CursorAcpIncompatibleError("Cursor ACP did not confirm the exact configured model");
|
|
3014
3324
|
}
|
|
3015
3325
|
}
|
|
3016
|
-
admitPrompt(text) {
|
|
3326
|
+
admitPrompt(text, delivery = "prompt") {
|
|
3017
3327
|
if (!this.sessionId)
|
|
3018
3328
|
return { ok: false, reason: "not_ready", error: "Cursor ACP session is not ready" };
|
|
3019
|
-
if (this.activePrompt)
|
|
3020
|
-
return { ok: false, reason: "runtime_busy", error: "Cursor ACP prompt is still active" };
|
|
3021
3329
|
const requestId = ++this.requestSequence;
|
|
3022
|
-
const
|
|
3330
|
+
const prompt = {
|
|
3023
3331
|
requestId,
|
|
3024
3332
|
receipt: `cursor:acp:${requestId}`
|
|
3025
3333
|
};
|
|
3334
|
+
const previousPromptRequestId = this.currentPromptRequestId;
|
|
3335
|
+
const previousTerminalOwner = this.terminalOwner;
|
|
3336
|
+
const previousOpenToolCalls = new Set(this.openToolCalls);
|
|
3337
|
+
if (delivery === "prompt")
|
|
3338
|
+
this.terminalOwner = prompt.receipt;
|
|
3339
|
+
this.currentPromptRequestId = requestId;
|
|
3026
3340
|
this.openToolCalls.clear();
|
|
3027
|
-
this.
|
|
3028
|
-
this.pending.set(requestId, { kind: "prompt", method: "session/prompt", active });
|
|
3341
|
+
this.pending.set(requestId, { kind: "prompt", method: "session/prompt", prompt });
|
|
3029
3342
|
try {
|
|
3030
3343
|
this.write({
|
|
3031
3344
|
jsonrpc: "2.0",
|
|
@@ -3038,41 +3351,51 @@ class CursorAcpLane {
|
|
|
3038
3351
|
});
|
|
3039
3352
|
} catch (error) {
|
|
3040
3353
|
this.pending.delete(requestId);
|
|
3041
|
-
if (this.
|
|
3042
|
-
this.
|
|
3354
|
+
if (this.currentPromptRequestId === requestId) {
|
|
3355
|
+
this.currentPromptRequestId = previousPromptRequestId;
|
|
3356
|
+
this.terminalOwner = previousTerminalOwner;
|
|
3357
|
+
this.openToolCalls.clear();
|
|
3358
|
+
for (const toolCallId of previousOpenToolCalls)
|
|
3359
|
+
this.openToolCalls.add(toolCallId);
|
|
3360
|
+
}
|
|
3043
3361
|
throw error;
|
|
3044
3362
|
}
|
|
3045
|
-
return { ok: true, acceptedAs:
|
|
3363
|
+
return { ok: true, acceptedAs: delivery, receipt: prompt.receipt };
|
|
3046
3364
|
}
|
|
3047
|
-
completePrompt(
|
|
3048
|
-
if (this.
|
|
3365
|
+
completePrompt(prompt, value) {
|
|
3366
|
+
if (this.currentPromptRequestId !== prompt.requestId)
|
|
3049
3367
|
return;
|
|
3050
3368
|
const result = record2(value);
|
|
3051
3369
|
if (!result || typeof result.stopReason !== "string" || !PROMPT_STOP_REASONS.has(result.stopReason)) {
|
|
3052
|
-
this.failPrompt(
|
|
3370
|
+
this.failPrompt(prompt, new Error("Cursor ACP prompt response did not contain a supported stopReason"), "cursor.invalid_stop_reason");
|
|
3053
3371
|
return;
|
|
3054
3372
|
}
|
|
3055
|
-
this.
|
|
3373
|
+
const terminalOwner = this.terminalOwner ?? prompt.receipt;
|
|
3374
|
+
this.currentPromptRequestId = null;
|
|
3375
|
+
this.terminalOwner = null;
|
|
3056
3376
|
this.openToolCalls.clear();
|
|
3057
3377
|
this.events.emit("runtime_event", {
|
|
3058
3378
|
kind: "turn_end",
|
|
3059
3379
|
sessionId: this.sessionId ?? undefined,
|
|
3060
|
-
turnOwner:
|
|
3380
|
+
turnOwner: terminalOwner
|
|
3061
3381
|
});
|
|
3062
3382
|
}
|
|
3063
|
-
failPrompt(
|
|
3064
|
-
if (this.
|
|
3383
|
+
failPrompt(prompt, error, code = "cursor.prompt_failed") {
|
|
3384
|
+
if (this.currentPromptRequestId !== prompt.requestId)
|
|
3065
3385
|
return;
|
|
3066
|
-
this.
|
|
3386
|
+
const terminalOwner = this.terminalOwner ?? prompt.receipt;
|
|
3387
|
+
this.currentPromptRequestId = null;
|
|
3388
|
+
this.terminalOwner = null;
|
|
3067
3389
|
this.openToolCalls.clear();
|
|
3068
3390
|
this.events.emit("runtime_event", {
|
|
3069
3391
|
kind: "error",
|
|
3392
|
+
code,
|
|
3070
3393
|
message: error instanceof Error ? error.message : String(error)
|
|
3071
3394
|
});
|
|
3072
3395
|
this.events.emit("runtime_event", {
|
|
3073
3396
|
kind: "turn_end",
|
|
3074
3397
|
sessionId: this.sessionId ?? undefined,
|
|
3075
|
-
turnOwner:
|
|
3398
|
+
turnOwner: terminalOwner
|
|
3076
3399
|
});
|
|
3077
3400
|
}
|
|
3078
3401
|
call(method, params) {
|
|
@@ -3158,7 +3481,8 @@ class CursorAcpLane {
|
|
|
3158
3481
|
}
|
|
3159
3482
|
this.processTerminal = true;
|
|
3160
3483
|
this.ready = false;
|
|
3161
|
-
this.
|
|
3484
|
+
this.currentPromptRequestId = null;
|
|
3485
|
+
this.terminalOwner = null;
|
|
3162
3486
|
this.openToolCalls.clear();
|
|
3163
3487
|
this.stopPromise ??= this.stopPhysicalOnce({ reason: "runtime_error", forceAfterMs: 0 });
|
|
3164
3488
|
this.stopPromise.catch(() => {});
|
|
@@ -3176,7 +3500,8 @@ class CursorAcpLane {
|
|
|
3176
3500
|
return;
|
|
3177
3501
|
this.processTerminal = true;
|
|
3178
3502
|
this.ready = false;
|
|
3179
|
-
this.
|
|
3503
|
+
this.currentPromptRequestId = null;
|
|
3504
|
+
this.terminalOwner = null;
|
|
3180
3505
|
this.openToolCalls.clear();
|
|
3181
3506
|
this.rejectAllPending(new Error("Cursor ACP process exited"));
|
|
3182
3507
|
if (this.suppressExit || !this.processActivated)
|
|
@@ -3218,11 +3543,12 @@ class CursorAcpLane {
|
|
|
3218
3543
|
this.pending.delete(id);
|
|
3219
3544
|
if (message.error !== undefined) {
|
|
3220
3545
|
const payload = record2(message.error);
|
|
3221
|
-
|
|
3546
|
+
const rpcCode = typeof payload?.code === "number" && Number.isSafeInteger(payload.code) ? payload.code : undefined;
|
|
3547
|
+
this.failPrompt(pending.prompt, new CursorAcpRpcError(pending.method, rpcCode, rpcErrorMessage(message.error)), rpcCode === undefined ? "cursor.rpc_error" : `cursor.rpc.${rpcCode}`);
|
|
3222
3548
|
} else if (!("result" in message)) {
|
|
3223
|
-
this.failPrompt(pending.
|
|
3549
|
+
this.failPrompt(pending.prompt, new Error("Cursor ACP response omitted result"), "cursor.invalid_response");
|
|
3224
3550
|
} else {
|
|
3225
|
-
this.completePrompt(pending.
|
|
3551
|
+
this.completePrompt(pending.prompt, message.result);
|
|
3226
3552
|
}
|
|
3227
3553
|
return;
|
|
3228
3554
|
}
|
|
@@ -3263,7 +3589,7 @@ class CursorAcpLane {
|
|
|
3263
3589
|
const sameSession = payload?.sessionId === this.sessionId;
|
|
3264
3590
|
const options = Array.isArray(payload?.options) ? payload.options.map(record2).filter(Boolean) : [];
|
|
3265
3591
|
const allowOnce = options.find((option) => option.kind === "allow_once" && typeof option.optionId === "string" && option.optionId.trim().length > 0);
|
|
3266
|
-
if (!this.ready ||
|
|
3592
|
+
if (!this.ready || this.currentPromptRequestId === null || !sameSession || !allowOnce) {
|
|
3267
3593
|
this.write({ jsonrpc: "2.0", id, result: { outcome: { outcome: "cancelled" } } });
|
|
3268
3594
|
this.diagnostic("error", "Cursor ACP permission request was not allowed for the active prompt");
|
|
3269
3595
|
return;
|
|
@@ -3287,7 +3613,7 @@ class CursorAcpLane {
|
|
|
3287
3613
|
this.diagnostic("warning", "Cursor ACP emitted an update for a different session");
|
|
3288
3614
|
return;
|
|
3289
3615
|
}
|
|
3290
|
-
if (!this.ready ||
|
|
3616
|
+
if (!this.ready || this.currentPromptRequestId === null) {
|
|
3291
3617
|
this.diagnostic("warning", "Cursor ACP emitted a session update without an active prompt");
|
|
3292
3618
|
return;
|
|
3293
3619
|
}
|
|
@@ -3505,7 +3831,11 @@ function parseModelRef(model) {
|
|
|
3505
3831
|
function messageFromError(value) {
|
|
3506
3832
|
const payload = record3(value);
|
|
3507
3833
|
const message = typeof payload?.message === "string" && payload.message.trim() ? payload.message : undefined;
|
|
3508
|
-
return message
|
|
3834
|
+
return message ?? "OpenCode reported an inconsistent turn outcome";
|
|
3835
|
+
}
|
|
3836
|
+
function codeFromError(value) {
|
|
3837
|
+
const payload = record3(value);
|
|
3838
|
+
return payload?.type === "unknown" ? "opencode.unknown" : "opencode.step_failed";
|
|
3509
3839
|
}
|
|
3510
3840
|
|
|
3511
3841
|
class OpenCodeServiceLane {
|
|
@@ -4173,7 +4503,10 @@ class OpenCodeServiceLane {
|
|
|
4173
4503
|
root.outcomes.push({
|
|
4174
4504
|
seq,
|
|
4175
4505
|
ok: successful,
|
|
4176
|
-
...!successful ? {
|
|
4506
|
+
...!successful ? {
|
|
4507
|
+
code: "opencode.unsupported_finish",
|
|
4508
|
+
message: "OpenCode reported an unsupported final step outcome"
|
|
4509
|
+
} : {}
|
|
4177
4510
|
});
|
|
4178
4511
|
}
|
|
4179
4512
|
const tokens = record3(data.tokens);
|
|
@@ -4202,7 +4535,12 @@ class OpenCodeServiceLane {
|
|
|
4202
4535
|
break;
|
|
4203
4536
|
}
|
|
4204
4537
|
case "session.next.step.failed":
|
|
4205
|
-
root.outcomes.push({
|
|
4538
|
+
root.outcomes.push({
|
|
4539
|
+
seq,
|
|
4540
|
+
ok: false,
|
|
4541
|
+
code: codeFromError(data.error),
|
|
4542
|
+
message: messageFromError(data.error)
|
|
4543
|
+
});
|
|
4206
4544
|
break;
|
|
4207
4545
|
default:
|
|
4208
4546
|
break;
|
|
@@ -4382,11 +4720,11 @@ class OpenCodeServiceLane {
|
|
|
4382
4720
|
return;
|
|
4383
4721
|
const outcome = root.outcomes.filter((item) => item.seq > maxAdmission).sort((left, right) => left.seq - right.seq).at(-1);
|
|
4384
4722
|
if (!root.interrupted && !outcome) {
|
|
4385
|
-
this.settleRoot(root, false, "OpenCode drain settled without a durable turn outcome");
|
|
4723
|
+
this.settleRoot(root, false, "OpenCode drain settled without a durable turn outcome", "opencode.missing_outcome");
|
|
4386
4724
|
return;
|
|
4387
4725
|
}
|
|
4388
4726
|
if (outcome && !outcome.ok) {
|
|
4389
|
-
this.settleRoot(root, false, outcome.message ?? "OpenCode turn failed");
|
|
4727
|
+
this.settleRoot(root, false, outcome.message ?? "OpenCode turn failed", outcome.code ?? "opencode.turn_failed");
|
|
4390
4728
|
return;
|
|
4391
4729
|
}
|
|
4392
4730
|
this.settleRoot(root, true);
|
|
@@ -4412,7 +4750,7 @@ class OpenCodeServiceLane {
|
|
|
4412
4750
|
barrierStillCurrent(root, identity, generation) {
|
|
4413
4751
|
return this.activeRoot === root && root.generation === generation && root.pendingAdmissions === 0 && !root.interruptPending && this.isIdentity(identity) && this.identityMatches();
|
|
4414
4752
|
}
|
|
4415
|
-
settleRoot(root, success, message) {
|
|
4753
|
+
settleRoot(root, success, message, code = "opencode.turn_failed") {
|
|
4416
4754
|
if (this.activeRoot !== root)
|
|
4417
4755
|
return;
|
|
4418
4756
|
this.activeRoot = null;
|
|
@@ -4420,6 +4758,7 @@ class OpenCodeServiceLane {
|
|
|
4420
4758
|
if (!success) {
|
|
4421
4759
|
this.events.emit("runtime_event", {
|
|
4422
4760
|
kind: "error",
|
|
4761
|
+
code,
|
|
4423
4762
|
message: message ?? "OpenCode turn failed"
|
|
4424
4763
|
});
|
|
4425
4764
|
}
|
|
@@ -4935,7 +5274,11 @@ function mapPiSdkEvent(event, sessionId, state) {
|
|
|
4935
5274
|
return [{ kind: "assistant_message_completed", text: d.content ?? "" }];
|
|
4936
5275
|
}
|
|
4937
5276
|
case "error":
|
|
4938
|
-
return [{
|
|
5277
|
+
return [{
|
|
5278
|
+
kind: "error",
|
|
5279
|
+
code: "pi.message_error",
|
|
5280
|
+
message: d.error?.errorMessage ?? "Pi error"
|
|
5281
|
+
}];
|
|
4939
5282
|
default:
|
|
4940
5283
|
return [];
|
|
4941
5284
|
}
|
|
@@ -5215,7 +5558,7 @@ var capabilities = {
|
|
|
5215
5558
|
commandOverride: true,
|
|
5216
5559
|
resume: "by_id",
|
|
5217
5560
|
sessionLifetime: "persistent",
|
|
5218
|
-
midTurnDelivery: "
|
|
5561
|
+
midTurnDelivery: "steer",
|
|
5219
5562
|
interrupt: true
|
|
5220
5563
|
},
|
|
5221
5564
|
opencode: {
|
|
@@ -5523,7 +5866,9 @@ class LogicalAgentSession {
|
|
|
5523
5866
|
interruptedTurnId;
|
|
5524
5867
|
processTurnEnded = false;
|
|
5525
5868
|
stopRequestId;
|
|
5526
|
-
|
|
5869
|
+
anonymousOutstandingToolUses = 0;
|
|
5870
|
+
outstandingToolCallIds = new Set;
|
|
5871
|
+
toolLifecycleMismatchCount = 0;
|
|
5527
5872
|
compacting = false;
|
|
5528
5873
|
reviewing = false;
|
|
5529
5874
|
steeringDeliveries = new Set;
|
|
@@ -5713,6 +6058,9 @@ class LogicalAgentSession {
|
|
|
5713
6058
|
queueDwellCount: this.metricValue(this.queueDwellCount),
|
|
5714
6059
|
queueDwellTotalMs: this.metricValue(this.queueDwellTotalMs),
|
|
5715
6060
|
sseReconnectCount: this.metricValue(this.sseReconnectCount),
|
|
6061
|
+
outstandingToolUses: this.outstandingToolUseCount(),
|
|
6062
|
+
anonymousOutstandingToolUses: this.metricValue(this.anonymousOutstandingToolUses),
|
|
6063
|
+
toolLifecycleMismatchCount: this.metricValue(this.toolLifecycleMismatchCount),
|
|
5716
6064
|
resumeOutcome: this.resumeOutcome,
|
|
5717
6065
|
terminalOwnerKind: this.adapter.execution.terminalOwnership
|
|
5718
6066
|
}
|
|
@@ -6032,15 +6380,36 @@ class LogicalAgentSession {
|
|
|
6032
6380
|
}
|
|
6033
6381
|
return;
|
|
6034
6382
|
case "tool_call":
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6383
|
+
if (!turnId) {
|
|
6384
|
+
this.toolLifecycleMismatchCount += 1;
|
|
6385
|
+
return;
|
|
6386
|
+
}
|
|
6387
|
+
if (!this.startToolUse(event.callId))
|
|
6388
|
+
return;
|
|
6389
|
+
const startedCallId = this.toolCallId(event.callId);
|
|
6390
|
+
this.emit({
|
|
6391
|
+
type: "tool_started",
|
|
6392
|
+
turnId,
|
|
6393
|
+
...startedCallId ? { callId: startedCallId } : {},
|
|
6394
|
+
name: event.name,
|
|
6395
|
+
input: jsonValue(event.input)
|
|
6396
|
+
});
|
|
6038
6397
|
return;
|
|
6039
6398
|
case "tool_output":
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6399
|
+
if (!turnId) {
|
|
6400
|
+
this.toolLifecycleMismatchCount += 1;
|
|
6401
|
+
return;
|
|
6402
|
+
}
|
|
6403
|
+
if (!this.finishToolUse(event.callId))
|
|
6404
|
+
return;
|
|
6405
|
+
const finishedCallId = this.toolCallId(event.callId);
|
|
6406
|
+
this.emit({
|
|
6407
|
+
type: "tool_finished",
|
|
6408
|
+
turnId,
|
|
6409
|
+
...finishedCallId ? { callId: finishedCallId } : {},
|
|
6410
|
+
name: event.name
|
|
6411
|
+
});
|
|
6412
|
+
if (this.outstandingToolUseCount() === 0 && !this.toolBoundaryFlushDisabled)
|
|
6044
6413
|
this.flushSafeBoundaryQueue();
|
|
6045
6414
|
return;
|
|
6046
6415
|
case "compaction_started":
|
|
@@ -6096,7 +6465,7 @@ class LogicalAgentSession {
|
|
|
6096
6465
|
}
|
|
6097
6466
|
case "error":
|
|
6098
6467
|
this.toolBoundaryFlushDisabled = true;
|
|
6099
|
-
this.turnError = driverError("process", "runtime_error", event.message, true);
|
|
6468
|
+
this.turnError = driverError("process", stableErrorCode(event.code, "runtime_error"), event.message, true);
|
|
6100
6469
|
this.emit({
|
|
6101
6470
|
type: "diagnostic",
|
|
6102
6471
|
turnId,
|
|
@@ -6183,7 +6552,7 @@ class LogicalAgentSession {
|
|
|
6183
6552
|
this.activeTurn = undefined;
|
|
6184
6553
|
this.turnError = undefined;
|
|
6185
6554
|
this.interruptedTurnId = undefined;
|
|
6186
|
-
this.
|
|
6555
|
+
this.clearOutstandingToolUses();
|
|
6187
6556
|
this.compacting = false;
|
|
6188
6557
|
this.reviewing = false;
|
|
6189
6558
|
this.toolBoundaryFlushDisabled = false;
|
|
@@ -6207,7 +6576,7 @@ class LogicalAgentSession {
|
|
|
6207
6576
|
return this.safeBoundaryFlush;
|
|
6208
6577
|
}
|
|
6209
6578
|
canFlushSafeBoundaryQueue() {
|
|
6210
|
-
return this.state === "working" && this.behavior.midTurnDelivery === "safe_boundary_queue" && this.
|
|
6579
|
+
return this.state === "working" && this.behavior.midTurnDelivery === "safe_boundary_queue" && this.outstandingToolUseCount() === 0 && !this.compacting && !this.reviewing && this.activeTurn !== undefined && this.lane !== null && this.queued.length > 0;
|
|
6211
6580
|
}
|
|
6212
6581
|
async drainSafeBoundaryQueue() {
|
|
6213
6582
|
while (this.canFlushSafeBoundaryQueue()) {
|
|
@@ -6388,7 +6757,7 @@ class LogicalAgentSession {
|
|
|
6388
6757
|
return "compacting";
|
|
6389
6758
|
if (this.reviewing)
|
|
6390
6759
|
return "reviewing";
|
|
6391
|
-
if (this.
|
|
6760
|
+
if (this.outstandingToolUseCount() > 0)
|
|
6392
6761
|
return "tool_wait";
|
|
6393
6762
|
if (this.activeTurn)
|
|
6394
6763
|
return "working";
|
|
@@ -6398,6 +6767,46 @@ class LogicalAgentSession {
|
|
|
6398
6767
|
const now = this.host.now();
|
|
6399
6768
|
return Number.isFinite(now) && now >= 0 ? now : 0;
|
|
6400
6769
|
}
|
|
6770
|
+
outstandingToolUseCount() {
|
|
6771
|
+
return this.anonymousOutstandingToolUses + this.outstandingToolCallIds.size;
|
|
6772
|
+
}
|
|
6773
|
+
startToolUse(rawCallId) {
|
|
6774
|
+
const callId = this.toolCallId(rawCallId);
|
|
6775
|
+
if (!callId) {
|
|
6776
|
+
this.anonymousOutstandingToolUses += 1;
|
|
6777
|
+
return true;
|
|
6778
|
+
}
|
|
6779
|
+
if (this.outstandingToolCallIds.has(callId)) {
|
|
6780
|
+
this.toolLifecycleMismatchCount += 1;
|
|
6781
|
+
return false;
|
|
6782
|
+
}
|
|
6783
|
+
this.outstandingToolCallIds.add(callId);
|
|
6784
|
+
return true;
|
|
6785
|
+
}
|
|
6786
|
+
finishToolUse(rawCallId) {
|
|
6787
|
+
const callId = this.toolCallId(rawCallId);
|
|
6788
|
+
if (!callId) {
|
|
6789
|
+
if (this.anonymousOutstandingToolUses === 0) {
|
|
6790
|
+
this.toolLifecycleMismatchCount += 1;
|
|
6791
|
+
return false;
|
|
6792
|
+
}
|
|
6793
|
+
this.anonymousOutstandingToolUses -= 1;
|
|
6794
|
+
return true;
|
|
6795
|
+
}
|
|
6796
|
+
if (!this.outstandingToolCallIds.delete(callId)) {
|
|
6797
|
+
this.toolLifecycleMismatchCount += 1;
|
|
6798
|
+
return false;
|
|
6799
|
+
}
|
|
6800
|
+
return true;
|
|
6801
|
+
}
|
|
6802
|
+
clearOutstandingToolUses() {
|
|
6803
|
+
this.anonymousOutstandingToolUses = 0;
|
|
6804
|
+
this.outstandingToolCallIds.clear();
|
|
6805
|
+
}
|
|
6806
|
+
toolCallId(rawCallId) {
|
|
6807
|
+
const callId = rawCallId?.trim();
|
|
6808
|
+
return callId && callId.length <= 1024 ? callId : undefined;
|
|
6809
|
+
}
|
|
6401
6810
|
metricValue(value) {
|
|
6402
6811
|
return Number.isFinite(value) && value >= 0 ? value : 0;
|
|
6403
6812
|
}
|
|
@@ -7892,6 +8301,7 @@ function reduceManager(state, event) {
|
|
|
7892
8301
|
if ((lease.state === "active" || lease.state === "suspect_active") && lease.outstandingToolUses !== undefined) {
|
|
7893
8302
|
const unblockedLease = { ...lease };
|
|
7894
8303
|
delete unblockedLease.outstandingToolUses;
|
|
8304
|
+
delete unblockedLease.outstandingToolCallIds;
|
|
7895
8305
|
a.execution.lease = unblockedLease;
|
|
7896
8306
|
}
|
|
7897
8307
|
});
|
|
@@ -8152,26 +8562,40 @@ function onTurnToolLifecycle(state, event, lifecycle) {
|
|
|
8152
8562
|
const matchesTerminal = lifecycle === "started" && current.state === "none" && current.lastTerminal !== null && sameIdentity(current.lastTerminal.identity, identity);
|
|
8153
8563
|
if (!matchesActive && !matchesTerminal)
|
|
8154
8564
|
return { state, effects: [] };
|
|
8155
|
-
if (lifecycle === "finished" && matchesActive && (current.outstandingToolUses ?? 0) === 0) {
|
|
8156
|
-
return { state, effects: [] };
|
|
8157
|
-
}
|
|
8158
8565
|
return mutate(state, event.agentId, (agent) => {
|
|
8159
8566
|
if (!matchesSession(agent, event.sessionInstanceId))
|
|
8160
8567
|
return;
|
|
8161
8568
|
const lease = agent.execution.lease;
|
|
8569
|
+
const callId = normalizedToolCallId(event.callId);
|
|
8162
8570
|
if ((lease.state === "active" || lease.state === "suspect_active") && sameIdentity(lease.identity, identity)) {
|
|
8163
|
-
const
|
|
8571
|
+
const identified = new Set(lease.outstandingToolCallIds ?? []);
|
|
8572
|
+
const currentCount = lease.outstandingToolUses ?? 0;
|
|
8573
|
+
const anonymousCount = Math.max(0, currentCount - identified.size);
|
|
8574
|
+
if (lifecycle === "started") {
|
|
8575
|
+
if (callId && identified.has(callId))
|
|
8576
|
+
return;
|
|
8577
|
+
if (callId)
|
|
8578
|
+
identified.add(callId);
|
|
8579
|
+
} else if (callId) {
|
|
8580
|
+
if (!identified.delete(callId))
|
|
8581
|
+
return;
|
|
8582
|
+
} else if (anonymousCount === 0) {
|
|
8583
|
+
return;
|
|
8584
|
+
}
|
|
8585
|
+
const outstandingToolUses = currentCount + (lifecycle === "started" ? 1 : -1);
|
|
8164
8586
|
if (outstandingToolUses > 0) {
|
|
8165
8587
|
agent.execution.lease = {
|
|
8166
8588
|
...lease,
|
|
8167
8589
|
lastWorkAt: event.nowMs,
|
|
8168
8590
|
nativeDeadlineAt: event.nowMs + agent.turnSilence.normalBudgetMs,
|
|
8169
8591
|
recoveryExtensionsUsed: 0,
|
|
8170
|
-
outstandingToolUses
|
|
8592
|
+
outstandingToolUses,
|
|
8593
|
+
...identified.size > 0 ? { outstandingToolCallIds: [...identified] } : {}
|
|
8171
8594
|
};
|
|
8172
8595
|
} else {
|
|
8173
8596
|
const unblockedLease = { ...lease };
|
|
8174
8597
|
delete unblockedLease.outstandingToolUses;
|
|
8598
|
+
delete unblockedLease.outstandingToolCallIds;
|
|
8175
8599
|
agent.execution.lease = {
|
|
8176
8600
|
...unblockedLease,
|
|
8177
8601
|
lastWorkAt: event.nowMs,
|
|
@@ -8190,6 +8614,7 @@ function onTurnToolLifecycle(state, event, lifecycle) {
|
|
|
8190
8614
|
nativeDeadlineAt: event.nowMs + agent.turnSilence.normalBudgetMs,
|
|
8191
8615
|
recoveryExtensionsUsed: 0,
|
|
8192
8616
|
outstandingToolUses: 1,
|
|
8617
|
+
...callId ? { outstandingToolCallIds: [callId] } : {},
|
|
8193
8618
|
reason: "work_after_terminal"
|
|
8194
8619
|
};
|
|
8195
8620
|
}
|
|
@@ -8353,6 +8778,10 @@ function identityOf(event) {
|
|
|
8353
8778
|
function sameIdentity(left, right) {
|
|
8354
8779
|
return left.sessionInstanceId === right.sessionInstanceId && left.turnId === right.turnId;
|
|
8355
8780
|
}
|
|
8781
|
+
function normalizedToolCallId(rawCallId) {
|
|
8782
|
+
const callId = rawCallId?.trim();
|
|
8783
|
+
return callId && callId.length <= 1024 ? callId : undefined;
|
|
8784
|
+
}
|
|
8356
8785
|
function leaseIsWorking(lease) {
|
|
8357
8786
|
return lease.state === "active" || lease.state === "suspect_active";
|
|
8358
8787
|
}
|
|
@@ -8432,13 +8861,13 @@ function cliCommandsSection() {
|
|
|
8432
8861
|
"",
|
|
8433
8862
|
"### Messaging",
|
|
8434
8863
|
"",
|
|
8435
|
-
`1. \`${CLI} inbox pull\` — fetch unread messages
|
|
8864
|
+
`1. \`${CLI} inbox pull\` — fetch unread messages and advance their read waterlines.`,
|
|
8436
8865
|
`2. \`${CLI} message send --target <ref> --remind-after <0|Nm|Nh> --stdin\` — send to a ` + `channel, DM, or thread. ` + `The body is required through \`--stdin\` and limited to 1 KiB of UTF-8. ` + `Attach uploaded files with \`--attachment <id>\` (repeatable, order matters). ` + `\`--remind-after\` accepts \`0\`, or a whole-number duration from \`1m\` to \`24h\`.`,
|
|
8437
8866
|
`3. \`${CLI} message attachment upload --target <ref> --file <path>\` — upload a file; ` + `returns an id stable across pending→persisted.`,
|
|
8438
8867
|
`4. \`${CLI} message attachment download --id <id> [--out <path>]\` — download any ` + `attachment you can see (or your own pending uploads).`,
|
|
8439
|
-
`5. \`${CLI} message
|
|
8440
|
-
`6. \`${CLI} message
|
|
8441
|
-
`7. \`${CLI} message
|
|
8868
|
+
`5. \`${CLI} message property set --target <full-message-ref> --json <json>\` — add a supported property value to a message.`,
|
|
8869
|
+
`6. \`${CLI} message property list --target <full-message-ref>\` — list the message's current properties and supported capabilities.`,
|
|
8870
|
+
`7. \`${CLI} message property remove --target <full-message-ref> --json <json>\` — remove a supported property value from a message.`,
|
|
8442
8871
|
`8. \`${CLI} message mark list\` — list every currently visible marked message with its full content.`,
|
|
8443
8872
|
"",
|
|
8444
8873
|
"### Servers",
|
|
@@ -8446,12 +8875,14 @@ function cliCommandsSection() {
|
|
|
8446
8875
|
`1. \`${CLI} server list\` — list your servers.`,
|
|
8447
8876
|
`2. \`${CLI} server member --server <name#discriminator>\` — list a server's members.`,
|
|
8448
8877
|
`3. \`${CLI} server join --invite <link>\` — join via invite link or token.`,
|
|
8878
|
+
`4. \`${CLI} server leave --server <name#NNNN>\` — leave a server you belong to. A server owner cannot leave.`,
|
|
8449
8879
|
"",
|
|
8450
8880
|
"### Channels",
|
|
8451
8881
|
"",
|
|
8452
8882
|
`1. \`${CLI} channel list --server <name#discriminator>\` — list top-level channels, grouped by ` + `category; each is marked \`public\` or \`private\`.`,
|
|
8453
8883
|
`2. \`${CLI} channel history --channel <ref>\` — read a channel's or thread's past messages ` + `(the context you weren't awake for). Page with \`--before N\` / \`--after N\` (seq N as ` + "anchor), `--around N` to center on a message, `--limit N` for page size.",
|
|
8454
8884
|
`3. \`${CLI} channel member --channel <ref>\` — roster of a channel or thread. A private ` + `channel/forum returns its concrete member list; a public one returns a hint pointing at ` + `\`${CLI} server member\` (its audience is the whole server).`,
|
|
8885
|
+
`4. \`${CLI} channel leave --channel <ref>\` — leave a thread or private channel/forum. Public channels require leaving their server; DMs cannot be left.`,
|
|
8455
8886
|
"",
|
|
8456
8887
|
"### Friends",
|
|
8457
8888
|
"",
|
|
@@ -8504,6 +8935,15 @@ function messagingSection() {
|
|
|
8504
8935
|
"Reply where the message came from. Post results in the channel that owns the topic. " + "When uncertain, read history (below) or DM the relevant people.",
|
|
8505
8936
|
"Write every message body in the stdin/heredoc block; never place it directly on the command line.",
|
|
8506
8937
|
"",
|
|
8938
|
+
"### Message properties",
|
|
8939
|
+
"",
|
|
8940
|
+
`Message properties are capabilities determined by the target message's channel type. \`${CLI} ` + "message property list --target <full-message-ref>` returns its current values and supported `capabilities`.",
|
|
8941
|
+
"",
|
|
8942
|
+
`- \`emoji\` works on text-channel, DM, and thread messages. Set or remove your own reaction with ` + `\`{"type":"emoji","value":"\uD83D\uDC4D"}\`; \`list\` shows everyone who reacted. Add: ` + `\`${CLI} message property set --target <full-message-ref> --json '{"type":"emoji","value":"\uD83D\uDC4D"}'\`.`,
|
|
8943
|
+
`- \`tag\` works on forum opener messages. Set or remove named tags with ` + `\`{"type":"tag","value":["bug","ready"]}\`. A forum reply is a thread message, so it ` + `supports \`emoji\`, not \`tag\`. Add: \`${CLI} message property set --target ` + `<full-message-ref> --json '{"type":"tag","value":["bug"]}'\`.`,
|
|
8944
|
+
`- \`mark\` works on every message. Mark outstanding work with \`{"type":"mark","value":true}\`: ` + `\`${CLI} message property set --target <full-message-ref> --json ` + `'{"type":"mark","value":true}'\`. Clear it with the same JSON through ` + `\`message property remove\`. Use \`${CLI} message mark list\` for the one-shot global view ` + `of all currently visible marked messages.`,
|
|
8945
|
+
"- `archived` is a reserved tag meaning the forum post is complete. Setting it hides the " + "post from the normal forum feed; removing it reopens the post.",
|
|
8946
|
+
"",
|
|
8507
8947
|
"### Context refs",
|
|
8508
8948
|
"",
|
|
8509
8949
|
"Path-style refs:",
|
|
@@ -8616,6 +9056,7 @@ function criticalRulesSection() {
|
|
|
8616
9056
|
`- **\`${CLI}\` is the only way to communicate.** Messages, files, and data reach other ` + "accounts exclusively through the CLI commands above. Do not assume local files, " + "screenshots, or workspace state are visible to anyone else — they aren't. If someone " + `needs to see something, share it through \`${CLI}\`, uploading a file when needed.`,
|
|
8617
9057
|
"- **Never expose tokens, keys, or secrets.** Redact credential-like strings from tool output " + "before sharing.",
|
|
8618
9058
|
"- **Match the sender's language.** Reply in the language they wrote in.",
|
|
9059
|
+
`- **Leaving a scope is destructive.** Use \`${CLI} channel leave\` or \`${CLI} server leave\` only after your owner explicitly asks you to leave that exact scope.`,
|
|
8619
9060
|
"- **Channel alignment**: you can't send to a channel with unread messages. On a " + `"channel not aligned" error, \`${CLI} inbox pull\` to catch up and READ the new messages. ` + "Judge if your message is still needed or overlaps with what just landed. Adjust or skip; " + "don't mechanically resend."
|
|
8620
9061
|
].join(`
|
|
8621
9062
|
`);
|
|
@@ -8628,7 +9069,7 @@ function executionModelSection() {
|
|
|
8628
9069
|
"",
|
|
8629
9070
|
"On wake, restore durable context from `memory.md` and the context timeline, then pull your inbox. " + "Follow *Outstanding work marks* below before taking new work.",
|
|
8630
9071
|
"",
|
|
8631
|
-
"`inbox pull` advances your read waterline
|
|
9072
|
+
"`inbox pull` advances your read waterline — pulled messages won't come back in a " + "future pull. A task-bearing message stays durable through its server mark even after the " + "read waterline advances."
|
|
8632
9073
|
].join(`
|
|
8633
9074
|
`);
|
|
8634
9075
|
}
|
|
@@ -8700,9 +9141,9 @@ function workspaceMemorySection(config) {
|
|
|
8700
9141
|
"",
|
|
8701
9142
|
"### Outstanding work marks",
|
|
8702
9143
|
"",
|
|
8703
|
-
"Marks are the durable work queue. If a message requires work beyond an immediate reply, " + `acknowledge it, then run \`${CLI} message
|
|
9144
|
+
"Marks are the durable work queue. If a message requires work beyond an immediate reply, " + `acknowledge it, then run \`${CLI} message property set --target <full-message-ref> ` + `--json '{"type":"mark","value":true}'\` before ` + "starting or queuing. Do not mark a message you answer immediately.",
|
|
8704
9145
|
"",
|
|
8705
|
-
"Keep it marked while the work is active, queued, or blocked; report blockers where the task " + `came from. Run \`${CLI} message
|
|
9146
|
+
"Keep it marked while the work is active, queued, or blocked; report blockers where the task " + `came from. Run \`${CLI} message property remove --target <full-message-ref> ` + `--json '{"type":"mark","value":true}'\` only after ` + "sending the result, or when the request is cancelled or superseded and nothing remains.",
|
|
8706
9147
|
"",
|
|
8707
9148
|
"If `inbox pull` returns a `markedReminder`, run " + `\`${CLI} message mark list\` before taking new work. Do not copy marked tasks into local files.`
|
|
8708
9149
|
].join(`
|
|
@@ -8819,7 +9260,7 @@ function nowLocalISO() {
|
|
|
8819
9260
|
}
|
|
8820
9261
|
|
|
8821
9262
|
// src/manager/managerRuntime.ts
|
|
8822
|
-
import { randomUUID as
|
|
9263
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
8823
9264
|
var SESSION_STOP_GRACE_MS2 = 2000;
|
|
8824
9265
|
function normalizeTerminationCause(value) {
|
|
8825
9266
|
return value === "runtime_error" || value === "killed_stalled" ? value : "other";
|
|
@@ -9056,7 +9497,7 @@ class AgentProcessManager {
|
|
|
9056
9497
|
liveBackendIds = new Map;
|
|
9057
9498
|
activeSpawnState = new Map;
|
|
9058
9499
|
publishedAgentActivity = new Map;
|
|
9059
|
-
traceProcessNonce =
|
|
9500
|
+
traceProcessNonce = randomUUID6();
|
|
9060
9501
|
nextSpawnOrdinal = 1;
|
|
9061
9502
|
nextDaemonTurnOrdinal = 1;
|
|
9062
9503
|
nextDeliveryOrdinal = 1;
|
|
@@ -9332,6 +9773,12 @@ class AgentProcessManager {
|
|
|
9332
9773
|
if (this.sessions.get(agentId) === session)
|
|
9333
9774
|
this.sessions.delete(agentId);
|
|
9334
9775
|
}
|
|
9776
|
+
async interrupt(agentId) {
|
|
9777
|
+
const session = this.sessions.get(agentId);
|
|
9778
|
+
if (!session)
|
|
9779
|
+
return;
|
|
9780
|
+
await session.interrupt({ requestId: randomUUID6(), reason: "owner_request" });
|
|
9781
|
+
}
|
|
9335
9782
|
async stopAll() {
|
|
9336
9783
|
if (this.tickTimer) {
|
|
9337
9784
|
clearInterval(this.tickTimer);
|
|
@@ -9829,7 +10276,7 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
9829
10276
|
case "reset_idle_session": {
|
|
9830
10277
|
const spawnState = this.activeSpawnState.get(effect.agentId);
|
|
9831
10278
|
const completion = {
|
|
9832
|
-
eventId: `bae_${
|
|
10279
|
+
eventId: `bae_${randomUUID6()}`,
|
|
9833
10280
|
occurredAt: new Date(this.now()).toISOString()
|
|
9834
10281
|
};
|
|
9835
10282
|
const persisted = this.forgetSession(effect.agentId, "reset_session", effect.sessionId, completion);
|
|
@@ -10224,11 +10671,12 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
10224
10671
|
emitErrorAudit(agentId, scope, code, rawMessage) {
|
|
10225
10672
|
if (!this.opts.onBotAuditEvent)
|
|
10226
10673
|
return;
|
|
10674
|
+
const stableCode = /^[A-Za-z0-9_.-]{1,100}$/.test(code) ? code : "runtime_error";
|
|
10227
10675
|
const message = scrubRuntimeErrorDiagnosticText(rawMessage).slice(0, AUDIT_ERROR_MESSAGE_MAX_LEN);
|
|
10228
10676
|
try {
|
|
10229
10677
|
this.opts.onBotAuditEvent(agentId, {
|
|
10230
10678
|
kind: "error",
|
|
10231
|
-
payload: { scope, code, message, model: this.currentModelFor(agentId) }
|
|
10679
|
+
payload: { scope, code: stableCode, message, model: this.currentModelFor(agentId) }
|
|
10232
10680
|
}, {
|
|
10233
10681
|
sessionId: this.liveSessions.get(agentId) ?? null,
|
|
10234
10682
|
launchId: this.launchIds.get(agentId) ?? null
|
|
@@ -10253,18 +10701,18 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
10253
10701
|
return;
|
|
10254
10702
|
}
|
|
10255
10703
|
const sessionSuperseded = owner.superseded;
|
|
10256
|
-
const
|
|
10257
|
-
if (
|
|
10258
|
-
this.emitErrorAudit(agentId, "runtime",
|
|
10704
|
+
const runtimeError = event.type === "session_failed" ? event.error : event.type === "turn_completed" && event.result.outcome === "failed" ? event.result.error : undefined;
|
|
10705
|
+
if (runtimeError !== undefined && !sessionSuperseded) {
|
|
10706
|
+
this.emitErrorAudit(agentId, "runtime", runtimeError.code, runtimeError.message);
|
|
10259
10707
|
if (this.nonCleanEndMarker.get(agentId)?.cause !== "killed_stalled") {
|
|
10260
|
-
this.nonCleanEndMarker.set(agentId, { cause: "runtime_error", detail:
|
|
10708
|
+
this.nonCleanEndMarker.set(agentId, { cause: "runtime_error", detail: runtimeError.message });
|
|
10261
10709
|
}
|
|
10262
10710
|
const agent = this.state.agents[agentId];
|
|
10263
10711
|
if (agent?.resetting && agent.resettingSince !== null && this.now() - agent.resettingSince >= this.state.resetStuckThresholdMs) {
|
|
10264
10712
|
this.log.warn("runtime error during a stuck reset window", {
|
|
10265
10713
|
agentId,
|
|
10266
10714
|
resettingForMs: this.now() - agent.resettingSince,
|
|
10267
|
-
message:
|
|
10715
|
+
message: runtimeError.message
|
|
10268
10716
|
});
|
|
10269
10717
|
}
|
|
10270
10718
|
}
|
|
@@ -10359,9 +10807,9 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
10359
10807
|
case "assistant_message_completed":
|
|
10360
10808
|
return { type: "turn_work", turnId: event.turnId };
|
|
10361
10809
|
case "tool_started":
|
|
10362
|
-
return { type: "turn_tool_started", turnId: event.turnId };
|
|
10810
|
+
return { type: "turn_tool_started", turnId: event.turnId, callId: event.callId };
|
|
10363
10811
|
case "tool_finished":
|
|
10364
|
-
return { type: "turn_tool_finished", turnId: event.turnId };
|
|
10812
|
+
return { type: "turn_tool_finished", turnId: event.turnId, callId: event.callId };
|
|
10365
10813
|
case "compaction_started":
|
|
10366
10814
|
case "compaction_finished":
|
|
10367
10815
|
case "review_started":
|
|
@@ -27136,6 +27584,10 @@ var OwnerDiagnosticReportSchema = exports_external.discriminatedUnion("status",
|
|
|
27136
27584
|
// ../shared/src/community-cli-contract.ts
|
|
27137
27585
|
var DM_SERVER = ".dm";
|
|
27138
27586
|
var CONTROL_HEARTBEAT_CAPABILITY = "control-heartbeat-v1";
|
|
27587
|
+
var AgentInterruptRequestSchema = exports_external.strictObject({
|
|
27588
|
+
type: exports_external.literal("agent:interrupt"),
|
|
27589
|
+
agentId: exports_external.string().min(1).max(128)
|
|
27590
|
+
});
|
|
27139
27591
|
function parseRef(ref) {
|
|
27140
27592
|
if (!ref.startsWith("/"))
|
|
27141
27593
|
throw new Error(`ref must start with "/": ${ref}`);
|
|
@@ -27227,6 +27679,7 @@ var HostCommandSchema = exports_external.discriminatedUnion("type", [
|
|
|
27227
27679
|
type: exports_external.literal("agent:stop"),
|
|
27228
27680
|
agentId: exports_external.string().min(1)
|
|
27229
27681
|
}),
|
|
27682
|
+
AgentInterruptRequestSchema,
|
|
27230
27683
|
exports_external.object({
|
|
27231
27684
|
type: exports_external.literal("agent:reset"),
|
|
27232
27685
|
agentId: exports_external.string().min(1),
|
|
@@ -27625,6 +28078,10 @@ class AgentRouter {
|
|
|
27625
28078
|
}
|
|
27626
28079
|
break;
|
|
27627
28080
|
}
|
|
28081
|
+
case "agent:interrupt": {
|
|
28082
|
+
await this.opts.manager.interrupt(cmd.agentId);
|
|
28083
|
+
break;
|
|
28084
|
+
}
|
|
27628
28085
|
case "agent:stop":
|
|
27629
28086
|
this.log.info("agent:stop received", { agentId: cmd.agentId });
|
|
27630
28087
|
try {
|
|
@@ -27794,10 +28251,11 @@ var DEFAULT_CAPABILITY_RESOLVER = (method, pathname) => {
|
|
|
27794
28251
|
const isMessagesDoor = /\/channels\/[^/]+\/messages(\/|$|\?)/.test(pathname);
|
|
27795
28252
|
if (isMessagesDoor)
|
|
27796
28253
|
return method === "GET" ? "read" : "send";
|
|
28254
|
+
if (/\/messages\/[^/]+\/properties(\/|$|\?)/.test(pathname)) {
|
|
28255
|
+
return method === "GET" ? "read" : "send";
|
|
28256
|
+
}
|
|
27797
28257
|
if (/\/messages\/[^/]+\/reactions\//.test(pathname))
|
|
27798
28258
|
return "send";
|
|
27799
|
-
if ((method === "PUT" || method === "DELETE") && /\/messages\/[^/]+\/marks(\/|$|\?)/.test(pathname))
|
|
27800
|
-
return "send";
|
|
27801
28259
|
if (method === "GET" && /\/users\/me\/marks(\/|$|\?)/.test(pathname))
|
|
27802
28260
|
return "read";
|
|
27803
28261
|
if (method === "GET" && /\/messages\/[^/]+(\?|$)/.test(pathname))
|
|
@@ -27838,6 +28296,12 @@ function isCanonicalChannelScope(channel2) {
|
|
|
27838
28296
|
return false;
|
|
27839
28297
|
}
|
|
27840
28298
|
}
|
|
28299
|
+
function propertyAuditMetadata(method, pathname, requestUrl) {
|
|
28300
|
+
if (method !== "PUT" && method !== "DELETE" || !/^\/api\/community\/messages\/[^/]+\/properties$/.test(pathname))
|
|
28301
|
+
return;
|
|
28302
|
+
const type = new URL2(requestUrl, "http://placeholder").searchParams.get("type");
|
|
28303
|
+
return type === "emoji" || type === "tag" || type === "mark" ? { propertyType: type } : undefined;
|
|
28304
|
+
}
|
|
27841
28305
|
function parseLocalMessageReminderBody(body, agentId) {
|
|
27842
28306
|
let value;
|
|
27843
28307
|
try {
|
|
@@ -27976,7 +28440,12 @@ async function startCredentialProxy(broker, options = {}) {
|
|
|
27976
28440
|
}
|
|
27977
28441
|
if (onProxyRequest) {
|
|
27978
28442
|
try {
|
|
27979
|
-
|
|
28443
|
+
const method = req.method ?? "GET";
|
|
28444
|
+
const metadata = propertyAuditMetadata(method, pathname, req.url ?? pathname);
|
|
28445
|
+
if (metadata)
|
|
28446
|
+
onProxyRequest(reg.agentId, method, pathname, metadata);
|
|
28447
|
+
else
|
|
28448
|
+
onProxyRequest(reg.agentId, method, pathname);
|
|
27980
28449
|
} catch {}
|
|
27981
28450
|
}
|
|
27982
28451
|
const outHeaders = { ...req.headers };
|
|
@@ -28394,6 +28863,7 @@ var ALLOWED_ICON_MIME_TYPES = [
|
|
|
28394
28863
|
];
|
|
28395
28864
|
var MAX_ICON_SOURCE_FILE_SIZE_BYTES = 15 * 1024 * 1024;
|
|
28396
28865
|
// ../shared/src/provider-telemetry.ts
|
|
28866
|
+
var DAILY_TOKEN_USAGE_WINDOW_DAYS = 30;
|
|
28397
28867
|
var safeToken = exports_external.number().int().min(0).max(Number.MAX_SAFE_INTEGER);
|
|
28398
28868
|
var boundedText = exports_external.string().min(1).refine((value) => new TextEncoder().encode(value).length <= 64, { message: "must be at most 64 UTF-8 bytes" });
|
|
28399
28869
|
var DailyUsageMetricSchema = safeToken.nullable();
|
|
@@ -29125,7 +29595,7 @@ var AgentActivityMessageSchema = exports_external.object({
|
|
|
29125
29595
|
state: exports_external.enum(["idle", "starting", "running", "stopping"]),
|
|
29126
29596
|
usageTimeZone: exports_external.string().min(1).max(128).optional(),
|
|
29127
29597
|
usageDay: exports_external.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
|
|
29128
|
-
dailyUsage: exports_external.array(DailyUsageSnapshotSchema).max(
|
|
29598
|
+
dailyUsage: exports_external.array(DailyUsageSnapshotSchema).max(DAILY_TOKEN_USAGE_WINDOW_DAYS).optional(),
|
|
29129
29599
|
quota: ProviderQuotaSnapshotSchema.optional()
|
|
29130
29600
|
});
|
|
29131
29601
|
var AgentTypingMessageSchema = exports_external.object({
|
|
@@ -29289,17 +29759,18 @@ var CommunityAgentNapRequestSchema = exports_external.object({
|
|
|
29289
29759
|
message: "handoff is required"
|
|
29290
29760
|
})
|
|
29291
29761
|
});
|
|
29292
|
-
var
|
|
29762
|
+
var CommunityAgentMessagePropertyRequestSchema = exports_external.object({
|
|
29293
29763
|
channel: exports_external.string().min(1),
|
|
29294
29764
|
seq: CommunityAgentPositiveSeqSchema,
|
|
29295
|
-
|
|
29765
|
+
property: exports_external.unknown()
|
|
29296
29766
|
});
|
|
29297
29767
|
var CommunityAgentFriendRequestSchema = exports_external.object({
|
|
29298
29768
|
username: exports_external.string().min(1)
|
|
29299
29769
|
});
|
|
29300
29770
|
var CommunityAgentListFriendsSchema = exports_external.object({});
|
|
29301
29771
|
var AuditLogCliInvocationPayloadSchema = exports_external.object({
|
|
29302
|
-
subcommand: exports_external.string().min(1)
|
|
29772
|
+
subcommand: exports_external.string().min(1),
|
|
29773
|
+
propertyType: exports_external.enum(["emoji", "tag", "mark"]).optional()
|
|
29303
29774
|
});
|
|
29304
29775
|
var AuditLogToolCallPayloadSchema = exports_external.object({
|
|
29305
29776
|
name: exports_external.string().min(1),
|
|
@@ -32678,7 +33149,7 @@ class MessageReminderScheduler {
|
|
|
32678
33149
|
}
|
|
32679
33150
|
|
|
32680
33151
|
// src/manager/agentDriverHost.ts
|
|
32681
|
-
import { randomUUID as
|
|
33152
|
+
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
32682
33153
|
import { homedir as homedir4 } from "node:os";
|
|
32683
33154
|
import { join as join13 } from "node:path";
|
|
32684
33155
|
|
|
@@ -32834,7 +33305,7 @@ function createDaemonAgentDriverHost(ctx, onRawLine) {
|
|
|
32834
33305
|
onRawLine?.(event.text);
|
|
32835
33306
|
},
|
|
32836
33307
|
now: () => Date.now(),
|
|
32837
|
-
createId: () =>
|
|
33308
|
+
createId: () => randomUUID7()
|
|
32838
33309
|
};
|
|
32839
33310
|
}
|
|
32840
33311
|
|
|
@@ -32913,13 +33384,16 @@ class DaemonSelfSleepScheduler {
|
|
|
32913
33384
|
// src/telemetry/dailyTokenUsage.ts
|
|
32914
33385
|
import { chmod, mkdir, open, readFile as readFile2, rename, rm } from "node:fs/promises";
|
|
32915
33386
|
import { dirname as dirname5, join as join14 } from "node:path";
|
|
32916
|
-
import { randomUUID as
|
|
33387
|
+
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
33388
|
+
var TOKEN_USAGE_WINDOW_DAYS = 30;
|
|
33389
|
+
var TOKEN_USAGE_RECOVERY_DAYS = 2;
|
|
33390
|
+
var TOKEN_USAGE_RETENTION_DAYS = TOKEN_USAGE_WINDOW_DAYS + TOKEN_USAGE_RECOVERY_DAYS;
|
|
32917
33391
|
function oldestRetainedDay(at, timeZone) {
|
|
32918
33392
|
const today = dayKeyInTimeZone(at, timeZone);
|
|
32919
|
-
return calendarDayKeyDaysAgo(today,
|
|
33393
|
+
return calendarDayKeyDaysAgo(today, TOKEN_USAGE_RETENTION_DAYS - 1);
|
|
32920
33394
|
}
|
|
32921
33395
|
function oldestVisibleDay(today) {
|
|
32922
|
-
return calendarDayKeyDaysAgo(today,
|
|
33396
|
+
return calendarDayKeyDaysAgo(today, TOKEN_USAGE_WINDOW_DAYS - 1);
|
|
32923
33397
|
}
|
|
32924
33398
|
function isMetric(value) {
|
|
32925
33399
|
return value === null || typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
@@ -33081,7 +33555,7 @@ class DailyTokenUsageStore {
|
|
|
33081
33555
|
async persist() {
|
|
33082
33556
|
const directory = dirname5(this.filePath);
|
|
33083
33557
|
await mkdir(directory, { recursive: true, mode: 448 });
|
|
33084
|
-
const temporary = `${this.filePath}.${
|
|
33558
|
+
const temporary = `${this.filePath}.${randomUUID8()}.tmp`;
|
|
33085
33559
|
try {
|
|
33086
33560
|
const file2 = await open(temporary, "wx", 384);
|
|
33087
33561
|
try {
|
|
@@ -33163,15 +33637,17 @@ function createRuntimeRawLineTap(args) {
|
|
|
33163
33637
|
}
|
|
33164
33638
|
function deriveAuditLogSubcommand(pathname, method) {
|
|
33165
33639
|
const canonical = pathname.split("?")[0] ?? pathname;
|
|
33166
|
-
if (/^\/api\/community\/messages\/[^/]+\/
|
|
33167
|
-
|
|
33168
|
-
|
|
33640
|
+
if (/^\/api\/community\/messages\/[^/]+\/properties$/.test(canonical)) {
|
|
33641
|
+
if (method === "GET")
|
|
33642
|
+
return "messagePropertyList";
|
|
33169
33643
|
if (method === "PUT")
|
|
33170
|
-
return "
|
|
33644
|
+
return "messagePropertySet";
|
|
33171
33645
|
if (method === "DELETE")
|
|
33172
|
-
return "
|
|
33646
|
+
return "messagePropertyRemove";
|
|
33173
33647
|
return null;
|
|
33174
33648
|
}
|
|
33649
|
+
if (/^\/api\/community\/messages\/[^/]+\/reactions\//.test(canonical))
|
|
33650
|
+
return "reactAdd";
|
|
33175
33651
|
if (method === "GET" && /^\/api\/community\/users\/me\/marks$/.test(canonical))
|
|
33176
33652
|
return "markList";
|
|
33177
33653
|
if (/^\/api\/community\/channels\/[^/]+\/messages\/seq\//.test(canonical))
|
|
@@ -33337,14 +33813,14 @@ async function createDaemon(opts) {
|
|
|
33337
33813
|
log2.warn("inbox pull timeline observation failed", { agentId, reason, contentEncoding });
|
|
33338
33814
|
},
|
|
33339
33815
|
onMessageReminderArm: (input) => reminderSchedulerRef?.arm(input) ?? { armed: false, reason: "reminder_scheduler_unavailable" },
|
|
33340
|
-
onProxyRequest: (agentId, method, pathname) => {
|
|
33816
|
+
onProxyRequest: (agentId, method, pathname, metadata) => {
|
|
33341
33817
|
const subcommand = deriveAuditLogSubcommand(pathname, method);
|
|
33342
33818
|
if (!subcommand)
|
|
33343
33819
|
return;
|
|
33344
33820
|
const context = managerRef?.auditContext(agentId);
|
|
33345
33821
|
emitBotAuditEvent(agentId, {
|
|
33346
33822
|
kind: "cli_invocation",
|
|
33347
|
-
payload: { subcommand }
|
|
33823
|
+
payload: { subcommand, ...metadata ? { propertyType: metadata.propertyType } : {} }
|
|
33348
33824
|
}, context);
|
|
33349
33825
|
emitImplicitTypingStopOnSend({
|
|
33350
33826
|
subcommand,
|