@harmonyos-arkts/opencode-acp 0.0.6 → 0.0.8
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/CHANGELOG.md +5 -0
- package/README.md +6 -1
- package/dist/index.cjs +510 -147
- package/dist/index.cjs.map +4 -4
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -374,15 +374,15 @@ var require_readShebang = __commonJS({
|
|
|
374
374
|
var shebangCommand = require_shebang_command();
|
|
375
375
|
function readShebang(command) {
|
|
376
376
|
const size = 150;
|
|
377
|
-
const
|
|
377
|
+
const buffer2 = Buffer.alloc(size);
|
|
378
378
|
let fd;
|
|
379
379
|
try {
|
|
380
380
|
fd = fs.openSync(command, "r");
|
|
381
|
-
fs.readSync(fd,
|
|
381
|
+
fs.readSync(fd, buffer2, 0, size, 0);
|
|
382
382
|
fs.closeSync(fd);
|
|
383
383
|
} catch (e) {
|
|
384
384
|
}
|
|
385
|
-
return shebangCommand(
|
|
385
|
+
return shebangCommand(buffer2.toString());
|
|
386
386
|
}
|
|
387
387
|
module2.exports = readShebang;
|
|
388
388
|
}
|
|
@@ -15846,7 +15846,7 @@ var createSseClient = ({ onRequest, onSseError, onSseEvent, responseTransformer,
|
|
|
15846
15846
|
if (!response.body)
|
|
15847
15847
|
throw new Error("No body in SSE response");
|
|
15848
15848
|
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
|
|
15849
|
-
let
|
|
15849
|
+
let buffer2 = "";
|
|
15850
15850
|
const abortHandler = () => {
|
|
15851
15851
|
try {
|
|
15852
15852
|
reader.cancel();
|
|
@@ -15859,10 +15859,10 @@ var createSseClient = ({ onRequest, onSseError, onSseEvent, responseTransformer,
|
|
|
15859
15859
|
const { done, value } = await reader.read();
|
|
15860
15860
|
if (done)
|
|
15861
15861
|
break;
|
|
15862
|
-
|
|
15863
|
-
|
|
15864
|
-
const chunks =
|
|
15865
|
-
|
|
15862
|
+
buffer2 += value;
|
|
15863
|
+
buffer2 = buffer2.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
15864
|
+
const chunks = buffer2.split("\n\n");
|
|
15865
|
+
buffer2 = chunks.pop() ?? "";
|
|
15866
15866
|
for (const chunk of chunks) {
|
|
15867
15867
|
const lines = chunk.split("\n");
|
|
15868
15868
|
const dataLines = [];
|
|
@@ -19704,6 +19704,14 @@ var SessionManager = class {
|
|
|
19704
19704
|
getChildren(parentId) {
|
|
19705
19705
|
return [...this.children.get(parentId) ?? []];
|
|
19706
19706
|
}
|
|
19707
|
+
/** All registered session IDs (parent + discovered children). */
|
|
19708
|
+
listSessionIds() {
|
|
19709
|
+
return [...this.sessions.keys()];
|
|
19710
|
+
}
|
|
19711
|
+
/** Top-level sessions only (no parentID) — used for global error broadcast. */
|
|
19712
|
+
listTopLevelSessionIds() {
|
|
19713
|
+
return [...this.sessions.values()].filter((s) => !s.parentID).map((s) => s.id);
|
|
19714
|
+
}
|
|
19707
19715
|
// Model and mode management
|
|
19708
19716
|
getModel(sessionId) {
|
|
19709
19717
|
return this.get(sessionId).model;
|
|
@@ -20290,9 +20298,15 @@ var import_fs = require("fs");
|
|
|
20290
20298
|
var import_path = require("path");
|
|
20291
20299
|
var import_os = require("os");
|
|
20292
20300
|
var LOG_DIR = (0, import_path.join)((0, import_os.homedir)(), ".harmony-acp", "logs");
|
|
20301
|
+
var MAX_LOG_SIZE_BYTES = 5 * 1024 * 1024;
|
|
20302
|
+
var MAX_LOG_AGE_DAYS = 7;
|
|
20293
20303
|
var MAX_LOG_FILES = 30;
|
|
20304
|
+
var FLUSH_INTERVAL_MS = 500;
|
|
20305
|
+
var BUFFER_SIZE_LIMIT = 50;
|
|
20294
20306
|
var logFile = "";
|
|
20295
20307
|
var enabled = false;
|
|
20308
|
+
var buffer = [];
|
|
20309
|
+
var flushTimer = null;
|
|
20296
20310
|
function initLogger() {
|
|
20297
20311
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
20298
20312
|
logFile = (0, import_path.join)(LOG_DIR, `${ts}.log`);
|
|
@@ -20302,24 +20316,72 @@ function initLogger() {
|
|
|
20302
20316
|
}
|
|
20303
20317
|
function setLogEnabled(v) {
|
|
20304
20318
|
enabled = v;
|
|
20319
|
+
if (!v) flush();
|
|
20305
20320
|
}
|
|
20306
20321
|
function cleanOldLogs() {
|
|
20307
20322
|
try {
|
|
20308
|
-
const
|
|
20309
|
-
|
|
20310
|
-
|
|
20311
|
-
|
|
20312
|
-
|
|
20313
|
-
|
|
20323
|
+
const now = Date.now();
|
|
20324
|
+
const maxAgeMs = MAX_LOG_AGE_DAYS * 24 * 60 * 60 * 1e3;
|
|
20325
|
+
const files = (0, import_fs.readdirSync)(LOG_DIR).filter((f) => f.endsWith(".log")).map((f) => {
|
|
20326
|
+
const full = (0, import_path.join)(LOG_DIR, f);
|
|
20327
|
+
try {
|
|
20328
|
+
return { name: f, path: full, mtime: (0, import_fs.statSync)(full).mtime.getTime() };
|
|
20329
|
+
} catch {
|
|
20330
|
+
return null;
|
|
20331
|
+
}
|
|
20332
|
+
}).filter((e) => e !== null);
|
|
20333
|
+
for (const f of files) {
|
|
20334
|
+
if (now - f.mtime > maxAgeMs) {
|
|
20335
|
+
try {
|
|
20336
|
+
(0, import_fs.unlinkSync)(f.path);
|
|
20337
|
+
} catch {
|
|
20338
|
+
}
|
|
20339
|
+
}
|
|
20340
|
+
}
|
|
20341
|
+
const surviving = files.filter((f) => now - f.mtime <= maxAgeMs).sort((a, b) => b.mtime - a.mtime);
|
|
20342
|
+
for (let i = MAX_LOG_FILES; i < surviving.length; i++) {
|
|
20314
20343
|
try {
|
|
20315
|
-
|
|
20316
|
-
unlinkSync(files[i].path);
|
|
20344
|
+
(0, import_fs.unlinkSync)(surviving[i].path);
|
|
20317
20345
|
} catch {
|
|
20318
20346
|
}
|
|
20319
20347
|
}
|
|
20320
20348
|
} catch {
|
|
20321
20349
|
}
|
|
20322
20350
|
}
|
|
20351
|
+
function rotateIfNeeded() {
|
|
20352
|
+
try {
|
|
20353
|
+
const stat = (0, import_fs.statSync)(logFile);
|
|
20354
|
+
if (stat.size >= MAX_LOG_SIZE_BYTES) {
|
|
20355
|
+
const now = /* @__PURE__ */ new Date();
|
|
20356
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
20357
|
+
const ts = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
20358
|
+
const baseName = logFile.replace(/\.log$/, "");
|
|
20359
|
+
(0, import_fs.renameSync)(logFile, `${baseName}.${ts}.log`);
|
|
20360
|
+
}
|
|
20361
|
+
} catch {
|
|
20362
|
+
}
|
|
20363
|
+
}
|
|
20364
|
+
function flush() {
|
|
20365
|
+
if (flushTimer) {
|
|
20366
|
+
clearTimeout(flushTimer);
|
|
20367
|
+
flushTimer = null;
|
|
20368
|
+
}
|
|
20369
|
+
if (buffer.length === 0) return;
|
|
20370
|
+
const data = buffer.join("");
|
|
20371
|
+
buffer = [];
|
|
20372
|
+
try {
|
|
20373
|
+
(0, import_fs.appendFileSync)(logFile, data);
|
|
20374
|
+
rotateIfNeeded();
|
|
20375
|
+
} catch {
|
|
20376
|
+
}
|
|
20377
|
+
}
|
|
20378
|
+
function scheduleFlush() {
|
|
20379
|
+
if (flushTimer) return;
|
|
20380
|
+
flushTimer = setTimeout(() => {
|
|
20381
|
+
flushTimer = null;
|
|
20382
|
+
flush();
|
|
20383
|
+
}, FLUSH_INTERVAL_MS);
|
|
20384
|
+
}
|
|
20323
20385
|
function write(category, action, data) {
|
|
20324
20386
|
if (!enabled) return;
|
|
20325
20387
|
const entry = {
|
|
@@ -20328,9 +20390,11 @@ function write(category, action, data) {
|
|
|
20328
20390
|
action,
|
|
20329
20391
|
...data && { data }
|
|
20330
20392
|
};
|
|
20331
|
-
|
|
20332
|
-
|
|
20333
|
-
|
|
20393
|
+
buffer.push(JSON.stringify(entry) + "\n");
|
|
20394
|
+
if (buffer.length >= BUFFER_SIZE_LIMIT) {
|
|
20395
|
+
flush();
|
|
20396
|
+
} else {
|
|
20397
|
+
scheduleFlush();
|
|
20334
20398
|
}
|
|
20335
20399
|
}
|
|
20336
20400
|
function acpIn(method, params) {
|
|
@@ -20433,13 +20497,105 @@ async function sendToClient(connection, params) {
|
|
|
20433
20497
|
const update = params.update;
|
|
20434
20498
|
acpOut(`sessionUpdate.${updateType}`, {
|
|
20435
20499
|
sessionId: params.sessionId.slice(0, 12),
|
|
20436
|
-
...updateType === "agent_message_chunk" || updateType === "agent_thought_chunk" ? { messageId: update.messageId?.slice(0, 12), delta: update.content?.text?.slice(0, 200) } : updateType === "tool_call" || updateType === "tool_call_update" ? { toolCallId: update.toolCallId, tool: update.title, kind: update.kind, status: update.status } : updateType === "session_info_update" ? { title: update.title, _meta: update._meta } : updateType === "usage_update" ? {
|
|
20500
|
+
...updateType === "agent_message_chunk" || updateType === "agent_thought_chunk" ? { messageId: update.messageId?.slice(0, 12), delta: update.content?.text?.slice(0, 200) } : updateType === "tool_call" || updateType === "tool_call_update" ? { toolCallId: update.toolCallId, tool: update.title, kind: update.kind, status: update.status } : updateType === "session_info_update" ? { title: update.title, _meta: update._meta } : updateType === "usage_update" ? {
|
|
20501
|
+
used: update.used,
|
|
20502
|
+
size: update.size,
|
|
20503
|
+
cost: update.cost,
|
|
20504
|
+
_meta: update._meta,
|
|
20505
|
+
error: update._meta?.error?.message
|
|
20506
|
+
} : update._meta?.error ? { error: update._meta.error?.message ?? update._meta.error } : {}
|
|
20437
20507
|
});
|
|
20438
20508
|
return connection.sessionUpdate(params);
|
|
20439
20509
|
}
|
|
20440
20510
|
|
|
20511
|
+
// src/acp-error.ts
|
|
20512
|
+
function inferCode(err) {
|
|
20513
|
+
const name = typeof err.name === "string" ? err.name : "";
|
|
20514
|
+
if (name === "AI_LoadAPIKeyError" || /auth/i.test(name)) return "auth_required";
|
|
20515
|
+
if (/provider|api|model/i.test(name)) return "provider_error";
|
|
20516
|
+
return "turn_error";
|
|
20517
|
+
}
|
|
20518
|
+
function extractTurnError(raw) {
|
|
20519
|
+
return normalizeError(raw.responseError) ?? normalizeError(raw.messageError);
|
|
20520
|
+
}
|
|
20521
|
+
function normalizeError(err) {
|
|
20522
|
+
if (err == null || err === false) return void 0;
|
|
20523
|
+
if (typeof err === "string" && err.trim()) {
|
|
20524
|
+
return { code: "turn_error", message: err.trim(), display: "inline" };
|
|
20525
|
+
}
|
|
20526
|
+
if (err instanceof Error && err.message.trim()) {
|
|
20527
|
+
return {
|
|
20528
|
+
code: inferCode({ name: err.name }),
|
|
20529
|
+
message: err.message.trim(),
|
|
20530
|
+
name: err.name,
|
|
20531
|
+
cause: err.cause instanceof Error ? err.cause.message : void 0,
|
|
20532
|
+
display: "inline"
|
|
20533
|
+
};
|
|
20534
|
+
}
|
|
20535
|
+
if (typeof err === "object") {
|
|
20536
|
+
const o = err;
|
|
20537
|
+
const message = typeof o.message === "string" && o.message.trim() ? o.message.trim() : typeof o.error === "string" && o.error.trim() ? o.error.trim() : void 0;
|
|
20538
|
+
if (!message) return void 0;
|
|
20539
|
+
const cause = o.cause instanceof Error ? o.cause.message : typeof o.cause === "object" && o.cause !== null && typeof o.cause.message === "string" ? o.cause.message : void 0;
|
|
20540
|
+
return {
|
|
20541
|
+
code: inferCode(o),
|
|
20542
|
+
message,
|
|
20543
|
+
name: typeof o.name === "string" ? o.name : void 0,
|
|
20544
|
+
cause,
|
|
20545
|
+
display: "inline",
|
|
20546
|
+
details: o
|
|
20547
|
+
};
|
|
20548
|
+
}
|
|
20549
|
+
return void 0;
|
|
20550
|
+
}
|
|
20551
|
+
function subscriptionErrorFrom(err) {
|
|
20552
|
+
const normalized = normalizeError(err);
|
|
20553
|
+
if (normalized) {
|
|
20554
|
+
return { ...normalized, code: "subscription_error", display: "banner" };
|
|
20555
|
+
}
|
|
20556
|
+
const message = err instanceof Error ? err.message : "OpenCode event stream disconnected";
|
|
20557
|
+
return {
|
|
20558
|
+
code: "subscription_error",
|
|
20559
|
+
message: message.trim() || "OpenCode event stream disconnected",
|
|
20560
|
+
display: "banner"
|
|
20561
|
+
};
|
|
20562
|
+
}
|
|
20563
|
+
function stopReasonForTurn(error48, finish) {
|
|
20564
|
+
if (!error48) return "end_turn";
|
|
20565
|
+
if (finish === "refusal" || finish === "content_filter") return "refusal";
|
|
20566
|
+
if (/refus/i.test(error48.message) || error48.code === "auth_required") return "refusal";
|
|
20567
|
+
return "end_turn";
|
|
20568
|
+
}
|
|
20569
|
+
async function emitSessionError(connection, sessionId, payload, options) {
|
|
20570
|
+
const display = payload.display ?? "inline";
|
|
20571
|
+
const messageId = options?.messageId ?? payload.messageId ?? `err-${Date.now()}`;
|
|
20572
|
+
if (display !== "silent") {
|
|
20573
|
+
await sendToClient(connection, {
|
|
20574
|
+
sessionId,
|
|
20575
|
+
update: {
|
|
20576
|
+
sessionUpdate: "agent_message_chunk",
|
|
20577
|
+
messageId,
|
|
20578
|
+
content: { type: "text", text: payload.message },
|
|
20579
|
+
_meta: { error: payload }
|
|
20580
|
+
}
|
|
20581
|
+
}).catch(() => {
|
|
20582
|
+
});
|
|
20583
|
+
}
|
|
20584
|
+
if (display === "banner" || display === "inline") {
|
|
20585
|
+
await sendToClient(connection, {
|
|
20586
|
+
sessionId,
|
|
20587
|
+
update: {
|
|
20588
|
+
sessionUpdate: "session_info_update",
|
|
20589
|
+
_meta: { error: payload }
|
|
20590
|
+
}
|
|
20591
|
+
}).catch(() => {
|
|
20592
|
+
});
|
|
20593
|
+
}
|
|
20594
|
+
}
|
|
20595
|
+
|
|
20441
20596
|
// src/event-handler.ts
|
|
20442
|
-
var
|
|
20597
|
+
var SUBSCRIPTION_RETRY_MS = 3e3;
|
|
20598
|
+
var SUBSCRIPTION_ERROR_BROADCAST_MS = 3e4;
|
|
20443
20599
|
var permissionOptions = [
|
|
20444
20600
|
{ optionId: "once", kind: "allow_once", name: "Allow once" },
|
|
20445
20601
|
{ optionId: "always", kind: "allow_always", name: "Always allow" },
|
|
@@ -20463,38 +20619,65 @@ var EventHandler = class {
|
|
|
20463
20619
|
fileDiffStats = /* @__PURE__ */ new Map();
|
|
20464
20620
|
/** AI code change stats per session, keyed by sessionId → filePath → { additions, deletions }. */
|
|
20465
20621
|
aiCodeChangeStats = /* @__PURE__ */ new Map();
|
|
20622
|
+
lastSubscriptionErrorBroadcastAt = 0;
|
|
20623
|
+
turnTimeoutTracker;
|
|
20466
20624
|
constructor(deps) {
|
|
20467
20625
|
this.connection = deps.connection;
|
|
20468
20626
|
this.sdk = deps.sdk;
|
|
20469
20627
|
this.sessionManager = deps.sessionManager;
|
|
20628
|
+
this.turnTimeoutTracker = deps.turnTimeoutTracker;
|
|
20470
20629
|
}
|
|
20471
20630
|
start() {
|
|
20472
20631
|
if (this.started) return;
|
|
20473
20632
|
this.started = true;
|
|
20474
|
-
this.
|
|
20475
|
-
|
|
20476
|
-
console.error("[event-handler] subscription failed:", err);
|
|
20477
|
-
});
|
|
20633
|
+
this.turnTimeoutTracker?.start();
|
|
20634
|
+
void this.runSubscription();
|
|
20478
20635
|
}
|
|
20479
20636
|
stop() {
|
|
20480
20637
|
this.abort.abort();
|
|
20638
|
+
this.turnTimeoutTracker?.stop();
|
|
20481
20639
|
}
|
|
20482
20640
|
async runSubscription() {
|
|
20483
20641
|
while (true) {
|
|
20484
20642
|
if (this.abort.signal.aborted) return;
|
|
20485
|
-
|
|
20486
|
-
|
|
20487
|
-
|
|
20488
|
-
for await (const event of events.stream) {
|
|
20489
|
-
if (this.abort.signal.aborted) return;
|
|
20490
|
-
const payload = event?.payload;
|
|
20491
|
-
if (!payload) continue;
|
|
20492
|
-
await this.handleEvent(payload).catch((err) => {
|
|
20493
|
-
console.error("[event-handler] failed to handle event:", err);
|
|
20643
|
+
try {
|
|
20644
|
+
const events = await this.sdk.global.event({
|
|
20645
|
+
signal: this.abort.signal
|
|
20494
20646
|
});
|
|
20647
|
+
for await (const event of events.stream) {
|
|
20648
|
+
if (this.abort.signal.aborted) return;
|
|
20649
|
+
const payload = event?.payload;
|
|
20650
|
+
if (!payload) continue;
|
|
20651
|
+
await this.handleEvent(payload).catch((err) => {
|
|
20652
|
+
console.error("[event-handler] failed to handle event:", err);
|
|
20653
|
+
});
|
|
20654
|
+
}
|
|
20655
|
+
} catch (err) {
|
|
20656
|
+
if (this.abort.signal.aborted) return;
|
|
20657
|
+
console.error("[event-handler] subscription failed:", err);
|
|
20658
|
+
await this.broadcastSubscriptionError(err);
|
|
20659
|
+
await new Promise((r) => setTimeout(r, SUBSCRIPTION_RETRY_MS));
|
|
20495
20660
|
}
|
|
20496
20661
|
}
|
|
20497
20662
|
}
|
|
20663
|
+
/** Notify all top-level sessions that the OpenCode SSE stream dropped. */
|
|
20664
|
+
async broadcastSubscriptionError(err) {
|
|
20665
|
+
const now = Date.now();
|
|
20666
|
+
if (now - this.lastSubscriptionErrorBroadcastAt < SUBSCRIPTION_ERROR_BROADCAST_MS) {
|
|
20667
|
+
return;
|
|
20668
|
+
}
|
|
20669
|
+
this.lastSubscriptionErrorBroadcastAt = now;
|
|
20670
|
+
const payload = subscriptionErrorFrom(err);
|
|
20671
|
+
sysLog("subscription.error", { message: payload.message });
|
|
20672
|
+
const sessionIds = this.sessionManager.listTopLevelSessionIds();
|
|
20673
|
+
if (sessionIds.length === 0) {
|
|
20674
|
+
return;
|
|
20675
|
+
}
|
|
20676
|
+
for (const sessionId of sessionIds) {
|
|
20677
|
+
await emitSessionError(this.connection, sessionId, payload).catch(() => {
|
|
20678
|
+
});
|
|
20679
|
+
}
|
|
20680
|
+
}
|
|
20498
20681
|
// ─── Resolve session for routing ────────────────────────────────
|
|
20499
20682
|
/**
|
|
20500
20683
|
* Resolve the sessionId to use for ACP routing.
|
|
@@ -20511,6 +20694,7 @@ var EventHandler = class {
|
|
|
20511
20694
|
if (event.type === "server.heartbeat") {
|
|
20512
20695
|
return;
|
|
20513
20696
|
}
|
|
20697
|
+
this.turnTimeoutTracker?.noteEvent(event);
|
|
20514
20698
|
switch (event.type) {
|
|
20515
20699
|
case "session.created":
|
|
20516
20700
|
case "session.updated": {
|
|
@@ -20627,17 +20811,12 @@ var EventHandler = class {
|
|
|
20627
20811
|
const sessionId = resolved.sessionId;
|
|
20628
20812
|
const prev = this.questionQueues.get(q.sessionID) ?? Promise.resolve();
|
|
20629
20813
|
const next = prev.then(async () => {
|
|
20630
|
-
const extResult = await
|
|
20631
|
-
|
|
20632
|
-
|
|
20633
|
-
|
|
20634
|
-
|
|
20635
|
-
|
|
20636
|
-
}),
|
|
20637
|
-
new Promise(
|
|
20638
|
-
(_, reject) => setTimeout(() => reject(new Error("questionAsked timeout")), QUESTION_TIMEOUT_MS)
|
|
20639
|
-
)
|
|
20640
|
-
]).catch((err) => {
|
|
20814
|
+
const extResult = await this.connection.extMethod("questionAsked", {
|
|
20815
|
+
sessionId,
|
|
20816
|
+
questionId: q.id,
|
|
20817
|
+
questions: q.questions,
|
|
20818
|
+
...q.tool && { tool: q.tool }
|
|
20819
|
+
}).catch((err) => {
|
|
20641
20820
|
console.error("[event-handler] extMethod questionAsked failed:", err?.message ?? err);
|
|
20642
20821
|
return void 0;
|
|
20643
20822
|
});
|
|
@@ -20781,6 +20960,7 @@ var EventHandler = class {
|
|
|
20781
20960
|
async handleToolPart(sessionId, part) {
|
|
20782
20961
|
if (!this.toolStarts.has(part.callID)) {
|
|
20783
20962
|
this.toolStarts.add(part.callID);
|
|
20963
|
+
this.turnTimeoutTracker?.onToolCallStart(sessionId);
|
|
20784
20964
|
const session = this.sessionManager.tryGet(sessionId);
|
|
20785
20965
|
if (session?.parentID) {
|
|
20786
20966
|
const tracker = this.childToolCounts.get(sessionId);
|
|
@@ -20843,15 +21023,18 @@ var EventHandler = class {
|
|
|
20843
21023
|
return;
|
|
20844
21024
|
}
|
|
20845
21025
|
case "completed": {
|
|
20846
|
-
this.toolStarts.delete(part.callID)
|
|
21026
|
+
if (this.toolStarts.delete(part.callID)) {
|
|
21027
|
+
this.turnTimeoutTracker?.onToolCallEnd(sessionId);
|
|
21028
|
+
}
|
|
20847
21029
|
this.bashSnapshots.delete(part.callID);
|
|
20848
21030
|
if (part.tool === "task" && part.state.metadata) {
|
|
20849
21031
|
const childSessionId = typeof part.state.metadata["sessionId"] === "string" ? part.state.metadata["sessionId"] : void 0;
|
|
20850
21032
|
if (childSessionId && this.sessionManager.tryGet(childSessionId)) {
|
|
20851
21033
|
const tracker = this.childToolCounts.get(childSessionId);
|
|
20852
|
-
await this.
|
|
21034
|
+
await this.sendChildSessionFinished(
|
|
20853
21035
|
childSessionId,
|
|
20854
21036
|
sessionId,
|
|
21037
|
+
"completed",
|
|
20855
21038
|
tracker?.completed ?? 0,
|
|
20856
21039
|
tracker ? Date.now() - tracker.startTime : 0
|
|
20857
21040
|
).catch(() => {
|
|
@@ -20921,8 +21104,32 @@ var EventHandler = class {
|
|
|
20921
21104
|
return;
|
|
20922
21105
|
}
|
|
20923
21106
|
case "error": {
|
|
20924
|
-
this.toolStarts.delete(part.callID)
|
|
21107
|
+
if (this.toolStarts.delete(part.callID)) {
|
|
21108
|
+
this.turnTimeoutTracker?.onToolCallEnd(sessionId);
|
|
21109
|
+
}
|
|
20925
21110
|
this.bashSnapshots.delete(part.callID);
|
|
21111
|
+
if (part.tool === "task" && part.state.metadata) {
|
|
21112
|
+
const childSessionId = typeof part.state.metadata["sessionId"] === "string" ? part.state.metadata["sessionId"] : void 0;
|
|
21113
|
+
if (childSessionId && this.sessionManager.tryGet(childSessionId)) {
|
|
21114
|
+
const tracker = this.childToolCounts.get(childSessionId);
|
|
21115
|
+
const errText = typeof part.state.error === "string" && part.state.error.trim() ? part.state.error.trim() : "Sub-agent task failed";
|
|
21116
|
+
const payload = {
|
|
21117
|
+
code: "session_error",
|
|
21118
|
+
message: errText,
|
|
21119
|
+
display: "inline"
|
|
21120
|
+
};
|
|
21121
|
+
await this.sendChildSessionFinished(
|
|
21122
|
+
childSessionId,
|
|
21123
|
+
sessionId,
|
|
21124
|
+
"failed",
|
|
21125
|
+
tracker?.completed ?? 0,
|
|
21126
|
+
tracker ? Date.now() - tracker.startTime : 0,
|
|
21127
|
+
payload
|
|
21128
|
+
).catch(() => {
|
|
21129
|
+
});
|
|
21130
|
+
this.childToolCounts.delete(childSessionId);
|
|
21131
|
+
}
|
|
21132
|
+
}
|
|
20926
21133
|
await sendToClient(this.connection, {
|
|
20927
21134
|
sessionId,
|
|
20928
21135
|
update: {
|
|
@@ -20950,9 +21157,9 @@ var EventHandler = class {
|
|
|
20950
21157
|
}
|
|
20951
21158
|
}
|
|
20952
21159
|
/**
|
|
20953
|
-
* Notify the ACP client that a child session
|
|
21160
|
+
* Notify the ACP client that a child session finished (success or failure).
|
|
20954
21161
|
*/
|
|
20955
|
-
async
|
|
21162
|
+
async sendChildSessionFinished(childSessionId, parentSessionId, status, toolCallCount, durationMs, error48) {
|
|
20956
21163
|
const child = this.sessionManager.tryGet(childSessionId);
|
|
20957
21164
|
const title = child?.title ?? "Subagent";
|
|
20958
21165
|
const agentMatch = title.match(/@(\w+)\s+subagent/);
|
|
@@ -20963,14 +21170,15 @@ var EventHandler = class {
|
|
|
20963
21170
|
update: {
|
|
20964
21171
|
sessionUpdate: "session_info_update",
|
|
20965
21172
|
title,
|
|
20966
|
-
status
|
|
21173
|
+
status,
|
|
20967
21174
|
_meta: {
|
|
20968
21175
|
parentSessionId,
|
|
20969
21176
|
isSubagent: true,
|
|
20970
21177
|
toolCallCount,
|
|
20971
21178
|
durationMs,
|
|
20972
21179
|
...agentType && { agentType },
|
|
20973
|
-
...description && { description }
|
|
21180
|
+
...description && { description },
|
|
21181
|
+
...error48 && { error: error48 }
|
|
20974
21182
|
}
|
|
20975
21183
|
}
|
|
20976
21184
|
});
|
|
@@ -21111,6 +21319,140 @@ var AuthProviderManager = class {
|
|
|
21111
21319
|
}
|
|
21112
21320
|
};
|
|
21113
21321
|
|
|
21322
|
+
// src/turn-timeout-tracker.ts
|
|
21323
|
+
var TURN_IDLE_LOG_MS = 9e4;
|
|
21324
|
+
var TURN_IDLE_LOG_MS_WITH_TOOL = 10 * 6e4;
|
|
21325
|
+
var TURN_IDLE_CHECK_INTERVAL_MS = 3e4;
|
|
21326
|
+
function parsePositiveIntEnv(name, fallback) {
|
|
21327
|
+
const raw = process.env[name];
|
|
21328
|
+
if (!raw) return fallback;
|
|
21329
|
+
const n = Number.parseInt(raw, 10);
|
|
21330
|
+
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
21331
|
+
}
|
|
21332
|
+
function turnIdleLogThresholdMs(hasActiveTool) {
|
|
21333
|
+
const base = parsePositiveIntEnv("HARMONY_ACP_TURN_IDLE_LOG_MS", TURN_IDLE_LOG_MS);
|
|
21334
|
+
const withTool = parsePositiveIntEnv("HARMONY_ACP_TURN_IDLE_LOG_MS_WITH_TOOL", TURN_IDLE_LOG_MS_WITH_TOOL);
|
|
21335
|
+
return hasActiveTool ? withTool : base;
|
|
21336
|
+
}
|
|
21337
|
+
function sessionIdFromEvent(event) {
|
|
21338
|
+
const props = event.properties;
|
|
21339
|
+
if (!props) return void 0;
|
|
21340
|
+
switch (event.type) {
|
|
21341
|
+
case "message.part.updated": {
|
|
21342
|
+
const part = props.part;
|
|
21343
|
+
return typeof part?.sessionID === "string" ? part.sessionID : void 0;
|
|
21344
|
+
}
|
|
21345
|
+
case "message.part.delta":
|
|
21346
|
+
return typeof props.sessionID === "string" ? props.sessionID : void 0;
|
|
21347
|
+
case "session.diff":
|
|
21348
|
+
case "session.updated":
|
|
21349
|
+
case "session.created":
|
|
21350
|
+
return typeof props.sessionID === "string" ? props.sessionID : typeof props.info?.id === "string" ? props.info.id : void 0;
|
|
21351
|
+
case "permission.asked": {
|
|
21352
|
+
const permission = props;
|
|
21353
|
+
return typeof permission.sessionID === "string" ? permission.sessionID : void 0;
|
|
21354
|
+
}
|
|
21355
|
+
}
|
|
21356
|
+
return void 0;
|
|
21357
|
+
}
|
|
21358
|
+
var TurnTimeoutTracker = class {
|
|
21359
|
+
constructor(sessionManager) {
|
|
21360
|
+
this.sessionManager = sessionManager;
|
|
21361
|
+
}
|
|
21362
|
+
turns = /* @__PURE__ */ new Map();
|
|
21363
|
+
timer;
|
|
21364
|
+
start() {
|
|
21365
|
+
if (this.timer) return;
|
|
21366
|
+
const interval = parsePositiveIntEnv(
|
|
21367
|
+
"HARMONY_ACP_TURN_IDLE_CHECK_MS",
|
|
21368
|
+
TURN_IDLE_CHECK_INTERVAL_MS
|
|
21369
|
+
);
|
|
21370
|
+
this.timer = setInterval(() => this.checkIdleTurns(), interval);
|
|
21371
|
+
}
|
|
21372
|
+
stop() {
|
|
21373
|
+
if (this.timer) {
|
|
21374
|
+
clearInterval(this.timer);
|
|
21375
|
+
this.timer = void 0;
|
|
21376
|
+
}
|
|
21377
|
+
}
|
|
21378
|
+
rootSessionId(sessionId) {
|
|
21379
|
+
return this.sessionManager.findRootSession(sessionId)?.id ?? sessionId;
|
|
21380
|
+
}
|
|
21381
|
+
onPromptStart(sessionId, meta3) {
|
|
21382
|
+
const root = this.rootSessionId(sessionId);
|
|
21383
|
+
const now = Date.now();
|
|
21384
|
+
this.turns.set(root, {
|
|
21385
|
+
rootSessionId: root,
|
|
21386
|
+
promptStartedAt: now,
|
|
21387
|
+
lastActivityAt: now,
|
|
21388
|
+
activeToolCalls: 0,
|
|
21389
|
+
idleLogged: false,
|
|
21390
|
+
model: meta3?.model
|
|
21391
|
+
});
|
|
21392
|
+
sysLog("turn.prompt_start", { sessionId: root, model: meta3?.model });
|
|
21393
|
+
}
|
|
21394
|
+
onPromptEnd(sessionId) {
|
|
21395
|
+
const root = this.rootSessionId(sessionId);
|
|
21396
|
+
const state = this.turns.get(root);
|
|
21397
|
+
if (state) {
|
|
21398
|
+
sysLog("turn.prompt_end", {
|
|
21399
|
+
sessionId: root,
|
|
21400
|
+
durationMs: Date.now() - state.promptStartedAt
|
|
21401
|
+
});
|
|
21402
|
+
}
|
|
21403
|
+
this.turns.delete(root);
|
|
21404
|
+
}
|
|
21405
|
+
/** SSE / OpenCode activity for a session (child events bump the root turn). */
|
|
21406
|
+
noteActivity(sessionId) {
|
|
21407
|
+
const root = this.rootSessionId(sessionId);
|
|
21408
|
+
const state = this.turns.get(root);
|
|
21409
|
+
if (!state) return;
|
|
21410
|
+
state.lastActivityAt = Date.now();
|
|
21411
|
+
state.idleLogged = false;
|
|
21412
|
+
}
|
|
21413
|
+
onToolCallStart(sessionId) {
|
|
21414
|
+
const root = this.rootSessionId(sessionId);
|
|
21415
|
+
const state = this.turns.get(root);
|
|
21416
|
+
if (!state) return;
|
|
21417
|
+
state.activeToolCalls++;
|
|
21418
|
+
state.lastActivityAt = Date.now();
|
|
21419
|
+
state.idleLogged = false;
|
|
21420
|
+
}
|
|
21421
|
+
onToolCallEnd(sessionId) {
|
|
21422
|
+
const root = this.rootSessionId(sessionId);
|
|
21423
|
+
const state = this.turns.get(root);
|
|
21424
|
+
if (!state) return;
|
|
21425
|
+
state.activeToolCalls = Math.max(0, state.activeToolCalls - 1);
|
|
21426
|
+
state.lastActivityAt = Date.now();
|
|
21427
|
+
state.idleLogged = false;
|
|
21428
|
+
}
|
|
21429
|
+
noteEvent(event) {
|
|
21430
|
+
const sid = sessionIdFromEvent(event);
|
|
21431
|
+
if (sid) {
|
|
21432
|
+
this.noteActivity(sid);
|
|
21433
|
+
}
|
|
21434
|
+
}
|
|
21435
|
+
checkIdleTurns() {
|
|
21436
|
+
const now = Date.now();
|
|
21437
|
+
for (const state of this.turns.values()) {
|
|
21438
|
+
const idleMs = now - state.lastActivityAt;
|
|
21439
|
+
const thresholdMs = turnIdleLogThresholdMs(state.activeToolCalls > 0);
|
|
21440
|
+
if (idleMs < thresholdMs || state.idleLogged) {
|
|
21441
|
+
continue;
|
|
21442
|
+
}
|
|
21443
|
+
state.idleLogged = true;
|
|
21444
|
+
sysLog("turn.idle_timeout", {
|
|
21445
|
+
sessionId: state.rootSessionId,
|
|
21446
|
+
idleMs,
|
|
21447
|
+
thresholdMs,
|
|
21448
|
+
promptAgeMs: now - state.promptStartedAt,
|
|
21449
|
+
activeToolCalls: state.activeToolCalls,
|
|
21450
|
+
model: state.model
|
|
21451
|
+
});
|
|
21452
|
+
}
|
|
21453
|
+
}
|
|
21454
|
+
};
|
|
21455
|
+
|
|
21114
21456
|
// src/agent.ts
|
|
21115
21457
|
function isApiKeyError(err) {
|
|
21116
21458
|
return err instanceof Error && err.name === "AI_LoadAPIKeyError";
|
|
@@ -21123,19 +21465,22 @@ var Agent = class {
|
|
|
21123
21465
|
eventHandler;
|
|
21124
21466
|
authProvider;
|
|
21125
21467
|
mcpManager;
|
|
21468
|
+
turnTimeoutTracker;
|
|
21126
21469
|
constructor(config2) {
|
|
21127
21470
|
this.config = config2;
|
|
21128
21471
|
this.sdk = config2.sdk;
|
|
21129
21472
|
this.sessionManager = new SessionManager(config2.sdk);
|
|
21130
21473
|
this.authProvider = new AuthProviderManager(config2.sdk);
|
|
21131
21474
|
this.mcpManager = new McpManager(config2.sdk);
|
|
21475
|
+
this.turnTimeoutTracker = new TurnTimeoutTracker(this.sessionManager);
|
|
21132
21476
|
}
|
|
21133
21477
|
init(connection) {
|
|
21134
21478
|
this.connection = connection;
|
|
21135
21479
|
this.eventHandler = new EventHandler({
|
|
21136
21480
|
connection,
|
|
21137
21481
|
sdk: this.sdk,
|
|
21138
|
-
sessionManager: this.sessionManager
|
|
21482
|
+
sessionManager: this.sessionManager,
|
|
21483
|
+
turnTimeoutTracker: this.turnTimeoutTracker
|
|
21139
21484
|
});
|
|
21140
21485
|
this.eventHandler.start();
|
|
21141
21486
|
}
|
|
@@ -21478,102 +21823,114 @@ var Agent = class {
|
|
|
21478
21823
|
const agent = session.modeId ?? await this.defaultAgent(directory);
|
|
21479
21824
|
const parts = this.convertPromptParts(params.prompt);
|
|
21480
21825
|
const cmd = this.parseCommand(parts);
|
|
21481
|
-
|
|
21482
|
-
|
|
21483
|
-
|
|
21826
|
+
const modelLabel = `${model.providerID}/${model.modelID}`;
|
|
21827
|
+
ocCall("session.prompt", { sessionID, agent, model: modelLabel });
|
|
21828
|
+
this.turnTimeoutTracker.onPromptStart(sessionID, { model: modelLabel });
|
|
21829
|
+
try {
|
|
21830
|
+
if (!cmd) {
|
|
21831
|
+
const response2 = await this.sdk.session.prompt({
|
|
21832
|
+
sessionID,
|
|
21833
|
+
model: {
|
|
21834
|
+
providerID: model.providerID,
|
|
21835
|
+
modelID: model.modelID
|
|
21836
|
+
},
|
|
21837
|
+
variant: this.sessionManager.getVariant(sessionID),
|
|
21838
|
+
parts,
|
|
21839
|
+
agent,
|
|
21840
|
+
directory
|
|
21841
|
+
});
|
|
21842
|
+
const rawErr2 = response2.error;
|
|
21843
|
+
ocCall("session.prompt.raw", {
|
|
21844
|
+
hasData: !!response2.data,
|
|
21845
|
+
hasError: !!rawErr2,
|
|
21846
|
+
errorName: rawErr2?.name,
|
|
21847
|
+
errorMessage: rawErr2?.message,
|
|
21848
|
+
errorCode: rawErr2?.code ?? rawErr2?.cause?.code,
|
|
21849
|
+
errorCauseName: rawErr2?.cause?.name,
|
|
21850
|
+
errorCauseMessage: rawErr2?.cause?.message
|
|
21851
|
+
});
|
|
21852
|
+
const msg2 = response2.data?.info;
|
|
21853
|
+
return await this.finishPromptTurn(sessionID, directory, { rawErr: rawErr2, msg: msg2 });
|
|
21854
|
+
}
|
|
21855
|
+
const command = await this.sdk.command.list({ directory }).then((x) => x.data?.find((c) => c.name === cmd.name));
|
|
21856
|
+
if (command) {
|
|
21857
|
+
const response2 = await this.sdk.session.command({
|
|
21858
|
+
sessionID,
|
|
21859
|
+
command: command.name,
|
|
21860
|
+
arguments: cmd.args,
|
|
21861
|
+
model: modelLabel,
|
|
21862
|
+
agent,
|
|
21863
|
+
directory
|
|
21864
|
+
});
|
|
21865
|
+
const rawErr2 = response2.error;
|
|
21866
|
+
const msg2 = response2.data?.info;
|
|
21867
|
+
return await this.finishPromptTurn(sessionID, directory, { rawErr: rawErr2, msg: msg2 });
|
|
21868
|
+
}
|
|
21869
|
+
if (cmd.name === "compact") {
|
|
21870
|
+
await this.sdk.session.summarize(
|
|
21871
|
+
{
|
|
21872
|
+
sessionID,
|
|
21873
|
+
directory,
|
|
21874
|
+
providerID: model.providerID,
|
|
21875
|
+
modelID: model.modelID
|
|
21876
|
+
},
|
|
21877
|
+
{ throwOnError: true }
|
|
21878
|
+
).catch(() => {
|
|
21879
|
+
});
|
|
21880
|
+
await this.sendUsageUpdate(sessionID, directory);
|
|
21881
|
+
return {
|
|
21882
|
+
stopReason: "end_turn",
|
|
21883
|
+
_meta: {}
|
|
21884
|
+
};
|
|
21885
|
+
}
|
|
21886
|
+
const response = await this.sdk.session.prompt({
|
|
21484
21887
|
sessionID,
|
|
21485
|
-
model: {
|
|
21486
|
-
providerID: model.providerID,
|
|
21487
|
-
modelID: model.modelID
|
|
21488
|
-
},
|
|
21888
|
+
model: { providerID: model.providerID, modelID: model.modelID },
|
|
21489
21889
|
variant: this.sessionManager.getVariant(sessionID),
|
|
21490
21890
|
parts,
|
|
21491
21891
|
agent,
|
|
21492
21892
|
directory
|
|
21493
21893
|
});
|
|
21494
|
-
const rawErr =
|
|
21495
|
-
|
|
21496
|
-
|
|
21497
|
-
|
|
21498
|
-
|
|
21499
|
-
errorMessage: rawErr?.message,
|
|
21500
|
-
errorCode: rawErr?.code ?? rawErr?.cause?.code,
|
|
21501
|
-
errorCauseName: rawErr?.cause?.name,
|
|
21502
|
-
errorCauseMessage: rawErr?.cause?.message
|
|
21503
|
-
});
|
|
21504
|
-
const msg2 = response2.data?.info;
|
|
21505
|
-
ocCall("session.prompt.response", {
|
|
21506
|
-
messageId: msg2?.id,
|
|
21507
|
-
role: msg2?.role,
|
|
21508
|
-
model: msg2 ? `${msg2.providerID}/${msg2.modelID}` : void 0,
|
|
21509
|
-
tokens: msg2?.tokens,
|
|
21510
|
-
cost: msg2?.cost,
|
|
21511
|
-
finish: msg2?.finish,
|
|
21512
|
-
error: msg2?.error
|
|
21513
|
-
});
|
|
21514
|
-
await this.logMessageContent(sessionID, msg2?.id, directory);
|
|
21515
|
-
await this.sendUsageUpdate(sessionID, directory);
|
|
21516
|
-
acpOut("prompt.response", {
|
|
21517
|
-
stopReason: "end_turn",
|
|
21518
|
-
usage: msg2 ? this.buildUsage(msg2) : void 0
|
|
21519
|
-
});
|
|
21520
|
-
return {
|
|
21521
|
-
stopReason: "end_turn",
|
|
21522
|
-
usage: msg2 ? this.buildUsage(msg2) : void 0,
|
|
21523
|
-
_meta: {}
|
|
21524
|
-
};
|
|
21525
|
-
}
|
|
21526
|
-
const command = await this.sdk.command.list({ directory }).then((x) => x.data?.find((c) => c.name === cmd.name));
|
|
21527
|
-
if (command) {
|
|
21528
|
-
const response2 = await this.sdk.session.command({
|
|
21529
|
-
sessionID,
|
|
21530
|
-
command: command.name,
|
|
21531
|
-
arguments: cmd.args,
|
|
21532
|
-
model: model.providerID + "/" + model.modelID,
|
|
21533
|
-
agent,
|
|
21534
|
-
directory
|
|
21535
|
-
});
|
|
21536
|
-
const msg2 = response2.data?.info;
|
|
21537
|
-
await this.logMessageContent(sessionID, msg2?.id, directory);
|
|
21538
|
-
await this.sendUsageUpdate(sessionID, directory);
|
|
21539
|
-
return {
|
|
21540
|
-
stopReason: "end_turn",
|
|
21541
|
-
usage: msg2 ? this.buildUsage(msg2) : void 0,
|
|
21542
|
-
_meta: {}
|
|
21543
|
-
};
|
|
21544
|
-
}
|
|
21545
|
-
if (cmd.name === "compact") {
|
|
21546
|
-
await this.sdk.session.summarize(
|
|
21547
|
-
{
|
|
21548
|
-
sessionID,
|
|
21549
|
-
directory,
|
|
21550
|
-
providerID: model.providerID,
|
|
21551
|
-
modelID: model.modelID
|
|
21552
|
-
},
|
|
21553
|
-
{ throwOnError: true }
|
|
21554
|
-
).catch(() => {
|
|
21555
|
-
});
|
|
21556
|
-
await this.sendUsageUpdate(sessionID, directory);
|
|
21557
|
-
return {
|
|
21558
|
-
stopReason: "end_turn",
|
|
21559
|
-
_meta: {}
|
|
21560
|
-
};
|
|
21894
|
+
const rawErr = response.error;
|
|
21895
|
+
const msg = response.data?.info;
|
|
21896
|
+
return await this.finishPromptTurn(sessionID, directory, { rawErr, msg });
|
|
21897
|
+
} finally {
|
|
21898
|
+
this.turnTimeoutTracker.onPromptEnd(sessionID);
|
|
21561
21899
|
}
|
|
21562
|
-
|
|
21563
|
-
|
|
21564
|
-
|
|
21565
|
-
|
|
21566
|
-
|
|
21567
|
-
|
|
21568
|
-
|
|
21900
|
+
}
|
|
21901
|
+
/**
|
|
21902
|
+
* Complete a prompt turn: emit streaming errors, usage, and PromptResponse._meta.error.
|
|
21903
|
+
*/
|
|
21904
|
+
async finishPromptTurn(sessionID, directory, opts) {
|
|
21905
|
+
const turnError = extractTurnError({
|
|
21906
|
+
responseError: opts.rawErr,
|
|
21907
|
+
messageError: opts.msg?.error
|
|
21908
|
+
});
|
|
21909
|
+
ocCall("session.prompt.response", {
|
|
21910
|
+
messageId: opts.msg?.id,
|
|
21911
|
+
role: opts.msg?.role,
|
|
21912
|
+
model: opts.msg ? `${opts.msg.providerID}/${opts.msg.modelID}` : void 0,
|
|
21913
|
+
tokens: opts.msg?.tokens,
|
|
21914
|
+
cost: opts.msg?.cost,
|
|
21915
|
+
finish: opts.msg?.finish,
|
|
21916
|
+
error: turnError?.message
|
|
21917
|
+
});
|
|
21918
|
+
if (turnError) {
|
|
21919
|
+
await emitSessionError(this.connection, sessionID, turnError, { messageId: opts.msg?.id });
|
|
21920
|
+
}
|
|
21921
|
+
await this.logMessageContent(sessionID, opts.msg?.id, directory);
|
|
21922
|
+
await this.sendUsageUpdate(sessionID, directory, turnError);
|
|
21923
|
+
const stopReason = stopReasonForTurn(turnError, opts.msg?.finish);
|
|
21924
|
+
const usage = opts.msg ? this.buildUsage(opts.msg) : void 0;
|
|
21925
|
+
acpOut("prompt.response", {
|
|
21926
|
+
stopReason,
|
|
21927
|
+
usage,
|
|
21928
|
+
error: turnError?.message
|
|
21569
21929
|
});
|
|
21570
|
-
const msg = response.data?.info;
|
|
21571
|
-
await this.logMessageContent(sessionID, msg?.id, directory);
|
|
21572
|
-
await this.sendUsageUpdate(sessionID, directory);
|
|
21573
21930
|
return {
|
|
21574
|
-
stopReason
|
|
21575
|
-
usage
|
|
21576
|
-
_meta: {}
|
|
21931
|
+
stopReason,
|
|
21932
|
+
usage,
|
|
21933
|
+
_meta: turnError ? { error: turnError } : {}
|
|
21577
21934
|
};
|
|
21578
21935
|
}
|
|
21579
21936
|
async cancel(params) {
|
|
@@ -21584,6 +21941,7 @@ var Agent = class {
|
|
|
21584
21941
|
sessionID: params.sessionId,
|
|
21585
21942
|
directory: session.cwd
|
|
21586
21943
|
});
|
|
21944
|
+
this.turnTimeoutTracker.onPromptEnd(params.sessionId);
|
|
21587
21945
|
}
|
|
21588
21946
|
// ─── Configuration ────────────────────────────────────────────────
|
|
21589
21947
|
async setSessionMode(params) {
|
|
@@ -22028,7 +22386,7 @@ var Agent = class {
|
|
|
22028
22386
|
break;
|
|
22029
22387
|
}
|
|
22030
22388
|
}
|
|
22031
|
-
async sendUsageUpdate(sessionId, directory) {
|
|
22389
|
+
async sendUsageUpdate(sessionId, directory, turnError) {
|
|
22032
22390
|
const messages = await this.sdk.session.messages({ sessionID: sessionId, directory }).then((x) => x.data).catch(() => void 0);
|
|
22033
22391
|
if (!messages) return;
|
|
22034
22392
|
const assistantMessages = messages.filter(
|
|
@@ -22059,6 +22417,9 @@ var Agent = class {
|
|
|
22059
22417
|
}
|
|
22060
22418
|
this.aggregateFileDiffStats(sessionId, children, _meta);
|
|
22061
22419
|
this.aggregateAICodeChangeStats(sessionId, children, _meta);
|
|
22420
|
+
if (turnError) {
|
|
22421
|
+
_meta.error = turnError;
|
|
22422
|
+
}
|
|
22062
22423
|
await sendToClient(this.connection, {
|
|
22063
22424
|
sessionId,
|
|
22064
22425
|
update: {
|
|
@@ -22202,13 +22563,15 @@ var Agent = class {
|
|
|
22202
22563
|
}
|
|
22203
22564
|
}
|
|
22204
22565
|
buildUsage(msg) {
|
|
22566
|
+
const tokens = msg.tokens;
|
|
22567
|
+
if (!tokens) return void 0;
|
|
22205
22568
|
return {
|
|
22206
|
-
totalTokens:
|
|
22207
|
-
inputTokens:
|
|
22208
|
-
outputTokens:
|
|
22209
|
-
thoughtTokens:
|
|
22210
|
-
cachedReadTokens:
|
|
22211
|
-
cachedWriteTokens:
|
|
22569
|
+
totalTokens: tokens.input + tokens.output + tokens.reasoning + (tokens.cache?.read ?? 0) + (tokens.cache?.write ?? 0),
|
|
22570
|
+
inputTokens: tokens.input,
|
|
22571
|
+
outputTokens: tokens.output,
|
|
22572
|
+
thoughtTokens: tokens.reasoning || void 0,
|
|
22573
|
+
cachedReadTokens: tokens.cache?.read || void 0,
|
|
22574
|
+
cachedWriteTokens: tokens.cache?.write || void 0
|
|
22212
22575
|
};
|
|
22213
22576
|
}
|
|
22214
22577
|
};
|