@harmonyos-arkts/opencode-acp 0.0.4 → 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 +747 -268
- package/dist/index.cjs.map +4 -4
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -19586,6 +19586,11 @@ function createOpencodeClient(config2) {
|
|
|
19586
19586
|
// node_modules/@opencode-ai/sdk/dist/v2/server.js
|
|
19587
19587
|
var import_cross_spawn = __toESM(require_cross_spawn(), 1);
|
|
19588
19588
|
|
|
19589
|
+
// src/index.ts
|
|
19590
|
+
var http = __toESM(require("node:http"), 1);
|
|
19591
|
+
var https = __toESM(require("node:https"), 1);
|
|
19592
|
+
var import_node_stream = require("node:stream");
|
|
19593
|
+
|
|
19589
19594
|
// src/agent.ts
|
|
19590
19595
|
var import_url2 = require("url");
|
|
19591
19596
|
|
|
@@ -19699,6 +19704,14 @@ var SessionManager = class {
|
|
|
19699
19704
|
getChildren(parentId) {
|
|
19700
19705
|
return [...this.children.get(parentId) ?? []];
|
|
19701
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
|
+
}
|
|
19702
19715
|
// Model and mode management
|
|
19703
19716
|
getModel(sessionId) {
|
|
19704
19717
|
return this.get(sessionId).model;
|
|
@@ -20279,65 +20292,6 @@ function applyStructuredPatch(source, patch, options = {}) {
|
|
|
20279
20292
|
|
|
20280
20293
|
// src/utils.ts
|
|
20281
20294
|
var import_url = require("url");
|
|
20282
|
-
function toToolKind(toolName) {
|
|
20283
|
-
const tool = toolName.toLowerCase();
|
|
20284
|
-
switch (tool) {
|
|
20285
|
-
case "bash":
|
|
20286
|
-
return "execute";
|
|
20287
|
-
case "webfetch":
|
|
20288
|
-
return "fetch";
|
|
20289
|
-
case "edit":
|
|
20290
|
-
case "patch":
|
|
20291
|
-
case "write":
|
|
20292
|
-
return "edit";
|
|
20293
|
-
case "grep":
|
|
20294
|
-
case "glob":
|
|
20295
|
-
case "context7_resolve_library_id":
|
|
20296
|
-
case "context7_get_library_docs":
|
|
20297
|
-
return "search";
|
|
20298
|
-
case "list":
|
|
20299
|
-
case "read":
|
|
20300
|
-
return "read";
|
|
20301
|
-
default:
|
|
20302
|
-
return "other";
|
|
20303
|
-
}
|
|
20304
|
-
}
|
|
20305
|
-
function toLocations(toolName, input) {
|
|
20306
|
-
const tool = toolName.toLowerCase();
|
|
20307
|
-
switch (tool) {
|
|
20308
|
-
case "read":
|
|
20309
|
-
case "edit":
|
|
20310
|
-
case "write":
|
|
20311
|
-
return input["filePath"] ? [{ path: input["filePath"] }] : [];
|
|
20312
|
-
case "glob":
|
|
20313
|
-
case "grep":
|
|
20314
|
-
return input["path"] ? [{ path: input["path"] }] : [];
|
|
20315
|
-
case "list":
|
|
20316
|
-
return input["path"] ? [{ path: input["path"] }] : [];
|
|
20317
|
-
default:
|
|
20318
|
-
return [];
|
|
20319
|
-
}
|
|
20320
|
-
}
|
|
20321
|
-
function parseUri(uri) {
|
|
20322
|
-
try {
|
|
20323
|
-
if (uri.startsWith("file://")) {
|
|
20324
|
-
const path = uri.slice(7);
|
|
20325
|
-
const name = path.split("/").pop() || path;
|
|
20326
|
-
return { type: "file", url: uri, filename: name, mime: "text/plain" };
|
|
20327
|
-
}
|
|
20328
|
-
if (uri.startsWith("zed://")) {
|
|
20329
|
-
const url2 = new URL(uri);
|
|
20330
|
-
const path = url2.searchParams.get("path");
|
|
20331
|
-
if (path) {
|
|
20332
|
-
const name = path.split("/").pop() || path;
|
|
20333
|
-
return { type: "file", url: (0, import_url.pathToFileURL)(path).href, filename: name, mime: "text/plain" };
|
|
20334
|
-
}
|
|
20335
|
-
}
|
|
20336
|
-
return { type: "text", text: uri };
|
|
20337
|
-
} catch {
|
|
20338
|
-
return { type: "text", text: uri };
|
|
20339
|
-
}
|
|
20340
|
-
}
|
|
20341
20295
|
|
|
20342
20296
|
// src/logger.ts
|
|
20343
20297
|
var import_fs = require("fs");
|
|
@@ -20403,13 +20357,18 @@ function sysLog(action, data) {
|
|
|
20403
20357
|
write("system", action, data);
|
|
20404
20358
|
}
|
|
20405
20359
|
function sanitize(obj, depth = 0) {
|
|
20406
|
-
if (!obj
|
|
20360
|
+
if (!obj) return obj;
|
|
20361
|
+
if (depth > 5) return { __truncated__: true, __depth__: depth };
|
|
20407
20362
|
const result = {};
|
|
20408
20363
|
for (const [key, val] of Object.entries(obj)) {
|
|
20409
20364
|
if (typeof val === "string" && val.length > 2e3) {
|
|
20410
20365
|
result[key] = val.slice(0, 2e3) + `... [${val.length} chars total]`;
|
|
20411
20366
|
} else if (val && typeof val === "object" && !Array.isArray(val)) {
|
|
20412
20367
|
result[key] = sanitize(val, depth + 1);
|
|
20368
|
+
} else if (val && Array.isArray(val)) {
|
|
20369
|
+
result[key] = val.map(
|
|
20370
|
+
(item) => typeof item === "string" && item.length > 2e3 ? item.slice(0, 2e3) + `... [${item.length} chars total]` : item && typeof item === "object" ? sanitize(item, depth + 1) : item
|
|
20371
|
+
);
|
|
20413
20372
|
} else {
|
|
20414
20373
|
result[key] = val;
|
|
20415
20374
|
}
|
|
@@ -20417,8 +20376,171 @@ function sanitize(obj, depth = 0) {
|
|
|
20417
20376
|
return result;
|
|
20418
20377
|
}
|
|
20419
20378
|
|
|
20379
|
+
// src/utils.ts
|
|
20380
|
+
function toToolKind(toolName) {
|
|
20381
|
+
const tool = toolName.toLowerCase();
|
|
20382
|
+
switch (tool) {
|
|
20383
|
+
case "bash":
|
|
20384
|
+
return "execute";
|
|
20385
|
+
case "webfetch":
|
|
20386
|
+
return "fetch";
|
|
20387
|
+
case "edit":
|
|
20388
|
+
case "patch":
|
|
20389
|
+
case "write":
|
|
20390
|
+
return "edit";
|
|
20391
|
+
case "grep":
|
|
20392
|
+
case "glob":
|
|
20393
|
+
case "context7_resolve_library_id":
|
|
20394
|
+
case "context7_get_library_docs":
|
|
20395
|
+
return "search";
|
|
20396
|
+
case "list":
|
|
20397
|
+
case "read":
|
|
20398
|
+
return "read";
|
|
20399
|
+
default:
|
|
20400
|
+
return "other";
|
|
20401
|
+
}
|
|
20402
|
+
}
|
|
20403
|
+
function toLocations(toolName, input) {
|
|
20404
|
+
const tool = toolName.toLowerCase();
|
|
20405
|
+
switch (tool) {
|
|
20406
|
+
case "read":
|
|
20407
|
+
case "edit":
|
|
20408
|
+
case "write":
|
|
20409
|
+
return input["filePath"] ? [{ path: input["filePath"] }] : [];
|
|
20410
|
+
case "glob":
|
|
20411
|
+
case "grep":
|
|
20412
|
+
return input["path"] ? [{ path: input["path"] }] : [];
|
|
20413
|
+
case "list":
|
|
20414
|
+
return input["path"] ? [{ path: input["path"] }] : [];
|
|
20415
|
+
default:
|
|
20416
|
+
return [];
|
|
20417
|
+
}
|
|
20418
|
+
}
|
|
20419
|
+
function parseUri(uri) {
|
|
20420
|
+
try {
|
|
20421
|
+
if (uri.startsWith("file://")) {
|
|
20422
|
+
const path = uri.slice(7);
|
|
20423
|
+
const name = path.split("/").pop() || path;
|
|
20424
|
+
return { type: "file", url: uri, filename: name, mime: "text/plain" };
|
|
20425
|
+
}
|
|
20426
|
+
if (uri.startsWith("zed://")) {
|
|
20427
|
+
const url2 = new URL(uri);
|
|
20428
|
+
const path = url2.searchParams.get("path");
|
|
20429
|
+
if (path) {
|
|
20430
|
+
const name = path.split("/").pop() || path;
|
|
20431
|
+
return { type: "file", url: (0, import_url.pathToFileURL)(path).href, filename: name, mime: "text/plain" };
|
|
20432
|
+
}
|
|
20433
|
+
}
|
|
20434
|
+
return { type: "text", text: uri };
|
|
20435
|
+
} catch {
|
|
20436
|
+
return { type: "text", text: uri };
|
|
20437
|
+
}
|
|
20438
|
+
}
|
|
20439
|
+
async function sendToClient(connection, params) {
|
|
20440
|
+
const updateType = params.update.sessionUpdate;
|
|
20441
|
+
const update = params.update;
|
|
20442
|
+
acpOut(`sessionUpdate.${updateType}`, {
|
|
20443
|
+
sessionId: params.sessionId.slice(0, 12),
|
|
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 } : {}
|
|
20451
|
+
});
|
|
20452
|
+
return connection.sessionUpdate(params);
|
|
20453
|
+
}
|
|
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
|
+
|
|
20420
20540
|
// src/event-handler.ts
|
|
20421
|
-
var
|
|
20541
|
+
var SUBSCRIPTION_RETRY_MS = 3e3;
|
|
20542
|
+
var SUBSCRIPTION_ERROR_BROADCAST_MS = 3e4;
|
|
20543
|
+
var QUESTION_TIMEOUT_MS = 3e5;
|
|
20422
20544
|
var permissionOptions = [
|
|
20423
20545
|
{ optionId: "once", kind: "allow_once", name: "Allow once" },
|
|
20424
20546
|
{ optionId: "always", kind: "allow_always", name: "Always allow" },
|
|
@@ -20438,60 +20560,69 @@ var EventHandler = class {
|
|
|
20438
20560
|
partMetaIndex = /* @__PURE__ */ new Map();
|
|
20439
20561
|
/** Track tool call counts per child session to detect completion. */
|
|
20440
20562
|
childToolCounts = /* @__PURE__ */ new Map();
|
|
20441
|
-
/** Cumulative
|
|
20442
|
-
|
|
20443
|
-
/**
|
|
20444
|
-
|
|
20445
|
-
|
|
20446
|
-
|
|
20447
|
-
async sendToClient(params) {
|
|
20448
|
-
const updateType = params.update.sessionUpdate;
|
|
20449
|
-
const update = params.update;
|
|
20450
|
-
acpOut(`sessionUpdate.${updateType}`, {
|
|
20451
|
-
sessionId: params.sessionId.slice(0, 12),
|
|
20452
|
-
...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" ? { used: update.used, size: update.size, cost: update.cost, _meta: update._meta } : {}
|
|
20453
|
-
});
|
|
20454
|
-
return this.connection.sessionUpdate(params);
|
|
20455
|
-
}
|
|
20456
|
-
getDiffStats(sessionId) {
|
|
20457
|
-
return this.diffStats.get(sessionId);
|
|
20458
|
-
}
|
|
20459
|
-
setDiffStats(sessionId, stats) {
|
|
20460
|
-
if (this.diffStats.has(sessionId)) return;
|
|
20461
|
-
this.diffStats.set(sessionId, stats);
|
|
20462
|
-
}
|
|
20563
|
+
/** Cumulative file diff stats per session, aggregated from session.diff SSE events. */
|
|
20564
|
+
fileDiffStats = /* @__PURE__ */ new Map();
|
|
20565
|
+
/** AI code change stats per session, keyed by sessionId → filePath → { additions, deletions }. */
|
|
20566
|
+
aiCodeChangeStats = /* @__PURE__ */ new Map();
|
|
20567
|
+
lastSubscriptionErrorBroadcastAt = 0;
|
|
20568
|
+
turnTimeoutTracker;
|
|
20463
20569
|
constructor(deps) {
|
|
20464
20570
|
this.connection = deps.connection;
|
|
20465
20571
|
this.sdk = deps.sdk;
|
|
20466
20572
|
this.sessionManager = deps.sessionManager;
|
|
20573
|
+
this.turnTimeoutTracker = deps.turnTimeoutTracker;
|
|
20467
20574
|
}
|
|
20468
20575
|
start() {
|
|
20469
20576
|
if (this.started) return;
|
|
20470
20577
|
this.started = true;
|
|
20471
|
-
this.
|
|
20472
|
-
|
|
20473
|
-
console.error("[event-handler] subscription failed:", err);
|
|
20474
|
-
});
|
|
20578
|
+
this.turnTimeoutTracker?.start();
|
|
20579
|
+
void this.runSubscription();
|
|
20475
20580
|
}
|
|
20476
20581
|
stop() {
|
|
20477
20582
|
this.abort.abort();
|
|
20583
|
+
this.turnTimeoutTracker?.stop();
|
|
20478
20584
|
}
|
|
20479
20585
|
async runSubscription() {
|
|
20480
20586
|
while (true) {
|
|
20481
20587
|
if (this.abort.signal.aborted) return;
|
|
20482
|
-
|
|
20483
|
-
|
|
20484
|
-
|
|
20485
|
-
for await (const event of events.stream) {
|
|
20486
|
-
if (this.abort.signal.aborted) return;
|
|
20487
|
-
const payload = event?.payload;
|
|
20488
|
-
if (!payload) continue;
|
|
20489
|
-
await this.handleEvent(payload).catch((err) => {
|
|
20490
|
-
console.error("[event-handler] failed to handle event:", err);
|
|
20588
|
+
try {
|
|
20589
|
+
const events = await this.sdk.global.event({
|
|
20590
|
+
signal: this.abort.signal
|
|
20491
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));
|
|
20492
20605
|
}
|
|
20493
20606
|
}
|
|
20494
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
|
+
}
|
|
20495
20626
|
// ─── Resolve session for routing ────────────────────────────────
|
|
20496
20627
|
/**
|
|
20497
20628
|
* Resolve the sessionId to use for ACP routing.
|
|
@@ -20508,6 +20639,7 @@ var EventHandler = class {
|
|
|
20508
20639
|
if (event.type === "server.heartbeat") {
|
|
20509
20640
|
return;
|
|
20510
20641
|
}
|
|
20642
|
+
this.turnTimeoutTracker?.noteEvent(event);
|
|
20511
20643
|
switch (event.type) {
|
|
20512
20644
|
case "session.created":
|
|
20513
20645
|
case "session.updated": {
|
|
@@ -20602,15 +20734,26 @@ var EventHandler = class {
|
|
|
20602
20734
|
}
|
|
20603
20735
|
case "question.asked": {
|
|
20604
20736
|
const q = event.properties;
|
|
20605
|
-
|
|
20606
|
-
|
|
20737
|
+
ocEvent("question.asked", event);
|
|
20738
|
+
let resolved = this.resolveSession(q.sessionID);
|
|
20739
|
+
if (!resolved) {
|
|
20740
|
+
console.warn(`[event-handler] question.asked: session ${q.sessionID} not in manager, attempting auto-register`);
|
|
20741
|
+
try {
|
|
20742
|
+
const sessionResp = await this.sdk.session.get({ sessionID: q.sessionID, directory: "" });
|
|
20743
|
+
const fetchedSession = sessionResp.data;
|
|
20744
|
+
if (fetchedSession?.parentID) {
|
|
20745
|
+
this.sessionManager.registerDiscovered(fetchedSession.id, fetchedSession.parentID, fetchedSession.title);
|
|
20746
|
+
resolved = this.resolveSession(q.sessionID);
|
|
20747
|
+
} else if (fetchedSession) {
|
|
20748
|
+
console.warn(`[event-handler] question.asked: top-level session ${q.sessionID} not registered; was agent.newSession called?`);
|
|
20749
|
+
}
|
|
20750
|
+
} catch (err) {
|
|
20751
|
+
console.error(`[event-handler] question.asked: failed to fetch session ${q.sessionID}:`, err);
|
|
20752
|
+
}
|
|
20753
|
+
if (!resolved) return;
|
|
20754
|
+
}
|
|
20607
20755
|
const directory = resolved.cwd;
|
|
20608
20756
|
const sessionId = resolved.sessionId;
|
|
20609
|
-
ocEvent("question.asked", {
|
|
20610
|
-
id: q.id,
|
|
20611
|
-
sessionID: q.sessionID,
|
|
20612
|
-
questions: q.questions.length
|
|
20613
|
-
});
|
|
20614
20757
|
const prev = this.questionQueues.get(q.sessionID) ?? Promise.resolve();
|
|
20615
20758
|
const next = prev.then(async () => {
|
|
20616
20759
|
const extResult = await Promise.race([
|
|
@@ -20630,7 +20773,7 @@ var EventHandler = class {
|
|
|
20630
20773
|
if (!extResult || !extResult.answers) {
|
|
20631
20774
|
await this.sdk.question.reject({ requestID: q.id, directory }).catch(() => {
|
|
20632
20775
|
});
|
|
20633
|
-
|
|
20776
|
+
sysLog("question.rejected", { requestID: q.id });
|
|
20634
20777
|
return;
|
|
20635
20778
|
}
|
|
20636
20779
|
const answers = extResult.answers;
|
|
@@ -20638,7 +20781,7 @@ var EventHandler = class {
|
|
|
20638
20781
|
console.error("[event-handler] invalid answers shape from client:", JSON.stringify(answers));
|
|
20639
20782
|
await this.sdk.question.reject({ requestID: q.id, directory }).catch(() => {
|
|
20640
20783
|
});
|
|
20641
|
-
|
|
20784
|
+
sysLog("question.rejected", {
|
|
20642
20785
|
requestID: q.id,
|
|
20643
20786
|
reason: "invalid_answers"
|
|
20644
20787
|
});
|
|
@@ -20649,7 +20792,7 @@ var EventHandler = class {
|
|
|
20649
20792
|
directory,
|
|
20650
20793
|
answers
|
|
20651
20794
|
});
|
|
20652
|
-
|
|
20795
|
+
sysLog("question.reply", { requestID: q.id, answers });
|
|
20653
20796
|
}).catch((err) => {
|
|
20654
20797
|
console.error("[event-handler] question handling error:", err);
|
|
20655
20798
|
}).finally(() => {
|
|
@@ -20673,7 +20816,7 @@ var EventHandler = class {
|
|
|
20673
20816
|
await this.handleToolPart(resolved.sessionId, part);
|
|
20674
20817
|
}
|
|
20675
20818
|
if (part.type === "text" && typeof part.text === "string" && part.ignored === true) {
|
|
20676
|
-
await this.
|
|
20819
|
+
await sendToClient(this.connection, {
|
|
20677
20820
|
sessionId: resolved.sessionId,
|
|
20678
20821
|
update: {
|
|
20679
20822
|
sessionUpdate: "agent_message_chunk",
|
|
@@ -20694,7 +20837,7 @@ var EventHandler = class {
|
|
|
20694
20837
|
const partMeta = this.partMetaIndex.get(props.partID);
|
|
20695
20838
|
if (!partMeta) return;
|
|
20696
20839
|
if (partMeta.type === "text" && props.field === "text" && partMeta.ignored !== true) {
|
|
20697
|
-
await this.
|
|
20840
|
+
await sendToClient(this.connection, {
|
|
20698
20841
|
sessionId,
|
|
20699
20842
|
update: {
|
|
20700
20843
|
sessionUpdate: "agent_message_chunk",
|
|
@@ -20706,7 +20849,7 @@ var EventHandler = class {
|
|
|
20706
20849
|
return;
|
|
20707
20850
|
}
|
|
20708
20851
|
if (partMeta.type === "reasoning" && props.field === "text") {
|
|
20709
|
-
await this.
|
|
20852
|
+
await sendToClient(this.connection, {
|
|
20710
20853
|
sessionId,
|
|
20711
20854
|
update: {
|
|
20712
20855
|
sessionUpdate: "agent_thought_chunk",
|
|
@@ -20731,7 +20874,7 @@ var EventHandler = class {
|
|
|
20731
20874
|
additions += d.additions;
|
|
20732
20875
|
deletions += d.deletions;
|
|
20733
20876
|
}
|
|
20734
|
-
this.
|
|
20877
|
+
this.fileDiffStats.set(resolved.sessionId, { additions, deletions, files: diff.length });
|
|
20735
20878
|
return;
|
|
20736
20879
|
}
|
|
20737
20880
|
default: {
|
|
@@ -20747,7 +20890,7 @@ var EventHandler = class {
|
|
|
20747
20890
|
* and _meta containing structured metadata about the subagent.
|
|
20748
20891
|
*/
|
|
20749
20892
|
async announceChildSession(childSessionId, parentSessionId, title, meta3) {
|
|
20750
|
-
await this.
|
|
20893
|
+
await sendToClient(this.connection, {
|
|
20751
20894
|
sessionId: childSessionId,
|
|
20752
20895
|
update: {
|
|
20753
20896
|
sessionUpdate: "session_info_update",
|
|
@@ -20767,6 +20910,7 @@ var EventHandler = class {
|
|
|
20767
20910
|
async handleToolPart(sessionId, part) {
|
|
20768
20911
|
if (!this.toolStarts.has(part.callID)) {
|
|
20769
20912
|
this.toolStarts.add(part.callID);
|
|
20913
|
+
this.turnTimeoutTracker?.onToolCallStart(sessionId);
|
|
20770
20914
|
const session = this.sessionManager.tryGet(sessionId);
|
|
20771
20915
|
if (session?.parentID) {
|
|
20772
20916
|
const tracker = this.childToolCounts.get(sessionId);
|
|
@@ -20780,7 +20924,7 @@ var EventHandler = class {
|
|
|
20780
20924
|
});
|
|
20781
20925
|
}
|
|
20782
20926
|
}
|
|
20783
|
-
await this.
|
|
20927
|
+
await sendToClient(this.connection, {
|
|
20784
20928
|
sessionId,
|
|
20785
20929
|
update: {
|
|
20786
20930
|
sessionUpdate: "tool_call",
|
|
@@ -20812,7 +20956,7 @@ var EventHandler = class {
|
|
|
20812
20956
|
content: { type: "text", text: output }
|
|
20813
20957
|
});
|
|
20814
20958
|
}
|
|
20815
|
-
await this.
|
|
20959
|
+
await sendToClient(this.connection, {
|
|
20816
20960
|
sessionId,
|
|
20817
20961
|
update: {
|
|
20818
20962
|
sessionUpdate: "tool_call_update",
|
|
@@ -20829,15 +20973,18 @@ var EventHandler = class {
|
|
|
20829
20973
|
return;
|
|
20830
20974
|
}
|
|
20831
20975
|
case "completed": {
|
|
20832
|
-
this.toolStarts.delete(part.callID)
|
|
20976
|
+
if (this.toolStarts.delete(part.callID)) {
|
|
20977
|
+
this.turnTimeoutTracker?.onToolCallEnd(sessionId);
|
|
20978
|
+
}
|
|
20833
20979
|
this.bashSnapshots.delete(part.callID);
|
|
20834
20980
|
if (part.tool === "task" && part.state.metadata) {
|
|
20835
20981
|
const childSessionId = typeof part.state.metadata["sessionId"] === "string" ? part.state.metadata["sessionId"] : void 0;
|
|
20836
20982
|
if (childSessionId && this.sessionManager.tryGet(childSessionId)) {
|
|
20837
20983
|
const tracker = this.childToolCounts.get(childSessionId);
|
|
20838
|
-
await this.
|
|
20984
|
+
await this.sendChildSessionFinished(
|
|
20839
20985
|
childSessionId,
|
|
20840
20986
|
sessionId,
|
|
20987
|
+
"completed",
|
|
20841
20988
|
tracker?.completed ?? 0,
|
|
20842
20989
|
tracker ? Date.now() - tracker.startTime : 0
|
|
20843
20990
|
).catch(() => {
|
|
@@ -20865,12 +21012,13 @@ var EventHandler = class {
|
|
|
20865
21012
|
const oldText = typeof input["oldString"] === "string" ? input["oldString"] : "";
|
|
20866
21013
|
const newText = typeof input["newString"] === "string" ? input["newString"] : typeof input["content"] === "string" ? input["content"] : "";
|
|
20867
21014
|
content.push({ type: "diff", path: filePath, oldText, newText });
|
|
21015
|
+
this.accumulateAICodeChangeStats(sessionId, part);
|
|
20868
21016
|
}
|
|
20869
21017
|
if (part.tool === "todowrite") {
|
|
20870
21018
|
try {
|
|
20871
21019
|
const todos = JSON.parse(part.state.output);
|
|
20872
21020
|
if (Array.isArray(todos)) {
|
|
20873
|
-
await this.
|
|
21021
|
+
await sendToClient(this.connection, {
|
|
20874
21022
|
sessionId,
|
|
20875
21023
|
update: {
|
|
20876
21024
|
sessionUpdate: "plan",
|
|
@@ -20886,7 +21034,7 @@ var EventHandler = class {
|
|
|
20886
21034
|
} catch {
|
|
20887
21035
|
}
|
|
20888
21036
|
}
|
|
20889
|
-
await this.
|
|
21037
|
+
await sendToClient(this.connection, {
|
|
20890
21038
|
sessionId,
|
|
20891
21039
|
update: {
|
|
20892
21040
|
sessionUpdate: "tool_call_update",
|
|
@@ -20906,9 +21054,33 @@ var EventHandler = class {
|
|
|
20906
21054
|
return;
|
|
20907
21055
|
}
|
|
20908
21056
|
case "error": {
|
|
20909
|
-
this.toolStarts.delete(part.callID)
|
|
21057
|
+
if (this.toolStarts.delete(part.callID)) {
|
|
21058
|
+
this.turnTimeoutTracker?.onToolCallEnd(sessionId);
|
|
21059
|
+
}
|
|
20910
21060
|
this.bashSnapshots.delete(part.callID);
|
|
20911
|
-
|
|
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
|
+
}
|
|
21083
|
+
await sendToClient(this.connection, {
|
|
20912
21084
|
sessionId,
|
|
20913
21085
|
update: {
|
|
20914
21086
|
sessionUpdate: "tool_call_update",
|
|
@@ -20935,21 +21107,20 @@ var EventHandler = class {
|
|
|
20935
21107
|
}
|
|
20936
21108
|
}
|
|
20937
21109
|
/**
|
|
20938
|
-
* Notify the ACP client that a child session
|
|
21110
|
+
* Notify the ACP client that a child session finished (success or failure).
|
|
20939
21111
|
*/
|
|
20940
|
-
async
|
|
21112
|
+
async sendChildSessionFinished(childSessionId, parentSessionId, status, toolCallCount, durationMs, error48) {
|
|
20941
21113
|
const child = this.sessionManager.tryGet(childSessionId);
|
|
20942
21114
|
const title = child?.title ?? "Subagent";
|
|
20943
21115
|
const agentMatch = title.match(/@(\w+)\s+subagent/);
|
|
20944
21116
|
const agentType = agentMatch?.[1];
|
|
20945
21117
|
const description = title.replace(/\s*\(@\w+\s+subagent\)\s*$/, "");
|
|
20946
|
-
|
|
20947
|
-
await this.sendToClient({
|
|
21118
|
+
await sendToClient(this.connection, {
|
|
20948
21119
|
sessionId: childSessionId,
|
|
20949
21120
|
update: {
|
|
20950
21121
|
sessionUpdate: "session_info_update",
|
|
20951
21122
|
title,
|
|
20952
|
-
status
|
|
21123
|
+
status,
|
|
20953
21124
|
_meta: {
|
|
20954
21125
|
parentSessionId,
|
|
20955
21126
|
isSubagent: true,
|
|
@@ -20957,7 +21128,7 @@ var EventHandler = class {
|
|
|
20957
21128
|
durationMs,
|
|
20958
21129
|
...agentType && { agentType },
|
|
20959
21130
|
...description && { description },
|
|
20960
|
-
...
|
|
21131
|
+
...error48 && { error: error48 }
|
|
20961
21132
|
}
|
|
20962
21133
|
}
|
|
20963
21134
|
});
|
|
@@ -20969,6 +21140,44 @@ var EventHandler = class {
|
|
|
20969
21140
|
if (typeof output !== "string") return;
|
|
20970
21141
|
return output;
|
|
20971
21142
|
}
|
|
21143
|
+
// ─── Statistics Accessors ────────────────────────────────────────
|
|
21144
|
+
getFileDiffStats(sessionId) {
|
|
21145
|
+
return this.fileDiffStats.get(sessionId);
|
|
21146
|
+
}
|
|
21147
|
+
setFileDiffStats(sessionId, stats) {
|
|
21148
|
+
if (this.fileDiffStats.has(sessionId)) return;
|
|
21149
|
+
this.fileDiffStats.set(sessionId, stats);
|
|
21150
|
+
}
|
|
21151
|
+
/**
|
|
21152
|
+
* Accumulate AI code change stats from a completed tool part's metadata.
|
|
21153
|
+
* The harmony-code plugin injects `metadata.aiCodeChange` for edit/write tools.
|
|
21154
|
+
*/
|
|
21155
|
+
accumulateAICodeChangeStats(sessionId, part) {
|
|
21156
|
+
if (part.state.status !== "completed") return;
|
|
21157
|
+
const meta3 = part.state.metadata ?? {};
|
|
21158
|
+
const aiCodeChange = meta3["aiCodeChange"];
|
|
21159
|
+
if (!aiCodeChange || aiCodeChange.additions === 0 && aiCodeChange.deletions === 0) return;
|
|
21160
|
+
const fileMap = this.aiCodeChangeStats.get(sessionId) ?? /* @__PURE__ */ new Map();
|
|
21161
|
+
const existing = fileMap.get(aiCodeChange.file) ?? { additions: 0, deletions: 0 };
|
|
21162
|
+
fileMap.set(aiCodeChange.file, {
|
|
21163
|
+
additions: existing.additions + aiCodeChange.additions,
|
|
21164
|
+
deletions: existing.deletions + aiCodeChange.deletions
|
|
21165
|
+
});
|
|
21166
|
+
if (!this.aiCodeChangeStats.has(sessionId)) {
|
|
21167
|
+
this.aiCodeChangeStats.set(sessionId, fileMap);
|
|
21168
|
+
}
|
|
21169
|
+
}
|
|
21170
|
+
getAICodeChangeStats(sessionId) {
|
|
21171
|
+
const fileMap = this.aiCodeChangeStats.get(sessionId);
|
|
21172
|
+
if (!fileMap || fileMap.size === 0) return void 0;
|
|
21173
|
+
let additions = 0;
|
|
21174
|
+
let deletions = 0;
|
|
21175
|
+
for (const stats of fileMap.values()) {
|
|
21176
|
+
additions += stats.additions;
|
|
21177
|
+
deletions += stats.deletions;
|
|
21178
|
+
}
|
|
21179
|
+
return { additions, deletions, files: fileMap.size };
|
|
21180
|
+
}
|
|
20972
21181
|
};
|
|
20973
21182
|
function simpleHash(str) {
|
|
20974
21183
|
let hash2 = 0;
|
|
@@ -21060,6 +21269,140 @@ var AuthProviderManager = class {
|
|
|
21060
21269
|
}
|
|
21061
21270
|
};
|
|
21062
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
|
+
|
|
21063
21406
|
// src/agent.ts
|
|
21064
21407
|
function isApiKeyError(err) {
|
|
21065
21408
|
return err instanceof Error && err.name === "AI_LoadAPIKeyError";
|
|
@@ -21072,44 +21415,22 @@ var Agent = class {
|
|
|
21072
21415
|
eventHandler;
|
|
21073
21416
|
authProvider;
|
|
21074
21417
|
mcpManager;
|
|
21075
|
-
|
|
21076
|
-
* Wrapper around connection.sessionUpdate that logs what's sent to the ACP client.
|
|
21077
|
-
*/
|
|
21078
|
-
async sendToClient(params) {
|
|
21079
|
-
const updateType = params.update.sessionUpdate;
|
|
21080
|
-
const update = params.update;
|
|
21081
|
-
acpOut(`sessionUpdate.${updateType}`, {
|
|
21082
|
-
sessionId: params.sessionId.slice(0, 12),
|
|
21083
|
-
...updateType === "agent_message_chunk" || updateType === "agent_thought_chunk" ? {
|
|
21084
|
-
messageId: update.messageId?.slice(0, 12),
|
|
21085
|
-
delta: update.content?.text?.slice(0, 200)
|
|
21086
|
-
} : updateType === "tool_call" || updateType === "tool_call_update" ? {
|
|
21087
|
-
toolCallId: update.toolCallId,
|
|
21088
|
-
tool: update.title,
|
|
21089
|
-
kind: update.kind,
|
|
21090
|
-
status: update.status
|
|
21091
|
-
} : updateType === "usage_update" ? {
|
|
21092
|
-
used: update.used,
|
|
21093
|
-
size: update.size,
|
|
21094
|
-
cost: update.cost,
|
|
21095
|
-
_meta: update._meta
|
|
21096
|
-
} : {}
|
|
21097
|
-
});
|
|
21098
|
-
return this.connection.sessionUpdate(params);
|
|
21099
|
-
}
|
|
21418
|
+
turnTimeoutTracker;
|
|
21100
21419
|
constructor(config2) {
|
|
21101
21420
|
this.config = config2;
|
|
21102
21421
|
this.sdk = config2.sdk;
|
|
21103
21422
|
this.sessionManager = new SessionManager(config2.sdk);
|
|
21104
21423
|
this.authProvider = new AuthProviderManager(config2.sdk);
|
|
21105
21424
|
this.mcpManager = new McpManager(config2.sdk);
|
|
21425
|
+
this.turnTimeoutTracker = new TurnTimeoutTracker(this.sessionManager);
|
|
21106
21426
|
}
|
|
21107
21427
|
init(connection) {
|
|
21108
21428
|
this.connection = connection;
|
|
21109
21429
|
this.eventHandler = new EventHandler({
|
|
21110
21430
|
connection,
|
|
21111
21431
|
sdk: this.sdk,
|
|
21112
|
-
sessionManager: this.sessionManager
|
|
21432
|
+
sessionManager: this.sessionManager,
|
|
21433
|
+
turnTimeoutTracker: this.turnTimeoutTracker
|
|
21113
21434
|
});
|
|
21114
21435
|
this.eventHandler.start();
|
|
21115
21436
|
}
|
|
@@ -21317,13 +21638,14 @@ var Agent = class {
|
|
|
21317
21638
|
}
|
|
21318
21639
|
}
|
|
21319
21640
|
async loadSession(params) {
|
|
21641
|
+
acpIn("loadSession", { sessionId: params.sessionId, cwd: params.cwd });
|
|
21320
21642
|
try {
|
|
21321
21643
|
const directory = params.cwd;
|
|
21322
21644
|
const sessionId = params.sessionId;
|
|
21323
21645
|
const model = await this.defaultModel(directory);
|
|
21324
21646
|
const { session } = await this.sessionManager.load(sessionId, params.cwd, params.mcpServers, model);
|
|
21325
21647
|
if (session.summary) {
|
|
21326
|
-
this.eventHandler.
|
|
21648
|
+
this.eventHandler.setFileDiffStats(sessionId, {
|
|
21327
21649
|
additions: session.summary.additions,
|
|
21328
21650
|
deletions: session.summary.deletions,
|
|
21329
21651
|
files: session.summary.files
|
|
@@ -21364,6 +21686,7 @@ var Agent = class {
|
|
|
21364
21686
|
}
|
|
21365
21687
|
}
|
|
21366
21688
|
async listSessions(params) {
|
|
21689
|
+
acpIn("listSessions", { cwd: params.cwd, cursor: params.cursor });
|
|
21367
21690
|
const limit = 100;
|
|
21368
21691
|
const cursor = params.cursor ? Number(params.cursor) : void 0;
|
|
21369
21692
|
const sessions = await this.sdk.session.list({ directory: params.cwd ?? void 0, roots: true }).then((x) => x.data ?? []);
|
|
@@ -21380,9 +21703,11 @@ var Agent = class {
|
|
|
21380
21703
|
const next = filtered.length > limit && last ? String(last.time.updated) : void 0;
|
|
21381
21704
|
const response = { sessions: entries };
|
|
21382
21705
|
if (next) response.nextCursor = next;
|
|
21706
|
+
acpOut("listSessions.response", { count: entries.length, hasNext: !!next });
|
|
21383
21707
|
return response;
|
|
21384
21708
|
}
|
|
21385
21709
|
async unstable_forkSession(params) {
|
|
21710
|
+
acpIn("forkSession", { sessionId: params.sessionId, cwd: params.cwd });
|
|
21386
21711
|
try {
|
|
21387
21712
|
const directory = params.cwd;
|
|
21388
21713
|
const mcpServers = params.mcpServers ?? [];
|
|
@@ -21408,6 +21733,7 @@ var Agent = class {
|
|
|
21408
21733
|
}
|
|
21409
21734
|
}
|
|
21410
21735
|
async unstable_resumeSession(params) {
|
|
21736
|
+
acpIn("resumeSession", { sessionId: params.sessionId, cwd: params.cwd });
|
|
21411
21737
|
try {
|
|
21412
21738
|
const directory = params.cwd;
|
|
21413
21739
|
const sessionId = params.sessionId;
|
|
@@ -21415,7 +21741,7 @@ var Agent = class {
|
|
|
21415
21741
|
const model = await this.defaultModel(directory);
|
|
21416
21742
|
const { session } = await this.sessionManager.load(sessionId, directory, mcpServers, model);
|
|
21417
21743
|
if (session.summary) {
|
|
21418
|
-
this.eventHandler.
|
|
21744
|
+
this.eventHandler.setFileDiffStats(sessionId, {
|
|
21419
21745
|
additions: session.summary.additions,
|
|
21420
21746
|
deletions: session.summary.deletions,
|
|
21421
21747
|
files: session.summary.files
|
|
@@ -21447,92 +21773,114 @@ var Agent = class {
|
|
|
21447
21773
|
const agent = session.modeId ?? await this.defaultAgent(directory);
|
|
21448
21774
|
const parts = this.convertPromptParts(params.prompt);
|
|
21449
21775
|
const cmd = this.parseCommand(parts);
|
|
21450
|
-
|
|
21451
|
-
|
|
21452
|
-
|
|
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({
|
|
21453
21837
|
sessionID,
|
|
21454
|
-
model: {
|
|
21455
|
-
providerID: model.providerID,
|
|
21456
|
-
modelID: model.modelID
|
|
21457
|
-
},
|
|
21838
|
+
model: { providerID: model.providerID, modelID: model.modelID },
|
|
21458
21839
|
variant: this.sessionManager.getVariant(sessionID),
|
|
21459
21840
|
parts,
|
|
21460
21841
|
agent,
|
|
21461
21842
|
directory
|
|
21462
21843
|
});
|
|
21463
|
-
const
|
|
21464
|
-
|
|
21465
|
-
|
|
21466
|
-
|
|
21467
|
-
|
|
21468
|
-
tokens: msg2?.tokens,
|
|
21469
|
-
cost: msg2?.cost,
|
|
21470
|
-
finish: msg2?.finish,
|
|
21471
|
-
error: msg2?.error
|
|
21472
|
-
});
|
|
21473
|
-
await this.logMessageContent(sessionID, msg2?.id, directory);
|
|
21474
|
-
await this.sendUsageUpdate(sessionID, directory);
|
|
21475
|
-
acpOut("prompt.response", {
|
|
21476
|
-
stopReason: "end_turn",
|
|
21477
|
-
usage: msg2 ? this.buildUsage(msg2) : void 0
|
|
21478
|
-
});
|
|
21479
|
-
return {
|
|
21480
|
-
stopReason: "end_turn",
|
|
21481
|
-
usage: msg2 ? this.buildUsage(msg2) : void 0,
|
|
21482
|
-
_meta: {}
|
|
21483
|
-
};
|
|
21484
|
-
}
|
|
21485
|
-
const command = await this.sdk.command.list({ directory }).then((x) => x.data?.find((c) => c.name === cmd.name));
|
|
21486
|
-
if (command) {
|
|
21487
|
-
const response2 = await this.sdk.session.command({
|
|
21488
|
-
sessionID,
|
|
21489
|
-
command: command.name,
|
|
21490
|
-
arguments: cmd.args,
|
|
21491
|
-
model: model.providerID + "/" + model.modelID,
|
|
21492
|
-
agent,
|
|
21493
|
-
directory
|
|
21494
|
-
});
|
|
21495
|
-
const msg2 = response2.data?.info;
|
|
21496
|
-
await this.logMessageContent(sessionID, msg2?.id, directory);
|
|
21497
|
-
await this.sendUsageUpdate(sessionID, directory);
|
|
21498
|
-
return {
|
|
21499
|
-
stopReason: "end_turn",
|
|
21500
|
-
usage: msg2 ? this.buildUsage(msg2) : void 0,
|
|
21501
|
-
_meta: {}
|
|
21502
|
-
};
|
|
21503
|
-
}
|
|
21504
|
-
if (cmd.name === "compact") {
|
|
21505
|
-
await this.sdk.session.summarize(
|
|
21506
|
-
{
|
|
21507
|
-
sessionID,
|
|
21508
|
-
directory,
|
|
21509
|
-
providerID: model.providerID,
|
|
21510
|
-
modelID: model.modelID
|
|
21511
|
-
},
|
|
21512
|
-
{ throwOnError: true }
|
|
21513
|
-
).catch(() => {
|
|
21514
|
-
});
|
|
21515
|
-
await this.sendUsageUpdate(sessionID, directory);
|
|
21516
|
-
return {
|
|
21517
|
-
stopReason: "end_turn",
|
|
21518
|
-
_meta: {}
|
|
21519
|
-
};
|
|
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);
|
|
21520
21849
|
}
|
|
21521
|
-
|
|
21522
|
-
|
|
21523
|
-
|
|
21524
|
-
|
|
21525
|
-
|
|
21526
|
-
|
|
21527
|
-
|
|
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
|
|
21528
21879
|
});
|
|
21529
|
-
const msg = response.data?.info;
|
|
21530
|
-
await this.logMessageContent(sessionID, msg?.id, directory);
|
|
21531
|
-
await this.sendUsageUpdate(sessionID, directory);
|
|
21532
21880
|
return {
|
|
21533
|
-
stopReason
|
|
21534
|
-
usage
|
|
21535
|
-
_meta: {}
|
|
21881
|
+
stopReason,
|
|
21882
|
+
usage,
|
|
21883
|
+
_meta: turnError ? { error: turnError } : {}
|
|
21536
21884
|
};
|
|
21537
21885
|
}
|
|
21538
21886
|
async cancel(params) {
|
|
@@ -21543,9 +21891,11 @@ var Agent = class {
|
|
|
21543
21891
|
sessionID: params.sessionId,
|
|
21544
21892
|
directory: session.cwd
|
|
21545
21893
|
});
|
|
21894
|
+
this.turnTimeoutTracker.onPromptEnd(params.sessionId);
|
|
21546
21895
|
}
|
|
21547
21896
|
// ─── Configuration ────────────────────────────────────────────────
|
|
21548
21897
|
async setSessionMode(params) {
|
|
21898
|
+
acpIn("setSessionMode", { sessionId: params.sessionId, modeId: params.modeId });
|
|
21549
21899
|
const session = this.sessionManager.get(params.sessionId);
|
|
21550
21900
|
const agents = await this.sdk.app.agents({ directory: session.cwd }).then((x) => x.data ?? []).catch(() => []);
|
|
21551
21901
|
const availableModes = agents.filter((a) => a.mode !== "subagent" && !a.hidden);
|
|
@@ -21555,6 +21905,7 @@ var Agent = class {
|
|
|
21555
21905
|
this.sessionManager.setMode(params.sessionId, params.modeId);
|
|
21556
21906
|
}
|
|
21557
21907
|
async unstable_setSessionModel(params) {
|
|
21908
|
+
acpIn("setSessionModel", { sessionId: params.sessionId, modelId: params.modelId });
|
|
21558
21909
|
const session = this.sessionManager.get(params.sessionId);
|
|
21559
21910
|
const providers = await this.sdk.config.providers({ directory: session.cwd }).then((x) => x.data?.providers ?? []).catch(() => []);
|
|
21560
21911
|
const selection = parseModelSelection(params.modelId, providers);
|
|
@@ -21568,6 +21919,7 @@ var Agent = class {
|
|
|
21568
21919
|
};
|
|
21569
21920
|
}
|
|
21570
21921
|
async setSessionConfigOption(params) {
|
|
21922
|
+
acpIn("setSessionConfigOption", { sessionId: params.sessionId, configId: params.configId, value: params.value });
|
|
21571
21923
|
const session = this.sessionManager.get(params.sessionId);
|
|
21572
21924
|
const providers = await this.sdk.config.providers({ directory: session.cwd }).then((x) => x.data?.providers ?? []).catch(() => []);
|
|
21573
21925
|
const sortedProviders = [...providers].sort(
|
|
@@ -21653,7 +22005,7 @@ var Agent = class {
|
|
|
21653
22005
|
if (!childMessages?.length) return;
|
|
21654
22006
|
const title = `Subagent (${childSessionId.slice(0, 8)})`;
|
|
21655
22007
|
this.sessionManager.registerDiscovered(childSessionId, parentSessionId, title);
|
|
21656
|
-
await this.
|
|
22008
|
+
await sendToClient(this.connection, {
|
|
21657
22009
|
sessionId: childSessionId,
|
|
21658
22010
|
update: {
|
|
21659
22011
|
sessionUpdate: "session_info_update",
|
|
@@ -21770,7 +22122,7 @@ var Agent = class {
|
|
|
21770
22122
|
availableCommands.push({ name: "compact", description: "compact the session" });
|
|
21771
22123
|
}
|
|
21772
22124
|
setTimeout(() => {
|
|
21773
|
-
this.
|
|
22125
|
+
sendToClient(this.connection, {
|
|
21774
22126
|
sessionId: params.sessionId,
|
|
21775
22127
|
update: {
|
|
21776
22128
|
sessionUpdate: "available_commands_update",
|
|
@@ -21823,7 +22175,7 @@ var Agent = class {
|
|
|
21823
22175
|
await this.replayToolPart(sessionId, part);
|
|
21824
22176
|
} else if (part.type === "text" && part.text) {
|
|
21825
22177
|
const audience = part.synthetic ? ["assistant"] : part.ignored ? ["user"] : void 0;
|
|
21826
|
-
await this.
|
|
22178
|
+
await sendToClient(this.connection, {
|
|
21827
22179
|
sessionId,
|
|
21828
22180
|
update: {
|
|
21829
22181
|
sessionUpdate: messageChunk,
|
|
@@ -21841,7 +22193,7 @@ var Agent = class {
|
|
|
21841
22193
|
const filename = part.filename ?? "file";
|
|
21842
22194
|
const mime = part.mime || "application/octet-stream";
|
|
21843
22195
|
if (url2.startsWith("file://")) {
|
|
21844
|
-
await this.
|
|
22196
|
+
await sendToClient(this.connection, {
|
|
21845
22197
|
sessionId,
|
|
21846
22198
|
update: {
|
|
21847
22199
|
sessionUpdate: messageChunk,
|
|
@@ -21856,7 +22208,7 @@ var Agent = class {
|
|
|
21856
22208
|
const base64Data = base64Match?.[2] ?? "";
|
|
21857
22209
|
const effectiveMime = dataMime || mime;
|
|
21858
22210
|
if (effectiveMime.startsWith("image/")) {
|
|
21859
|
-
await this.
|
|
22211
|
+
await sendToClient(this.connection, {
|
|
21860
22212
|
sessionId,
|
|
21861
22213
|
update: {
|
|
21862
22214
|
sessionUpdate: messageChunk,
|
|
@@ -21878,7 +22230,7 @@ var Agent = class {
|
|
|
21878
22230
|
mimeType: effectiveMime,
|
|
21879
22231
|
text: Buffer.from(base64Data, "base64").toString("utf-8")
|
|
21880
22232
|
} : { uri: fileUri, mimeType: effectiveMime, blob: base64Data };
|
|
21881
|
-
await this.
|
|
22233
|
+
await sendToClient(this.connection, {
|
|
21882
22234
|
sessionId,
|
|
21883
22235
|
update: {
|
|
21884
22236
|
sessionUpdate: messageChunk,
|
|
@@ -21890,7 +22242,7 @@ var Agent = class {
|
|
|
21890
22242
|
}
|
|
21891
22243
|
}
|
|
21892
22244
|
} else if (part.type === "reasoning" && part.text) {
|
|
21893
|
-
await this.
|
|
22245
|
+
await sendToClient(this.connection, {
|
|
21894
22246
|
sessionId,
|
|
21895
22247
|
update: {
|
|
21896
22248
|
sessionUpdate: "agent_thought_chunk",
|
|
@@ -21903,7 +22255,7 @@ var Agent = class {
|
|
|
21903
22255
|
}
|
|
21904
22256
|
}
|
|
21905
22257
|
async replayToolPart(sessionId, part) {
|
|
21906
|
-
await this.
|
|
22258
|
+
await sendToClient(this.connection, {
|
|
21907
22259
|
sessionId,
|
|
21908
22260
|
update: {
|
|
21909
22261
|
sessionUpdate: "tool_call",
|
|
@@ -21928,12 +22280,13 @@ var Agent = class {
|
|
|
21928
22280
|
oldText: typeof input["oldString"] === "string" ? input["oldString"] : "",
|
|
21929
22281
|
newText: typeof input["newString"] === "string" ? input["newString"] : typeof input["content"] === "string" ? input["content"] : ""
|
|
21930
22282
|
});
|
|
22283
|
+
this.eventHandler.accumulateAICodeChangeStats(sessionId, part);
|
|
21931
22284
|
}
|
|
21932
22285
|
if (part.tool === "todowrite") {
|
|
21933
22286
|
try {
|
|
21934
22287
|
const todos = JSON.parse(part.state.output);
|
|
21935
22288
|
if (Array.isArray(todos)) {
|
|
21936
|
-
await this.
|
|
22289
|
+
await sendToClient(this.connection, {
|
|
21937
22290
|
sessionId,
|
|
21938
22291
|
update: {
|
|
21939
22292
|
sessionUpdate: "plan",
|
|
@@ -21949,7 +22302,7 @@ var Agent = class {
|
|
|
21949
22302
|
} catch {
|
|
21950
22303
|
}
|
|
21951
22304
|
}
|
|
21952
|
-
await this.
|
|
22305
|
+
await sendToClient(this.connection, {
|
|
21953
22306
|
sessionId,
|
|
21954
22307
|
update: {
|
|
21955
22308
|
sessionUpdate: "tool_call_update",
|
|
@@ -21966,7 +22319,7 @@ var Agent = class {
|
|
|
21966
22319
|
break;
|
|
21967
22320
|
}
|
|
21968
22321
|
case "error":
|
|
21969
|
-
await this.
|
|
22322
|
+
await sendToClient(this.connection, {
|
|
21970
22323
|
sessionId,
|
|
21971
22324
|
update: {
|
|
21972
22325
|
sessionUpdate: "tool_call_update",
|
|
@@ -21983,7 +22336,7 @@ var Agent = class {
|
|
|
21983
22336
|
break;
|
|
21984
22337
|
}
|
|
21985
22338
|
}
|
|
21986
|
-
async sendUsageUpdate(sessionId, directory) {
|
|
22339
|
+
async sendUsageUpdate(sessionId, directory, turnError) {
|
|
21987
22340
|
const messages = await this.sdk.session.messages({ sessionID: sessionId, directory }).then((x) => x.data).catch(() => void 0);
|
|
21988
22341
|
if (!messages) return;
|
|
21989
22342
|
const assistantMessages = messages.filter(
|
|
@@ -22002,19 +22355,6 @@ var Agent = class {
|
|
|
22002
22355
|
childCost += childMessages.filter((m) => m.info.role === "assistant").reduce((sum, m) => sum + (m.info.cost ?? 0), 0);
|
|
22003
22356
|
}
|
|
22004
22357
|
}
|
|
22005
|
-
const stats = this.eventHandler.getDiffStats(sessionId);
|
|
22006
|
-
let childStats;
|
|
22007
|
-
for (const childId of children) {
|
|
22008
|
-
const cs = this.eventHandler.getDiffStats(childId);
|
|
22009
|
-
if (cs) {
|
|
22010
|
-
childStats = childStats ? {
|
|
22011
|
-
additions: childStats.additions + cs.additions,
|
|
22012
|
-
deletions: childStats.deletions + cs.deletions,
|
|
22013
|
-
files: childStats.files + cs.files
|
|
22014
|
-
} : { ...cs };
|
|
22015
|
-
}
|
|
22016
|
-
}
|
|
22017
|
-
const totalStats = stats ?? childStats;
|
|
22018
22358
|
const providers = await this.sdk.config.providers({ directory }).then((x) => x.data?.providers ?? []).catch(() => []);
|
|
22019
22359
|
const provider = providers.find((p) => p.id === msg.providerID);
|
|
22020
22360
|
const model = provider?.models[msg.modelID];
|
|
@@ -22025,13 +22365,12 @@ var Agent = class {
|
|
|
22025
22365
|
_meta.childCost = childCost;
|
|
22026
22366
|
_meta.childSessionCount = children.length;
|
|
22027
22367
|
}
|
|
22028
|
-
|
|
22029
|
-
|
|
22368
|
+
this.aggregateFileDiffStats(sessionId, children, _meta);
|
|
22369
|
+
this.aggregateAICodeChangeStats(sessionId, children, _meta);
|
|
22370
|
+
if (turnError) {
|
|
22371
|
+
_meta.error = turnError;
|
|
22030
22372
|
}
|
|
22031
|
-
|
|
22032
|
-
_meta.childCodeChange = childStats;
|
|
22033
|
-
}
|
|
22034
|
-
await this.sendToClient({
|
|
22373
|
+
await sendToClient(this.connection, {
|
|
22035
22374
|
sessionId,
|
|
22036
22375
|
update: {
|
|
22037
22376
|
sessionUpdate: "usage_update",
|
|
@@ -22043,6 +22382,56 @@ var Agent = class {
|
|
|
22043
22382
|
}).catch(() => {
|
|
22044
22383
|
});
|
|
22045
22384
|
}
|
|
22385
|
+
/**
|
|
22386
|
+
* Aggregate file diff stats (git-based) for parent + child sessions into _meta.
|
|
22387
|
+
* 主 session 的 session.diff 已包含子任务的变更数据,直接使用,不累加子 session。
|
|
22388
|
+
*/
|
|
22389
|
+
aggregateFileDiffStats(sessionId, children, _meta) {
|
|
22390
|
+
const stats = this.eventHandler.getFileDiffStats(sessionId);
|
|
22391
|
+
let childFileDiffStats;
|
|
22392
|
+
for (const childId of children) {
|
|
22393
|
+
const cs = this.eventHandler.getFileDiffStats(childId);
|
|
22394
|
+
if (cs) {
|
|
22395
|
+
childFileDiffStats = childFileDiffStats ? {
|
|
22396
|
+
additions: childFileDiffStats.additions + cs.additions,
|
|
22397
|
+
deletions: childFileDiffStats.deletions + cs.deletions,
|
|
22398
|
+
files: childFileDiffStats.files + cs.files
|
|
22399
|
+
} : { ...cs };
|
|
22400
|
+
}
|
|
22401
|
+
}
|
|
22402
|
+
const totalFileDiffStats = stats ?? childFileDiffStats;
|
|
22403
|
+
if (totalFileDiffStats) {
|
|
22404
|
+
_meta.fileDiffStats = totalFileDiffStats;
|
|
22405
|
+
}
|
|
22406
|
+
if (childFileDiffStats) {
|
|
22407
|
+
_meta.childFileDiffStats = childFileDiffStats;
|
|
22408
|
+
}
|
|
22409
|
+
}
|
|
22410
|
+
/**
|
|
22411
|
+
* Aggregate AI code change stats (tool-based) for parent + child sessions into _meta.
|
|
22412
|
+
*/
|
|
22413
|
+
aggregateAICodeChangeStats(sessionId, children, _meta) {
|
|
22414
|
+
const parentStats = this.eventHandler.getAICodeChangeStats(sessionId);
|
|
22415
|
+
let childAICodeChangeStats;
|
|
22416
|
+
for (const childId of children) {
|
|
22417
|
+
const cs = this.eventHandler.getAICodeChangeStats(childId);
|
|
22418
|
+
if (cs) {
|
|
22419
|
+
childAICodeChangeStats = childAICodeChangeStats ? {
|
|
22420
|
+
additions: childAICodeChangeStats.additions + cs.additions,
|
|
22421
|
+
deletions: childAICodeChangeStats.deletions + cs.deletions,
|
|
22422
|
+
files: childAICodeChangeStats.files + cs.files
|
|
22423
|
+
} : { ...cs };
|
|
22424
|
+
}
|
|
22425
|
+
}
|
|
22426
|
+
const totalAICodeChangeStats = parentStats ? childAICodeChangeStats ? {
|
|
22427
|
+
additions: parentStats.additions + childAICodeChangeStats.additions,
|
|
22428
|
+
deletions: parentStats.deletions + childAICodeChangeStats.deletions,
|
|
22429
|
+
files: parentStats.files + childAICodeChangeStats.files
|
|
22430
|
+
} : parentStats : childAICodeChangeStats;
|
|
22431
|
+
if (totalAICodeChangeStats) {
|
|
22432
|
+
_meta.aiCodeChange = totalAICodeChangeStats;
|
|
22433
|
+
}
|
|
22434
|
+
}
|
|
22046
22435
|
convertPromptParts(parts) {
|
|
22047
22436
|
const result = [];
|
|
22048
22437
|
for (const part of parts) {
|
|
@@ -22124,13 +22513,15 @@ var Agent = class {
|
|
|
22124
22513
|
}
|
|
22125
22514
|
}
|
|
22126
22515
|
buildUsage(msg) {
|
|
22516
|
+
const tokens = msg.tokens;
|
|
22517
|
+
if (!tokens) return void 0;
|
|
22127
22518
|
return {
|
|
22128
|
-
totalTokens:
|
|
22129
|
-
inputTokens:
|
|
22130
|
-
outputTokens:
|
|
22131
|
-
thoughtTokens:
|
|
22132
|
-
cachedReadTokens:
|
|
22133
|
-
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
|
|
22134
22525
|
};
|
|
22135
22526
|
}
|
|
22136
22527
|
};
|
|
@@ -22168,6 +22559,91 @@ function parseModelSelection(modelId, providers) {
|
|
|
22168
22559
|
}
|
|
22169
22560
|
|
|
22170
22561
|
// src/index.ts
|
|
22562
|
+
var nativeFetch = (input, init) => {
|
|
22563
|
+
return new Promise((resolve, reject) => {
|
|
22564
|
+
let url2;
|
|
22565
|
+
let requestMethod;
|
|
22566
|
+
let requestHeaders;
|
|
22567
|
+
let requestBody = null;
|
|
22568
|
+
if (typeof input === "string") {
|
|
22569
|
+
url2 = new URL(input);
|
|
22570
|
+
} else if (input instanceof URL) {
|
|
22571
|
+
url2 = input;
|
|
22572
|
+
} else {
|
|
22573
|
+
url2 = new URL(input.url);
|
|
22574
|
+
requestMethod = input.method;
|
|
22575
|
+
requestHeaders = input.headers;
|
|
22576
|
+
requestBody = input.body;
|
|
22577
|
+
}
|
|
22578
|
+
const method = (init?.method ?? requestMethod ?? "GET").toUpperCase();
|
|
22579
|
+
const mergedHeaders = new Headers(init?.headers ?? requestHeaders ?? void 0);
|
|
22580
|
+
const headerObj = {};
|
|
22581
|
+
mergedHeaders.forEach((value, key) => {
|
|
22582
|
+
headerObj[key] = value;
|
|
22583
|
+
});
|
|
22584
|
+
const body = init?.body !== void 0 ? init.body : requestBody;
|
|
22585
|
+
const signal = init?.signal;
|
|
22586
|
+
if (signal?.aborted) {
|
|
22587
|
+
return reject(
|
|
22588
|
+
signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted.", "AbortError")
|
|
22589
|
+
);
|
|
22590
|
+
}
|
|
22591
|
+
const mod = url2.protocol === "https:" ? https : http;
|
|
22592
|
+
const req = mod.request(
|
|
22593
|
+
url2,
|
|
22594
|
+
{
|
|
22595
|
+
method,
|
|
22596
|
+
headers: headerObj
|
|
22597
|
+
// Note: do NOT set `timeout` here. Leaving it unset means
|
|
22598
|
+
// node:http will not apply any inactivity-based abort.
|
|
22599
|
+
},
|
|
22600
|
+
(res) => {
|
|
22601
|
+
const respHeaders = new Headers();
|
|
22602
|
+
for (const [k, v] of Object.entries(res.headers)) {
|
|
22603
|
+
if (v == null) continue;
|
|
22604
|
+
if (Array.isArray(v)) {
|
|
22605
|
+
for (const item of v) respHeaders.append(k, item);
|
|
22606
|
+
} else {
|
|
22607
|
+
respHeaders.set(k, String(v));
|
|
22608
|
+
}
|
|
22609
|
+
}
|
|
22610
|
+
const webStream = import_node_stream.Readable.toWeb(res);
|
|
22611
|
+
resolve(
|
|
22612
|
+
new Response(webStream, {
|
|
22613
|
+
status: res.statusCode ?? 200,
|
|
22614
|
+
statusText: res.statusMessage ?? "",
|
|
22615
|
+
headers: respHeaders
|
|
22616
|
+
})
|
|
22617
|
+
);
|
|
22618
|
+
}
|
|
22619
|
+
);
|
|
22620
|
+
req.on("error", reject);
|
|
22621
|
+
if (signal) {
|
|
22622
|
+
signal.addEventListener(
|
|
22623
|
+
"abort",
|
|
22624
|
+
() => {
|
|
22625
|
+
req.destroy(
|
|
22626
|
+
signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted.", "AbortError")
|
|
22627
|
+
);
|
|
22628
|
+
},
|
|
22629
|
+
{ once: true }
|
|
22630
|
+
);
|
|
22631
|
+
}
|
|
22632
|
+
if (body == null) {
|
|
22633
|
+
req.end();
|
|
22634
|
+
} else if (typeof body === "string") {
|
|
22635
|
+
req.end(body);
|
|
22636
|
+
} else if (body instanceof Uint8Array || Buffer.isBuffer(body)) {
|
|
22637
|
+
req.end(body);
|
|
22638
|
+
} else if (body instanceof URLSearchParams) {
|
|
22639
|
+
req.end(body.toString());
|
|
22640
|
+
} else if (typeof body.pipeTo === "function") {
|
|
22641
|
+
import_node_stream.Readable.fromWeb(body).pipe(req);
|
|
22642
|
+
} else {
|
|
22643
|
+
req.end(String(body));
|
|
22644
|
+
}
|
|
22645
|
+
});
|
|
22646
|
+
};
|
|
22171
22647
|
function parseArgs(args) {
|
|
22172
22648
|
let server = "http://localhost:4096";
|
|
22173
22649
|
let cwd = process.cwd();
|
|
@@ -22207,7 +22683,10 @@ async function main() {
|
|
|
22207
22683
|
initLogger();
|
|
22208
22684
|
if (log) setLogEnabled(true);
|
|
22209
22685
|
sysLog("starting", { server, cwd });
|
|
22210
|
-
const sdk = createOpencodeClient({
|
|
22686
|
+
const sdk = createOpencodeClient({
|
|
22687
|
+
baseUrl: server,
|
|
22688
|
+
fetch: nativeFetch
|
|
22689
|
+
});
|
|
22211
22690
|
try {
|
|
22212
22691
|
await sdk.global.health();
|
|
22213
22692
|
sysLog("server_connected");
|