@harmonyos-arkts/opencode-acp 0.0.6 → 0.0.7
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/dist/index.cjs +429 -116
- package/dist/index.cjs.map +4 -4
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -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;
|
|
@@ -20433,12 +20441,105 @@ async function sendToClient(connection, params) {
|
|
|
20433
20441
|
const update = params.update;
|
|
20434
20442
|
acpOut(`sessionUpdate.${updateType}`, {
|
|
20435
20443
|
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" ? {
|
|
20444
|
+
...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" ? {
|
|
20445
|
+
used: update.used,
|
|
20446
|
+
size: update.size,
|
|
20447
|
+
cost: update.cost,
|
|
20448
|
+
_meta: update._meta,
|
|
20449
|
+
error: update._meta?.error?.message
|
|
20450
|
+
} : update._meta?.error ? { error: update._meta.error?.message ?? update._meta.error } : {}
|
|
20437
20451
|
});
|
|
20438
20452
|
return connection.sessionUpdate(params);
|
|
20439
20453
|
}
|
|
20440
20454
|
|
|
20455
|
+
// src/acp-error.ts
|
|
20456
|
+
function inferCode(err) {
|
|
20457
|
+
const name = typeof err.name === "string" ? err.name : "";
|
|
20458
|
+
if (name === "AI_LoadAPIKeyError" || /auth/i.test(name)) return "auth_required";
|
|
20459
|
+
if (/provider|api|model/i.test(name)) return "provider_error";
|
|
20460
|
+
return "turn_error";
|
|
20461
|
+
}
|
|
20462
|
+
function extractTurnError(raw) {
|
|
20463
|
+
return normalizeError(raw.responseError) ?? normalizeError(raw.messageError);
|
|
20464
|
+
}
|
|
20465
|
+
function normalizeError(err) {
|
|
20466
|
+
if (err == null || err === false) return void 0;
|
|
20467
|
+
if (typeof err === "string" && err.trim()) {
|
|
20468
|
+
return { code: "turn_error", message: err.trim(), display: "inline" };
|
|
20469
|
+
}
|
|
20470
|
+
if (err instanceof Error && err.message.trim()) {
|
|
20471
|
+
return {
|
|
20472
|
+
code: inferCode({ name: err.name }),
|
|
20473
|
+
message: err.message.trim(),
|
|
20474
|
+
name: err.name,
|
|
20475
|
+
cause: err.cause instanceof Error ? err.cause.message : void 0,
|
|
20476
|
+
display: "inline"
|
|
20477
|
+
};
|
|
20478
|
+
}
|
|
20479
|
+
if (typeof err === "object") {
|
|
20480
|
+
const o = err;
|
|
20481
|
+
const message = typeof o.message === "string" && o.message.trim() ? o.message.trim() : typeof o.error === "string" && o.error.trim() ? o.error.trim() : void 0;
|
|
20482
|
+
if (!message) return void 0;
|
|
20483
|
+
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;
|
|
20484
|
+
return {
|
|
20485
|
+
code: inferCode(o),
|
|
20486
|
+
message,
|
|
20487
|
+
name: typeof o.name === "string" ? o.name : void 0,
|
|
20488
|
+
cause,
|
|
20489
|
+
display: "inline",
|
|
20490
|
+
details: o
|
|
20491
|
+
};
|
|
20492
|
+
}
|
|
20493
|
+
return void 0;
|
|
20494
|
+
}
|
|
20495
|
+
function subscriptionErrorFrom(err) {
|
|
20496
|
+
const normalized = normalizeError(err);
|
|
20497
|
+
if (normalized) {
|
|
20498
|
+
return { ...normalized, code: "subscription_error", display: "banner" };
|
|
20499
|
+
}
|
|
20500
|
+
const message = err instanceof Error ? err.message : "OpenCode event stream disconnected";
|
|
20501
|
+
return {
|
|
20502
|
+
code: "subscription_error",
|
|
20503
|
+
message: message.trim() || "OpenCode event stream disconnected",
|
|
20504
|
+
display: "banner"
|
|
20505
|
+
};
|
|
20506
|
+
}
|
|
20507
|
+
function stopReasonForTurn(error48, finish) {
|
|
20508
|
+
if (!error48) return "end_turn";
|
|
20509
|
+
if (finish === "refusal" || finish === "content_filter") return "refusal";
|
|
20510
|
+
if (/refus/i.test(error48.message) || error48.code === "auth_required") return "refusal";
|
|
20511
|
+
return "end_turn";
|
|
20512
|
+
}
|
|
20513
|
+
async function emitSessionError(connection, sessionId, payload, options) {
|
|
20514
|
+
const display = payload.display ?? "inline";
|
|
20515
|
+
const messageId = options?.messageId ?? payload.messageId ?? `err-${Date.now()}`;
|
|
20516
|
+
if (display !== "silent") {
|
|
20517
|
+
await sendToClient(connection, {
|
|
20518
|
+
sessionId,
|
|
20519
|
+
update: {
|
|
20520
|
+
sessionUpdate: "agent_message_chunk",
|
|
20521
|
+
messageId,
|
|
20522
|
+
content: { type: "text", text: payload.message },
|
|
20523
|
+
_meta: { error: payload }
|
|
20524
|
+
}
|
|
20525
|
+
}).catch(() => {
|
|
20526
|
+
});
|
|
20527
|
+
}
|
|
20528
|
+
if (display === "banner" || display === "inline") {
|
|
20529
|
+
await sendToClient(connection, {
|
|
20530
|
+
sessionId,
|
|
20531
|
+
update: {
|
|
20532
|
+
sessionUpdate: "session_info_update",
|
|
20533
|
+
_meta: { error: payload }
|
|
20534
|
+
}
|
|
20535
|
+
}).catch(() => {
|
|
20536
|
+
});
|
|
20537
|
+
}
|
|
20538
|
+
}
|
|
20539
|
+
|
|
20441
20540
|
// src/event-handler.ts
|
|
20541
|
+
var SUBSCRIPTION_RETRY_MS = 3e3;
|
|
20542
|
+
var SUBSCRIPTION_ERROR_BROADCAST_MS = 3e4;
|
|
20442
20543
|
var QUESTION_TIMEOUT_MS = 3e5;
|
|
20443
20544
|
var permissionOptions = [
|
|
20444
20545
|
{ optionId: "once", kind: "allow_once", name: "Allow once" },
|
|
@@ -20463,38 +20564,65 @@ var EventHandler = class {
|
|
|
20463
20564
|
fileDiffStats = /* @__PURE__ */ new Map();
|
|
20464
20565
|
/** AI code change stats per session, keyed by sessionId → filePath → { additions, deletions }. */
|
|
20465
20566
|
aiCodeChangeStats = /* @__PURE__ */ new Map();
|
|
20567
|
+
lastSubscriptionErrorBroadcastAt = 0;
|
|
20568
|
+
turnTimeoutTracker;
|
|
20466
20569
|
constructor(deps) {
|
|
20467
20570
|
this.connection = deps.connection;
|
|
20468
20571
|
this.sdk = deps.sdk;
|
|
20469
20572
|
this.sessionManager = deps.sessionManager;
|
|
20573
|
+
this.turnTimeoutTracker = deps.turnTimeoutTracker;
|
|
20470
20574
|
}
|
|
20471
20575
|
start() {
|
|
20472
20576
|
if (this.started) return;
|
|
20473
20577
|
this.started = true;
|
|
20474
|
-
this.
|
|
20475
|
-
|
|
20476
|
-
console.error("[event-handler] subscription failed:", err);
|
|
20477
|
-
});
|
|
20578
|
+
this.turnTimeoutTracker?.start();
|
|
20579
|
+
void this.runSubscription();
|
|
20478
20580
|
}
|
|
20479
20581
|
stop() {
|
|
20480
20582
|
this.abort.abort();
|
|
20583
|
+
this.turnTimeoutTracker?.stop();
|
|
20481
20584
|
}
|
|
20482
20585
|
async runSubscription() {
|
|
20483
20586
|
while (true) {
|
|
20484
20587
|
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);
|
|
20588
|
+
try {
|
|
20589
|
+
const events = await this.sdk.global.event({
|
|
20590
|
+
signal: this.abort.signal
|
|
20494
20591
|
});
|
|
20592
|
+
for await (const event of events.stream) {
|
|
20593
|
+
if (this.abort.signal.aborted) return;
|
|
20594
|
+
const payload = event?.payload;
|
|
20595
|
+
if (!payload) continue;
|
|
20596
|
+
await this.handleEvent(payload).catch((err) => {
|
|
20597
|
+
console.error("[event-handler] failed to handle event:", err);
|
|
20598
|
+
});
|
|
20599
|
+
}
|
|
20600
|
+
} catch (err) {
|
|
20601
|
+
if (this.abort.signal.aborted) return;
|
|
20602
|
+
console.error("[event-handler] subscription failed:", err);
|
|
20603
|
+
await this.broadcastSubscriptionError(err);
|
|
20604
|
+
await new Promise((r) => setTimeout(r, SUBSCRIPTION_RETRY_MS));
|
|
20495
20605
|
}
|
|
20496
20606
|
}
|
|
20497
20607
|
}
|
|
20608
|
+
/** Notify all top-level sessions that the OpenCode SSE stream dropped. */
|
|
20609
|
+
async broadcastSubscriptionError(err) {
|
|
20610
|
+
const now = Date.now();
|
|
20611
|
+
if (now - this.lastSubscriptionErrorBroadcastAt < SUBSCRIPTION_ERROR_BROADCAST_MS) {
|
|
20612
|
+
return;
|
|
20613
|
+
}
|
|
20614
|
+
this.lastSubscriptionErrorBroadcastAt = now;
|
|
20615
|
+
const payload = subscriptionErrorFrom(err);
|
|
20616
|
+
sysLog("subscription.error", { message: payload.message });
|
|
20617
|
+
const sessionIds = this.sessionManager.listTopLevelSessionIds();
|
|
20618
|
+
if (sessionIds.length === 0) {
|
|
20619
|
+
return;
|
|
20620
|
+
}
|
|
20621
|
+
for (const sessionId of sessionIds) {
|
|
20622
|
+
await emitSessionError(this.connection, sessionId, payload).catch(() => {
|
|
20623
|
+
});
|
|
20624
|
+
}
|
|
20625
|
+
}
|
|
20498
20626
|
// ─── Resolve session for routing ────────────────────────────────
|
|
20499
20627
|
/**
|
|
20500
20628
|
* Resolve the sessionId to use for ACP routing.
|
|
@@ -20511,6 +20639,7 @@ var EventHandler = class {
|
|
|
20511
20639
|
if (event.type === "server.heartbeat") {
|
|
20512
20640
|
return;
|
|
20513
20641
|
}
|
|
20642
|
+
this.turnTimeoutTracker?.noteEvent(event);
|
|
20514
20643
|
switch (event.type) {
|
|
20515
20644
|
case "session.created":
|
|
20516
20645
|
case "session.updated": {
|
|
@@ -20781,6 +20910,7 @@ var EventHandler = class {
|
|
|
20781
20910
|
async handleToolPart(sessionId, part) {
|
|
20782
20911
|
if (!this.toolStarts.has(part.callID)) {
|
|
20783
20912
|
this.toolStarts.add(part.callID);
|
|
20913
|
+
this.turnTimeoutTracker?.onToolCallStart(sessionId);
|
|
20784
20914
|
const session = this.sessionManager.tryGet(sessionId);
|
|
20785
20915
|
if (session?.parentID) {
|
|
20786
20916
|
const tracker = this.childToolCounts.get(sessionId);
|
|
@@ -20843,15 +20973,18 @@ var EventHandler = class {
|
|
|
20843
20973
|
return;
|
|
20844
20974
|
}
|
|
20845
20975
|
case "completed": {
|
|
20846
|
-
this.toolStarts.delete(part.callID)
|
|
20976
|
+
if (this.toolStarts.delete(part.callID)) {
|
|
20977
|
+
this.turnTimeoutTracker?.onToolCallEnd(sessionId);
|
|
20978
|
+
}
|
|
20847
20979
|
this.bashSnapshots.delete(part.callID);
|
|
20848
20980
|
if (part.tool === "task" && part.state.metadata) {
|
|
20849
20981
|
const childSessionId = typeof part.state.metadata["sessionId"] === "string" ? part.state.metadata["sessionId"] : void 0;
|
|
20850
20982
|
if (childSessionId && this.sessionManager.tryGet(childSessionId)) {
|
|
20851
20983
|
const tracker = this.childToolCounts.get(childSessionId);
|
|
20852
|
-
await this.
|
|
20984
|
+
await this.sendChildSessionFinished(
|
|
20853
20985
|
childSessionId,
|
|
20854
20986
|
sessionId,
|
|
20987
|
+
"completed",
|
|
20855
20988
|
tracker?.completed ?? 0,
|
|
20856
20989
|
tracker ? Date.now() - tracker.startTime : 0
|
|
20857
20990
|
).catch(() => {
|
|
@@ -20921,8 +21054,32 @@ var EventHandler = class {
|
|
|
20921
21054
|
return;
|
|
20922
21055
|
}
|
|
20923
21056
|
case "error": {
|
|
20924
|
-
this.toolStarts.delete(part.callID)
|
|
21057
|
+
if (this.toolStarts.delete(part.callID)) {
|
|
21058
|
+
this.turnTimeoutTracker?.onToolCallEnd(sessionId);
|
|
21059
|
+
}
|
|
20925
21060
|
this.bashSnapshots.delete(part.callID);
|
|
21061
|
+
if (part.tool === "task" && part.state.metadata) {
|
|
21062
|
+
const childSessionId = typeof part.state.metadata["sessionId"] === "string" ? part.state.metadata["sessionId"] : void 0;
|
|
21063
|
+
if (childSessionId && this.sessionManager.tryGet(childSessionId)) {
|
|
21064
|
+
const tracker = this.childToolCounts.get(childSessionId);
|
|
21065
|
+
const errText = typeof part.state.error === "string" && part.state.error.trim() ? part.state.error.trim() : "Sub-agent task failed";
|
|
21066
|
+
const payload = {
|
|
21067
|
+
code: "session_error",
|
|
21068
|
+
message: errText,
|
|
21069
|
+
display: "inline"
|
|
21070
|
+
};
|
|
21071
|
+
await this.sendChildSessionFinished(
|
|
21072
|
+
childSessionId,
|
|
21073
|
+
sessionId,
|
|
21074
|
+
"failed",
|
|
21075
|
+
tracker?.completed ?? 0,
|
|
21076
|
+
tracker ? Date.now() - tracker.startTime : 0,
|
|
21077
|
+
payload
|
|
21078
|
+
).catch(() => {
|
|
21079
|
+
});
|
|
21080
|
+
this.childToolCounts.delete(childSessionId);
|
|
21081
|
+
}
|
|
21082
|
+
}
|
|
20926
21083
|
await sendToClient(this.connection, {
|
|
20927
21084
|
sessionId,
|
|
20928
21085
|
update: {
|
|
@@ -20950,9 +21107,9 @@ var EventHandler = class {
|
|
|
20950
21107
|
}
|
|
20951
21108
|
}
|
|
20952
21109
|
/**
|
|
20953
|
-
* Notify the ACP client that a child session
|
|
21110
|
+
* Notify the ACP client that a child session finished (success or failure).
|
|
20954
21111
|
*/
|
|
20955
|
-
async
|
|
21112
|
+
async sendChildSessionFinished(childSessionId, parentSessionId, status, toolCallCount, durationMs, error48) {
|
|
20956
21113
|
const child = this.sessionManager.tryGet(childSessionId);
|
|
20957
21114
|
const title = child?.title ?? "Subagent";
|
|
20958
21115
|
const agentMatch = title.match(/@(\w+)\s+subagent/);
|
|
@@ -20963,14 +21120,15 @@ var EventHandler = class {
|
|
|
20963
21120
|
update: {
|
|
20964
21121
|
sessionUpdate: "session_info_update",
|
|
20965
21122
|
title,
|
|
20966
|
-
status
|
|
21123
|
+
status,
|
|
20967
21124
|
_meta: {
|
|
20968
21125
|
parentSessionId,
|
|
20969
21126
|
isSubagent: true,
|
|
20970
21127
|
toolCallCount,
|
|
20971
21128
|
durationMs,
|
|
20972
21129
|
...agentType && { agentType },
|
|
20973
|
-
...description && { description }
|
|
21130
|
+
...description && { description },
|
|
21131
|
+
...error48 && { error: error48 }
|
|
20974
21132
|
}
|
|
20975
21133
|
}
|
|
20976
21134
|
});
|
|
@@ -21111,6 +21269,140 @@ var AuthProviderManager = class {
|
|
|
21111
21269
|
}
|
|
21112
21270
|
};
|
|
21113
21271
|
|
|
21272
|
+
// src/turn-timeout-tracker.ts
|
|
21273
|
+
var TURN_IDLE_LOG_MS = 9e4;
|
|
21274
|
+
var TURN_IDLE_LOG_MS_WITH_TOOL = 10 * 6e4;
|
|
21275
|
+
var TURN_IDLE_CHECK_INTERVAL_MS = 3e4;
|
|
21276
|
+
function parsePositiveIntEnv(name, fallback) {
|
|
21277
|
+
const raw = process.env[name];
|
|
21278
|
+
if (!raw) return fallback;
|
|
21279
|
+
const n = Number.parseInt(raw, 10);
|
|
21280
|
+
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
21281
|
+
}
|
|
21282
|
+
function turnIdleLogThresholdMs(hasActiveTool) {
|
|
21283
|
+
const base = parsePositiveIntEnv("HARMONY_ACP_TURN_IDLE_LOG_MS", TURN_IDLE_LOG_MS);
|
|
21284
|
+
const withTool = parsePositiveIntEnv("HARMONY_ACP_TURN_IDLE_LOG_MS_WITH_TOOL", TURN_IDLE_LOG_MS_WITH_TOOL);
|
|
21285
|
+
return hasActiveTool ? withTool : base;
|
|
21286
|
+
}
|
|
21287
|
+
function sessionIdFromEvent(event) {
|
|
21288
|
+
const props = event.properties;
|
|
21289
|
+
if (!props) return void 0;
|
|
21290
|
+
switch (event.type) {
|
|
21291
|
+
case "message.part.updated": {
|
|
21292
|
+
const part = props.part;
|
|
21293
|
+
return typeof part?.sessionID === "string" ? part.sessionID : void 0;
|
|
21294
|
+
}
|
|
21295
|
+
case "message.part.delta":
|
|
21296
|
+
return typeof props.sessionID === "string" ? props.sessionID : void 0;
|
|
21297
|
+
case "session.diff":
|
|
21298
|
+
case "session.updated":
|
|
21299
|
+
case "session.created":
|
|
21300
|
+
return typeof props.sessionID === "string" ? props.sessionID : typeof props.info?.id === "string" ? props.info.id : void 0;
|
|
21301
|
+
case "permission.asked": {
|
|
21302
|
+
const permission = props;
|
|
21303
|
+
return typeof permission.sessionID === "string" ? permission.sessionID : void 0;
|
|
21304
|
+
}
|
|
21305
|
+
}
|
|
21306
|
+
return void 0;
|
|
21307
|
+
}
|
|
21308
|
+
var TurnTimeoutTracker = class {
|
|
21309
|
+
constructor(sessionManager) {
|
|
21310
|
+
this.sessionManager = sessionManager;
|
|
21311
|
+
}
|
|
21312
|
+
turns = /* @__PURE__ */ new Map();
|
|
21313
|
+
timer;
|
|
21314
|
+
start() {
|
|
21315
|
+
if (this.timer) return;
|
|
21316
|
+
const interval = parsePositiveIntEnv(
|
|
21317
|
+
"HARMONY_ACP_TURN_IDLE_CHECK_MS",
|
|
21318
|
+
TURN_IDLE_CHECK_INTERVAL_MS
|
|
21319
|
+
);
|
|
21320
|
+
this.timer = setInterval(() => this.checkIdleTurns(), interval);
|
|
21321
|
+
}
|
|
21322
|
+
stop() {
|
|
21323
|
+
if (this.timer) {
|
|
21324
|
+
clearInterval(this.timer);
|
|
21325
|
+
this.timer = void 0;
|
|
21326
|
+
}
|
|
21327
|
+
}
|
|
21328
|
+
rootSessionId(sessionId) {
|
|
21329
|
+
return this.sessionManager.findRootSession(sessionId)?.id ?? sessionId;
|
|
21330
|
+
}
|
|
21331
|
+
onPromptStart(sessionId, meta3) {
|
|
21332
|
+
const root = this.rootSessionId(sessionId);
|
|
21333
|
+
const now = Date.now();
|
|
21334
|
+
this.turns.set(root, {
|
|
21335
|
+
rootSessionId: root,
|
|
21336
|
+
promptStartedAt: now,
|
|
21337
|
+
lastActivityAt: now,
|
|
21338
|
+
activeToolCalls: 0,
|
|
21339
|
+
idleLogged: false,
|
|
21340
|
+
model: meta3?.model
|
|
21341
|
+
});
|
|
21342
|
+
sysLog("turn.prompt_start", { sessionId: root, model: meta3?.model });
|
|
21343
|
+
}
|
|
21344
|
+
onPromptEnd(sessionId) {
|
|
21345
|
+
const root = this.rootSessionId(sessionId);
|
|
21346
|
+
const state = this.turns.get(root);
|
|
21347
|
+
if (state) {
|
|
21348
|
+
sysLog("turn.prompt_end", {
|
|
21349
|
+
sessionId: root,
|
|
21350
|
+
durationMs: Date.now() - state.promptStartedAt
|
|
21351
|
+
});
|
|
21352
|
+
}
|
|
21353
|
+
this.turns.delete(root);
|
|
21354
|
+
}
|
|
21355
|
+
/** SSE / OpenCode activity for a session (child events bump the root turn). */
|
|
21356
|
+
noteActivity(sessionId) {
|
|
21357
|
+
const root = this.rootSessionId(sessionId);
|
|
21358
|
+
const state = this.turns.get(root);
|
|
21359
|
+
if (!state) return;
|
|
21360
|
+
state.lastActivityAt = Date.now();
|
|
21361
|
+
state.idleLogged = false;
|
|
21362
|
+
}
|
|
21363
|
+
onToolCallStart(sessionId) {
|
|
21364
|
+
const root = this.rootSessionId(sessionId);
|
|
21365
|
+
const state = this.turns.get(root);
|
|
21366
|
+
if (!state) return;
|
|
21367
|
+
state.activeToolCalls++;
|
|
21368
|
+
state.lastActivityAt = Date.now();
|
|
21369
|
+
state.idleLogged = false;
|
|
21370
|
+
}
|
|
21371
|
+
onToolCallEnd(sessionId) {
|
|
21372
|
+
const root = this.rootSessionId(sessionId);
|
|
21373
|
+
const state = this.turns.get(root);
|
|
21374
|
+
if (!state) return;
|
|
21375
|
+
state.activeToolCalls = Math.max(0, state.activeToolCalls - 1);
|
|
21376
|
+
state.lastActivityAt = Date.now();
|
|
21377
|
+
state.idleLogged = false;
|
|
21378
|
+
}
|
|
21379
|
+
noteEvent(event) {
|
|
21380
|
+
const sid = sessionIdFromEvent(event);
|
|
21381
|
+
if (sid) {
|
|
21382
|
+
this.noteActivity(sid);
|
|
21383
|
+
}
|
|
21384
|
+
}
|
|
21385
|
+
checkIdleTurns() {
|
|
21386
|
+
const now = Date.now();
|
|
21387
|
+
for (const state of this.turns.values()) {
|
|
21388
|
+
const idleMs = now - state.lastActivityAt;
|
|
21389
|
+
const thresholdMs = turnIdleLogThresholdMs(state.activeToolCalls > 0);
|
|
21390
|
+
if (idleMs < thresholdMs || state.idleLogged) {
|
|
21391
|
+
continue;
|
|
21392
|
+
}
|
|
21393
|
+
state.idleLogged = true;
|
|
21394
|
+
sysLog("turn.idle_timeout", {
|
|
21395
|
+
sessionId: state.rootSessionId,
|
|
21396
|
+
idleMs,
|
|
21397
|
+
thresholdMs,
|
|
21398
|
+
promptAgeMs: now - state.promptStartedAt,
|
|
21399
|
+
activeToolCalls: state.activeToolCalls,
|
|
21400
|
+
model: state.model
|
|
21401
|
+
});
|
|
21402
|
+
}
|
|
21403
|
+
}
|
|
21404
|
+
};
|
|
21405
|
+
|
|
21114
21406
|
// src/agent.ts
|
|
21115
21407
|
function isApiKeyError(err) {
|
|
21116
21408
|
return err instanceof Error && err.name === "AI_LoadAPIKeyError";
|
|
@@ -21123,19 +21415,22 @@ var Agent = class {
|
|
|
21123
21415
|
eventHandler;
|
|
21124
21416
|
authProvider;
|
|
21125
21417
|
mcpManager;
|
|
21418
|
+
turnTimeoutTracker;
|
|
21126
21419
|
constructor(config2) {
|
|
21127
21420
|
this.config = config2;
|
|
21128
21421
|
this.sdk = config2.sdk;
|
|
21129
21422
|
this.sessionManager = new SessionManager(config2.sdk);
|
|
21130
21423
|
this.authProvider = new AuthProviderManager(config2.sdk);
|
|
21131
21424
|
this.mcpManager = new McpManager(config2.sdk);
|
|
21425
|
+
this.turnTimeoutTracker = new TurnTimeoutTracker(this.sessionManager);
|
|
21132
21426
|
}
|
|
21133
21427
|
init(connection) {
|
|
21134
21428
|
this.connection = connection;
|
|
21135
21429
|
this.eventHandler = new EventHandler({
|
|
21136
21430
|
connection,
|
|
21137
21431
|
sdk: this.sdk,
|
|
21138
|
-
sessionManager: this.sessionManager
|
|
21432
|
+
sessionManager: this.sessionManager,
|
|
21433
|
+
turnTimeoutTracker: this.turnTimeoutTracker
|
|
21139
21434
|
});
|
|
21140
21435
|
this.eventHandler.start();
|
|
21141
21436
|
}
|
|
@@ -21478,102 +21773,114 @@ var Agent = class {
|
|
|
21478
21773
|
const agent = session.modeId ?? await this.defaultAgent(directory);
|
|
21479
21774
|
const parts = this.convertPromptParts(params.prompt);
|
|
21480
21775
|
const cmd = this.parseCommand(parts);
|
|
21481
|
-
|
|
21482
|
-
|
|
21483
|
-
|
|
21776
|
+
const modelLabel = `${model.providerID}/${model.modelID}`;
|
|
21777
|
+
ocCall("session.prompt", { sessionID, agent, model: modelLabel });
|
|
21778
|
+
this.turnTimeoutTracker.onPromptStart(sessionID, { model: modelLabel });
|
|
21779
|
+
try {
|
|
21780
|
+
if (!cmd) {
|
|
21781
|
+
const response2 = await this.sdk.session.prompt({
|
|
21782
|
+
sessionID,
|
|
21783
|
+
model: {
|
|
21784
|
+
providerID: model.providerID,
|
|
21785
|
+
modelID: model.modelID
|
|
21786
|
+
},
|
|
21787
|
+
variant: this.sessionManager.getVariant(sessionID),
|
|
21788
|
+
parts,
|
|
21789
|
+
agent,
|
|
21790
|
+
directory
|
|
21791
|
+
});
|
|
21792
|
+
const rawErr2 = response2.error;
|
|
21793
|
+
ocCall("session.prompt.raw", {
|
|
21794
|
+
hasData: !!response2.data,
|
|
21795
|
+
hasError: !!rawErr2,
|
|
21796
|
+
errorName: rawErr2?.name,
|
|
21797
|
+
errorMessage: rawErr2?.message,
|
|
21798
|
+
errorCode: rawErr2?.code ?? rawErr2?.cause?.code,
|
|
21799
|
+
errorCauseName: rawErr2?.cause?.name,
|
|
21800
|
+
errorCauseMessage: rawErr2?.cause?.message
|
|
21801
|
+
});
|
|
21802
|
+
const msg2 = response2.data?.info;
|
|
21803
|
+
return await this.finishPromptTurn(sessionID, directory, { rawErr: rawErr2, msg: msg2 });
|
|
21804
|
+
}
|
|
21805
|
+
const command = await this.sdk.command.list({ directory }).then((x) => x.data?.find((c) => c.name === cmd.name));
|
|
21806
|
+
if (command) {
|
|
21807
|
+
const response2 = await this.sdk.session.command({
|
|
21808
|
+
sessionID,
|
|
21809
|
+
command: command.name,
|
|
21810
|
+
arguments: cmd.args,
|
|
21811
|
+
model: modelLabel,
|
|
21812
|
+
agent,
|
|
21813
|
+
directory
|
|
21814
|
+
});
|
|
21815
|
+
const rawErr2 = response2.error;
|
|
21816
|
+
const msg2 = response2.data?.info;
|
|
21817
|
+
return await this.finishPromptTurn(sessionID, directory, { rawErr: rawErr2, msg: msg2 });
|
|
21818
|
+
}
|
|
21819
|
+
if (cmd.name === "compact") {
|
|
21820
|
+
await this.sdk.session.summarize(
|
|
21821
|
+
{
|
|
21822
|
+
sessionID,
|
|
21823
|
+
directory,
|
|
21824
|
+
providerID: model.providerID,
|
|
21825
|
+
modelID: model.modelID
|
|
21826
|
+
},
|
|
21827
|
+
{ throwOnError: true }
|
|
21828
|
+
).catch(() => {
|
|
21829
|
+
});
|
|
21830
|
+
await this.sendUsageUpdate(sessionID, directory);
|
|
21831
|
+
return {
|
|
21832
|
+
stopReason: "end_turn",
|
|
21833
|
+
_meta: {}
|
|
21834
|
+
};
|
|
21835
|
+
}
|
|
21836
|
+
const response = await this.sdk.session.prompt({
|
|
21484
21837
|
sessionID,
|
|
21485
|
-
model: {
|
|
21486
|
-
providerID: model.providerID,
|
|
21487
|
-
modelID: model.modelID
|
|
21488
|
-
},
|
|
21838
|
+
model: { providerID: model.providerID, modelID: model.modelID },
|
|
21489
21839
|
variant: this.sessionManager.getVariant(sessionID),
|
|
21490
21840
|
parts,
|
|
21491
21841
|
agent,
|
|
21492
21842
|
directory
|
|
21493
21843
|
});
|
|
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
|
-
};
|
|
21844
|
+
const rawErr = response.error;
|
|
21845
|
+
const msg = response.data?.info;
|
|
21846
|
+
return await this.finishPromptTurn(sessionID, directory, { rawErr, msg });
|
|
21847
|
+
} finally {
|
|
21848
|
+
this.turnTimeoutTracker.onPromptEnd(sessionID);
|
|
21561
21849
|
}
|
|
21562
|
-
|
|
21563
|
-
|
|
21564
|
-
|
|
21565
|
-
|
|
21566
|
-
|
|
21567
|
-
|
|
21568
|
-
|
|
21850
|
+
}
|
|
21851
|
+
/**
|
|
21852
|
+
* Complete a prompt turn: emit streaming errors, usage, and PromptResponse._meta.error.
|
|
21853
|
+
*/
|
|
21854
|
+
async finishPromptTurn(sessionID, directory, opts) {
|
|
21855
|
+
const turnError = extractTurnError({
|
|
21856
|
+
responseError: opts.rawErr,
|
|
21857
|
+
messageError: opts.msg?.error
|
|
21858
|
+
});
|
|
21859
|
+
ocCall("session.prompt.response", {
|
|
21860
|
+
messageId: opts.msg?.id,
|
|
21861
|
+
role: opts.msg?.role,
|
|
21862
|
+
model: opts.msg ? `${opts.msg.providerID}/${opts.msg.modelID}` : void 0,
|
|
21863
|
+
tokens: opts.msg?.tokens,
|
|
21864
|
+
cost: opts.msg?.cost,
|
|
21865
|
+
finish: opts.msg?.finish,
|
|
21866
|
+
error: turnError?.message
|
|
21867
|
+
});
|
|
21868
|
+
if (turnError) {
|
|
21869
|
+
await emitSessionError(this.connection, sessionID, turnError, { messageId: opts.msg?.id });
|
|
21870
|
+
}
|
|
21871
|
+
await this.logMessageContent(sessionID, opts.msg?.id, directory);
|
|
21872
|
+
await this.sendUsageUpdate(sessionID, directory, turnError);
|
|
21873
|
+
const stopReason = stopReasonForTurn(turnError, opts.msg?.finish);
|
|
21874
|
+
const usage = opts.msg ? this.buildUsage(opts.msg) : void 0;
|
|
21875
|
+
acpOut("prompt.response", {
|
|
21876
|
+
stopReason,
|
|
21877
|
+
usage,
|
|
21878
|
+
error: turnError?.message
|
|
21569
21879
|
});
|
|
21570
|
-
const msg = response.data?.info;
|
|
21571
|
-
await this.logMessageContent(sessionID, msg?.id, directory);
|
|
21572
|
-
await this.sendUsageUpdate(sessionID, directory);
|
|
21573
21880
|
return {
|
|
21574
|
-
stopReason
|
|
21575
|
-
usage
|
|
21576
|
-
_meta: {}
|
|
21881
|
+
stopReason,
|
|
21882
|
+
usage,
|
|
21883
|
+
_meta: turnError ? { error: turnError } : {}
|
|
21577
21884
|
};
|
|
21578
21885
|
}
|
|
21579
21886
|
async cancel(params) {
|
|
@@ -21584,6 +21891,7 @@ var Agent = class {
|
|
|
21584
21891
|
sessionID: params.sessionId,
|
|
21585
21892
|
directory: session.cwd
|
|
21586
21893
|
});
|
|
21894
|
+
this.turnTimeoutTracker.onPromptEnd(params.sessionId);
|
|
21587
21895
|
}
|
|
21588
21896
|
// ─── Configuration ────────────────────────────────────────────────
|
|
21589
21897
|
async setSessionMode(params) {
|
|
@@ -22028,7 +22336,7 @@ var Agent = class {
|
|
|
22028
22336
|
break;
|
|
22029
22337
|
}
|
|
22030
22338
|
}
|
|
22031
|
-
async sendUsageUpdate(sessionId, directory) {
|
|
22339
|
+
async sendUsageUpdate(sessionId, directory, turnError) {
|
|
22032
22340
|
const messages = await this.sdk.session.messages({ sessionID: sessionId, directory }).then((x) => x.data).catch(() => void 0);
|
|
22033
22341
|
if (!messages) return;
|
|
22034
22342
|
const assistantMessages = messages.filter(
|
|
@@ -22059,6 +22367,9 @@ var Agent = class {
|
|
|
22059
22367
|
}
|
|
22060
22368
|
this.aggregateFileDiffStats(sessionId, children, _meta);
|
|
22061
22369
|
this.aggregateAICodeChangeStats(sessionId, children, _meta);
|
|
22370
|
+
if (turnError) {
|
|
22371
|
+
_meta.error = turnError;
|
|
22372
|
+
}
|
|
22062
22373
|
await sendToClient(this.connection, {
|
|
22063
22374
|
sessionId,
|
|
22064
22375
|
update: {
|
|
@@ -22202,13 +22513,15 @@ var Agent = class {
|
|
|
22202
22513
|
}
|
|
22203
22514
|
}
|
|
22204
22515
|
buildUsage(msg) {
|
|
22516
|
+
const tokens = msg.tokens;
|
|
22517
|
+
if (!tokens) return void 0;
|
|
22205
22518
|
return {
|
|
22206
|
-
totalTokens:
|
|
22207
|
-
inputTokens:
|
|
22208
|
-
outputTokens:
|
|
22209
|
-
thoughtTokens:
|
|
22210
|
-
cachedReadTokens:
|
|
22211
|
-
cachedWriteTokens:
|
|
22519
|
+
totalTokens: tokens.input + tokens.output + tokens.reasoning + (tokens.cache?.read ?? 0) + (tokens.cache?.write ?? 0),
|
|
22520
|
+
inputTokens: tokens.input,
|
|
22521
|
+
outputTokens: tokens.output,
|
|
22522
|
+
thoughtTokens: tokens.reasoning || void 0,
|
|
22523
|
+
cachedReadTokens: tokens.cache?.read || void 0,
|
|
22524
|
+
cachedWriteTokens: tokens.cache?.write || void 0
|
|
22212
22525
|
};
|
|
22213
22526
|
}
|
|
22214
22527
|
};
|