@harmonyos-arkts/opencode-acp 0.0.4 → 0.0.6

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 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
 
@@ -20279,65 +20284,6 @@ function applyStructuredPatch(source, patch, options = {}) {
20279
20284
 
20280
20285
  // src/utils.ts
20281
20286
  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
20287
 
20342
20288
  // src/logger.ts
20343
20289
  var import_fs = require("fs");
@@ -20403,13 +20349,18 @@ function sysLog(action, data) {
20403
20349
  write("system", action, data);
20404
20350
  }
20405
20351
  function sanitize(obj, depth = 0) {
20406
- if (!obj || depth > 5) return obj;
20352
+ if (!obj) return obj;
20353
+ if (depth > 5) return { __truncated__: true, __depth__: depth };
20407
20354
  const result = {};
20408
20355
  for (const [key, val] of Object.entries(obj)) {
20409
20356
  if (typeof val === "string" && val.length > 2e3) {
20410
20357
  result[key] = val.slice(0, 2e3) + `... [${val.length} chars total]`;
20411
20358
  } else if (val && typeof val === "object" && !Array.isArray(val)) {
20412
20359
  result[key] = sanitize(val, depth + 1);
20360
+ } else if (val && Array.isArray(val)) {
20361
+ result[key] = val.map(
20362
+ (item) => typeof item === "string" && item.length > 2e3 ? item.slice(0, 2e3) + `... [${item.length} chars total]` : item && typeof item === "object" ? sanitize(item, depth + 1) : item
20363
+ );
20413
20364
  } else {
20414
20365
  result[key] = val;
20415
20366
  }
@@ -20417,8 +20368,78 @@ function sanitize(obj, depth = 0) {
20417
20368
  return result;
20418
20369
  }
20419
20370
 
20371
+ // src/utils.ts
20372
+ function toToolKind(toolName) {
20373
+ const tool = toolName.toLowerCase();
20374
+ switch (tool) {
20375
+ case "bash":
20376
+ return "execute";
20377
+ case "webfetch":
20378
+ return "fetch";
20379
+ case "edit":
20380
+ case "patch":
20381
+ case "write":
20382
+ return "edit";
20383
+ case "grep":
20384
+ case "glob":
20385
+ case "context7_resolve_library_id":
20386
+ case "context7_get_library_docs":
20387
+ return "search";
20388
+ case "list":
20389
+ case "read":
20390
+ return "read";
20391
+ default:
20392
+ return "other";
20393
+ }
20394
+ }
20395
+ function toLocations(toolName, input) {
20396
+ const tool = toolName.toLowerCase();
20397
+ switch (tool) {
20398
+ case "read":
20399
+ case "edit":
20400
+ case "write":
20401
+ return input["filePath"] ? [{ path: input["filePath"] }] : [];
20402
+ case "glob":
20403
+ case "grep":
20404
+ return input["path"] ? [{ path: input["path"] }] : [];
20405
+ case "list":
20406
+ return input["path"] ? [{ path: input["path"] }] : [];
20407
+ default:
20408
+ return [];
20409
+ }
20410
+ }
20411
+ function parseUri(uri) {
20412
+ try {
20413
+ if (uri.startsWith("file://")) {
20414
+ const path = uri.slice(7);
20415
+ const name = path.split("/").pop() || path;
20416
+ return { type: "file", url: uri, filename: name, mime: "text/plain" };
20417
+ }
20418
+ if (uri.startsWith("zed://")) {
20419
+ const url2 = new URL(uri);
20420
+ const path = url2.searchParams.get("path");
20421
+ if (path) {
20422
+ const name = path.split("/").pop() || path;
20423
+ return { type: "file", url: (0, import_url.pathToFileURL)(path).href, filename: name, mime: "text/plain" };
20424
+ }
20425
+ }
20426
+ return { type: "text", text: uri };
20427
+ } catch {
20428
+ return { type: "text", text: uri };
20429
+ }
20430
+ }
20431
+ async function sendToClient(connection, params) {
20432
+ const updateType = params.update.sessionUpdate;
20433
+ const update = params.update;
20434
+ acpOut(`sessionUpdate.${updateType}`, {
20435
+ 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" ? { used: update.used, size: update.size, cost: update.cost, _meta: update._meta } : {}
20437
+ });
20438
+ return connection.sessionUpdate(params);
20439
+ }
20440
+
20420
20441
  // src/event-handler.ts
20421
- var QUESTION_TIMEOUT_MS = 6e4;
20442
+ var QUESTION_TIMEOUT_MS = 3e5;
20422
20443
  var permissionOptions = [
20423
20444
  { optionId: "once", kind: "allow_once", name: "Allow once" },
20424
20445
  { optionId: "always", kind: "allow_always", name: "Always allow" },
@@ -20438,28 +20459,10 @@ var EventHandler = class {
20438
20459
  partMetaIndex = /* @__PURE__ */ new Map();
20439
20460
  /** Track tool call counts per child session to detect completion. */
20440
20461
  childToolCounts = /* @__PURE__ */ new Map();
20441
- /** Cumulative code change stats per session, aggregated from session.diff SSE events. */
20442
- diffStats = /* @__PURE__ */ new Map();
20443
- /**
20444
- * Wrapper around connection.sessionUpdate that logs ALL outgoing traffic to the ACP client.
20445
- * Every call here corresponds to a real message sent over the wire.
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
- }
20462
+ /** Cumulative file diff stats per session, aggregated from session.diff SSE events. */
20463
+ fileDiffStats = /* @__PURE__ */ new Map();
20464
+ /** AI code change stats per session, keyed by sessionId → filePath → { additions, deletions }. */
20465
+ aiCodeChangeStats = /* @__PURE__ */ new Map();
20463
20466
  constructor(deps) {
20464
20467
  this.connection = deps.connection;
20465
20468
  this.sdk = deps.sdk;
@@ -20602,15 +20605,26 @@ var EventHandler = class {
20602
20605
  }
20603
20606
  case "question.asked": {
20604
20607
  const q = event.properties;
20605
- const resolved = this.resolveSession(q.sessionID);
20606
- if (!resolved) return;
20608
+ ocEvent("question.asked", event);
20609
+ let resolved = this.resolveSession(q.sessionID);
20610
+ if (!resolved) {
20611
+ console.warn(`[event-handler] question.asked: session ${q.sessionID} not in manager, attempting auto-register`);
20612
+ try {
20613
+ const sessionResp = await this.sdk.session.get({ sessionID: q.sessionID, directory: "" });
20614
+ const fetchedSession = sessionResp.data;
20615
+ if (fetchedSession?.parentID) {
20616
+ this.sessionManager.registerDiscovered(fetchedSession.id, fetchedSession.parentID, fetchedSession.title);
20617
+ resolved = this.resolveSession(q.sessionID);
20618
+ } else if (fetchedSession) {
20619
+ console.warn(`[event-handler] question.asked: top-level session ${q.sessionID} not registered; was agent.newSession called?`);
20620
+ }
20621
+ } catch (err) {
20622
+ console.error(`[event-handler] question.asked: failed to fetch session ${q.sessionID}:`, err);
20623
+ }
20624
+ if (!resolved) return;
20625
+ }
20607
20626
  const directory = resolved.cwd;
20608
20627
  const sessionId = resolved.sessionId;
20609
- ocEvent("question.asked", {
20610
- id: q.id,
20611
- sessionID: q.sessionID,
20612
- questions: q.questions.length
20613
- });
20614
20628
  const prev = this.questionQueues.get(q.sessionID) ?? Promise.resolve();
20615
20629
  const next = prev.then(async () => {
20616
20630
  const extResult = await Promise.race([
@@ -20630,7 +20644,7 @@ var EventHandler = class {
20630
20644
  if (!extResult || !extResult.answers) {
20631
20645
  await this.sdk.question.reject({ requestID: q.id, directory }).catch(() => {
20632
20646
  });
20633
- ocEvent("question.rejected", { requestID: q.id });
20647
+ sysLog("question.rejected", { requestID: q.id });
20634
20648
  return;
20635
20649
  }
20636
20650
  const answers = extResult.answers;
@@ -20638,7 +20652,7 @@ var EventHandler = class {
20638
20652
  console.error("[event-handler] invalid answers shape from client:", JSON.stringify(answers));
20639
20653
  await this.sdk.question.reject({ requestID: q.id, directory }).catch(() => {
20640
20654
  });
20641
- ocEvent("question.rejected", {
20655
+ sysLog("question.rejected", {
20642
20656
  requestID: q.id,
20643
20657
  reason: "invalid_answers"
20644
20658
  });
@@ -20649,7 +20663,7 @@ var EventHandler = class {
20649
20663
  directory,
20650
20664
  answers
20651
20665
  });
20652
- ocEvent("question.reply", { requestID: q.id, answers });
20666
+ sysLog("question.reply", { requestID: q.id, answers });
20653
20667
  }).catch((err) => {
20654
20668
  console.error("[event-handler] question handling error:", err);
20655
20669
  }).finally(() => {
@@ -20673,7 +20687,7 @@ var EventHandler = class {
20673
20687
  await this.handleToolPart(resolved.sessionId, part);
20674
20688
  }
20675
20689
  if (part.type === "text" && typeof part.text === "string" && part.ignored === true) {
20676
- await this.sendToClient({
20690
+ await sendToClient(this.connection, {
20677
20691
  sessionId: resolved.sessionId,
20678
20692
  update: {
20679
20693
  sessionUpdate: "agent_message_chunk",
@@ -20694,7 +20708,7 @@ var EventHandler = class {
20694
20708
  const partMeta = this.partMetaIndex.get(props.partID);
20695
20709
  if (!partMeta) return;
20696
20710
  if (partMeta.type === "text" && props.field === "text" && partMeta.ignored !== true) {
20697
- await this.sendToClient({
20711
+ await sendToClient(this.connection, {
20698
20712
  sessionId,
20699
20713
  update: {
20700
20714
  sessionUpdate: "agent_message_chunk",
@@ -20706,7 +20720,7 @@ var EventHandler = class {
20706
20720
  return;
20707
20721
  }
20708
20722
  if (partMeta.type === "reasoning" && props.field === "text") {
20709
- await this.sendToClient({
20723
+ await sendToClient(this.connection, {
20710
20724
  sessionId,
20711
20725
  update: {
20712
20726
  sessionUpdate: "agent_thought_chunk",
@@ -20731,7 +20745,7 @@ var EventHandler = class {
20731
20745
  additions += d.additions;
20732
20746
  deletions += d.deletions;
20733
20747
  }
20734
- this.diffStats.set(resolved.sessionId, { additions, deletions, files: diff.length });
20748
+ this.fileDiffStats.set(resolved.sessionId, { additions, deletions, files: diff.length });
20735
20749
  return;
20736
20750
  }
20737
20751
  default: {
@@ -20747,7 +20761,7 @@ var EventHandler = class {
20747
20761
  * and _meta containing structured metadata about the subagent.
20748
20762
  */
20749
20763
  async announceChildSession(childSessionId, parentSessionId, title, meta3) {
20750
- await this.sendToClient({
20764
+ await sendToClient(this.connection, {
20751
20765
  sessionId: childSessionId,
20752
20766
  update: {
20753
20767
  sessionUpdate: "session_info_update",
@@ -20780,7 +20794,7 @@ var EventHandler = class {
20780
20794
  });
20781
20795
  }
20782
20796
  }
20783
- await this.sendToClient({
20797
+ await sendToClient(this.connection, {
20784
20798
  sessionId,
20785
20799
  update: {
20786
20800
  sessionUpdate: "tool_call",
@@ -20812,7 +20826,7 @@ var EventHandler = class {
20812
20826
  content: { type: "text", text: output }
20813
20827
  });
20814
20828
  }
20815
- await this.sendToClient({
20829
+ await sendToClient(this.connection, {
20816
20830
  sessionId,
20817
20831
  update: {
20818
20832
  sessionUpdate: "tool_call_update",
@@ -20865,12 +20879,13 @@ var EventHandler = class {
20865
20879
  const oldText = typeof input["oldString"] === "string" ? input["oldString"] : "";
20866
20880
  const newText = typeof input["newString"] === "string" ? input["newString"] : typeof input["content"] === "string" ? input["content"] : "";
20867
20881
  content.push({ type: "diff", path: filePath, oldText, newText });
20882
+ this.accumulateAICodeChangeStats(sessionId, part);
20868
20883
  }
20869
20884
  if (part.tool === "todowrite") {
20870
20885
  try {
20871
20886
  const todos = JSON.parse(part.state.output);
20872
20887
  if (Array.isArray(todos)) {
20873
- await this.sendToClient({
20888
+ await sendToClient(this.connection, {
20874
20889
  sessionId,
20875
20890
  update: {
20876
20891
  sessionUpdate: "plan",
@@ -20886,7 +20901,7 @@ var EventHandler = class {
20886
20901
  } catch {
20887
20902
  }
20888
20903
  }
20889
- await this.sendToClient({
20904
+ await sendToClient(this.connection, {
20890
20905
  sessionId,
20891
20906
  update: {
20892
20907
  sessionUpdate: "tool_call_update",
@@ -20908,7 +20923,7 @@ var EventHandler = class {
20908
20923
  case "error": {
20909
20924
  this.toolStarts.delete(part.callID);
20910
20925
  this.bashSnapshots.delete(part.callID);
20911
- await this.sendToClient({
20926
+ await sendToClient(this.connection, {
20912
20927
  sessionId,
20913
20928
  update: {
20914
20929
  sessionUpdate: "tool_call_update",
@@ -20943,8 +20958,7 @@ var EventHandler = class {
20943
20958
  const agentMatch = title.match(/@(\w+)\s+subagent/);
20944
20959
  const agentType = agentMatch?.[1];
20945
20960
  const description = title.replace(/\s*\(@\w+\s+subagent\)\s*$/, "");
20946
- const childDiffStats = this.diffStats.get(childSessionId);
20947
- await this.sendToClient({
20961
+ await sendToClient(this.connection, {
20948
20962
  sessionId: childSessionId,
20949
20963
  update: {
20950
20964
  sessionUpdate: "session_info_update",
@@ -20956,8 +20970,7 @@ var EventHandler = class {
20956
20970
  toolCallCount,
20957
20971
  durationMs,
20958
20972
  ...agentType && { agentType },
20959
- ...description && { description },
20960
- ...childDiffStats && { codeChange: childDiffStats }
20973
+ ...description && { description }
20961
20974
  }
20962
20975
  }
20963
20976
  });
@@ -20969,6 +20982,44 @@ var EventHandler = class {
20969
20982
  if (typeof output !== "string") return;
20970
20983
  return output;
20971
20984
  }
20985
+ // ─── Statistics Accessors ────────────────────────────────────────
20986
+ getFileDiffStats(sessionId) {
20987
+ return this.fileDiffStats.get(sessionId);
20988
+ }
20989
+ setFileDiffStats(sessionId, stats) {
20990
+ if (this.fileDiffStats.has(sessionId)) return;
20991
+ this.fileDiffStats.set(sessionId, stats);
20992
+ }
20993
+ /**
20994
+ * Accumulate AI code change stats from a completed tool part's metadata.
20995
+ * The harmony-code plugin injects `metadata.aiCodeChange` for edit/write tools.
20996
+ */
20997
+ accumulateAICodeChangeStats(sessionId, part) {
20998
+ if (part.state.status !== "completed") return;
20999
+ const meta3 = part.state.metadata ?? {};
21000
+ const aiCodeChange = meta3["aiCodeChange"];
21001
+ if (!aiCodeChange || aiCodeChange.additions === 0 && aiCodeChange.deletions === 0) return;
21002
+ const fileMap = this.aiCodeChangeStats.get(sessionId) ?? /* @__PURE__ */ new Map();
21003
+ const existing = fileMap.get(aiCodeChange.file) ?? { additions: 0, deletions: 0 };
21004
+ fileMap.set(aiCodeChange.file, {
21005
+ additions: existing.additions + aiCodeChange.additions,
21006
+ deletions: existing.deletions + aiCodeChange.deletions
21007
+ });
21008
+ if (!this.aiCodeChangeStats.has(sessionId)) {
21009
+ this.aiCodeChangeStats.set(sessionId, fileMap);
21010
+ }
21011
+ }
21012
+ getAICodeChangeStats(sessionId) {
21013
+ const fileMap = this.aiCodeChangeStats.get(sessionId);
21014
+ if (!fileMap || fileMap.size === 0) return void 0;
21015
+ let additions = 0;
21016
+ let deletions = 0;
21017
+ for (const stats of fileMap.values()) {
21018
+ additions += stats.additions;
21019
+ deletions += stats.deletions;
21020
+ }
21021
+ return { additions, deletions, files: fileMap.size };
21022
+ }
20972
21023
  };
20973
21024
  function simpleHash(str) {
20974
21025
  let hash2 = 0;
@@ -21072,31 +21123,6 @@ var Agent = class {
21072
21123
  eventHandler;
21073
21124
  authProvider;
21074
21125
  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
- }
21100
21126
  constructor(config2) {
21101
21127
  this.config = config2;
21102
21128
  this.sdk = config2.sdk;
@@ -21317,13 +21343,14 @@ var Agent = class {
21317
21343
  }
21318
21344
  }
21319
21345
  async loadSession(params) {
21346
+ acpIn("loadSession", { sessionId: params.sessionId, cwd: params.cwd });
21320
21347
  try {
21321
21348
  const directory = params.cwd;
21322
21349
  const sessionId = params.sessionId;
21323
21350
  const model = await this.defaultModel(directory);
21324
21351
  const { session } = await this.sessionManager.load(sessionId, params.cwd, params.mcpServers, model);
21325
21352
  if (session.summary) {
21326
- this.eventHandler.setDiffStats(sessionId, {
21353
+ this.eventHandler.setFileDiffStats(sessionId, {
21327
21354
  additions: session.summary.additions,
21328
21355
  deletions: session.summary.deletions,
21329
21356
  files: session.summary.files
@@ -21364,6 +21391,7 @@ var Agent = class {
21364
21391
  }
21365
21392
  }
21366
21393
  async listSessions(params) {
21394
+ acpIn("listSessions", { cwd: params.cwd, cursor: params.cursor });
21367
21395
  const limit = 100;
21368
21396
  const cursor = params.cursor ? Number(params.cursor) : void 0;
21369
21397
  const sessions = await this.sdk.session.list({ directory: params.cwd ?? void 0, roots: true }).then((x) => x.data ?? []);
@@ -21380,9 +21408,11 @@ var Agent = class {
21380
21408
  const next = filtered.length > limit && last ? String(last.time.updated) : void 0;
21381
21409
  const response = { sessions: entries };
21382
21410
  if (next) response.nextCursor = next;
21411
+ acpOut("listSessions.response", { count: entries.length, hasNext: !!next });
21383
21412
  return response;
21384
21413
  }
21385
21414
  async unstable_forkSession(params) {
21415
+ acpIn("forkSession", { sessionId: params.sessionId, cwd: params.cwd });
21386
21416
  try {
21387
21417
  const directory = params.cwd;
21388
21418
  const mcpServers = params.mcpServers ?? [];
@@ -21408,6 +21438,7 @@ var Agent = class {
21408
21438
  }
21409
21439
  }
21410
21440
  async unstable_resumeSession(params) {
21441
+ acpIn("resumeSession", { sessionId: params.sessionId, cwd: params.cwd });
21411
21442
  try {
21412
21443
  const directory = params.cwd;
21413
21444
  const sessionId = params.sessionId;
@@ -21415,7 +21446,7 @@ var Agent = class {
21415
21446
  const model = await this.defaultModel(directory);
21416
21447
  const { session } = await this.sessionManager.load(sessionId, directory, mcpServers, model);
21417
21448
  if (session.summary) {
21418
- this.eventHandler.setDiffStats(sessionId, {
21449
+ this.eventHandler.setFileDiffStats(sessionId, {
21419
21450
  additions: session.summary.additions,
21420
21451
  deletions: session.summary.deletions,
21421
21452
  files: session.summary.files
@@ -21460,6 +21491,16 @@ var Agent = class {
21460
21491
  agent,
21461
21492
  directory
21462
21493
  });
21494
+ const rawErr = response2.error;
21495
+ ocCall("session.prompt.raw", {
21496
+ hasData: !!response2.data,
21497
+ hasError: !!rawErr,
21498
+ errorName: rawErr?.name,
21499
+ errorMessage: rawErr?.message,
21500
+ errorCode: rawErr?.code ?? rawErr?.cause?.code,
21501
+ errorCauseName: rawErr?.cause?.name,
21502
+ errorCauseMessage: rawErr?.cause?.message
21503
+ });
21463
21504
  const msg2 = response2.data?.info;
21464
21505
  ocCall("session.prompt.response", {
21465
21506
  messageId: msg2?.id,
@@ -21546,6 +21587,7 @@ var Agent = class {
21546
21587
  }
21547
21588
  // ─── Configuration ────────────────────────────────────────────────
21548
21589
  async setSessionMode(params) {
21590
+ acpIn("setSessionMode", { sessionId: params.sessionId, modeId: params.modeId });
21549
21591
  const session = this.sessionManager.get(params.sessionId);
21550
21592
  const agents = await this.sdk.app.agents({ directory: session.cwd }).then((x) => x.data ?? []).catch(() => []);
21551
21593
  const availableModes = agents.filter((a) => a.mode !== "subagent" && !a.hidden);
@@ -21555,6 +21597,7 @@ var Agent = class {
21555
21597
  this.sessionManager.setMode(params.sessionId, params.modeId);
21556
21598
  }
21557
21599
  async unstable_setSessionModel(params) {
21600
+ acpIn("setSessionModel", { sessionId: params.sessionId, modelId: params.modelId });
21558
21601
  const session = this.sessionManager.get(params.sessionId);
21559
21602
  const providers = await this.sdk.config.providers({ directory: session.cwd }).then((x) => x.data?.providers ?? []).catch(() => []);
21560
21603
  const selection = parseModelSelection(params.modelId, providers);
@@ -21568,6 +21611,7 @@ var Agent = class {
21568
21611
  };
21569
21612
  }
21570
21613
  async setSessionConfigOption(params) {
21614
+ acpIn("setSessionConfigOption", { sessionId: params.sessionId, configId: params.configId, value: params.value });
21571
21615
  const session = this.sessionManager.get(params.sessionId);
21572
21616
  const providers = await this.sdk.config.providers({ directory: session.cwd }).then((x) => x.data?.providers ?? []).catch(() => []);
21573
21617
  const sortedProviders = [...providers].sort(
@@ -21653,7 +21697,7 @@ var Agent = class {
21653
21697
  if (!childMessages?.length) return;
21654
21698
  const title = `Subagent (${childSessionId.slice(0, 8)})`;
21655
21699
  this.sessionManager.registerDiscovered(childSessionId, parentSessionId, title);
21656
- await this.sendToClient({
21700
+ await sendToClient(this.connection, {
21657
21701
  sessionId: childSessionId,
21658
21702
  update: {
21659
21703
  sessionUpdate: "session_info_update",
@@ -21770,7 +21814,7 @@ var Agent = class {
21770
21814
  availableCommands.push({ name: "compact", description: "compact the session" });
21771
21815
  }
21772
21816
  setTimeout(() => {
21773
- this.sendToClient({
21817
+ sendToClient(this.connection, {
21774
21818
  sessionId: params.sessionId,
21775
21819
  update: {
21776
21820
  sessionUpdate: "available_commands_update",
@@ -21823,7 +21867,7 @@ var Agent = class {
21823
21867
  await this.replayToolPart(sessionId, part);
21824
21868
  } else if (part.type === "text" && part.text) {
21825
21869
  const audience = part.synthetic ? ["assistant"] : part.ignored ? ["user"] : void 0;
21826
- await this.sendToClient({
21870
+ await sendToClient(this.connection, {
21827
21871
  sessionId,
21828
21872
  update: {
21829
21873
  sessionUpdate: messageChunk,
@@ -21841,7 +21885,7 @@ var Agent = class {
21841
21885
  const filename = part.filename ?? "file";
21842
21886
  const mime = part.mime || "application/octet-stream";
21843
21887
  if (url2.startsWith("file://")) {
21844
- await this.sendToClient({
21888
+ await sendToClient(this.connection, {
21845
21889
  sessionId,
21846
21890
  update: {
21847
21891
  sessionUpdate: messageChunk,
@@ -21856,7 +21900,7 @@ var Agent = class {
21856
21900
  const base64Data = base64Match?.[2] ?? "";
21857
21901
  const effectiveMime = dataMime || mime;
21858
21902
  if (effectiveMime.startsWith("image/")) {
21859
- await this.sendToClient({
21903
+ await sendToClient(this.connection, {
21860
21904
  sessionId,
21861
21905
  update: {
21862
21906
  sessionUpdate: messageChunk,
@@ -21878,7 +21922,7 @@ var Agent = class {
21878
21922
  mimeType: effectiveMime,
21879
21923
  text: Buffer.from(base64Data, "base64").toString("utf-8")
21880
21924
  } : { uri: fileUri, mimeType: effectiveMime, blob: base64Data };
21881
- await this.sendToClient({
21925
+ await sendToClient(this.connection, {
21882
21926
  sessionId,
21883
21927
  update: {
21884
21928
  sessionUpdate: messageChunk,
@@ -21890,7 +21934,7 @@ var Agent = class {
21890
21934
  }
21891
21935
  }
21892
21936
  } else if (part.type === "reasoning" && part.text) {
21893
- await this.sendToClient({
21937
+ await sendToClient(this.connection, {
21894
21938
  sessionId,
21895
21939
  update: {
21896
21940
  sessionUpdate: "agent_thought_chunk",
@@ -21903,7 +21947,7 @@ var Agent = class {
21903
21947
  }
21904
21948
  }
21905
21949
  async replayToolPart(sessionId, part) {
21906
- await this.sendToClient({
21950
+ await sendToClient(this.connection, {
21907
21951
  sessionId,
21908
21952
  update: {
21909
21953
  sessionUpdate: "tool_call",
@@ -21928,12 +21972,13 @@ var Agent = class {
21928
21972
  oldText: typeof input["oldString"] === "string" ? input["oldString"] : "",
21929
21973
  newText: typeof input["newString"] === "string" ? input["newString"] : typeof input["content"] === "string" ? input["content"] : ""
21930
21974
  });
21975
+ this.eventHandler.accumulateAICodeChangeStats(sessionId, part);
21931
21976
  }
21932
21977
  if (part.tool === "todowrite") {
21933
21978
  try {
21934
21979
  const todos = JSON.parse(part.state.output);
21935
21980
  if (Array.isArray(todos)) {
21936
- await this.sendToClient({
21981
+ await sendToClient(this.connection, {
21937
21982
  sessionId,
21938
21983
  update: {
21939
21984
  sessionUpdate: "plan",
@@ -21949,7 +21994,7 @@ var Agent = class {
21949
21994
  } catch {
21950
21995
  }
21951
21996
  }
21952
- await this.sendToClient({
21997
+ await sendToClient(this.connection, {
21953
21998
  sessionId,
21954
21999
  update: {
21955
22000
  sessionUpdate: "tool_call_update",
@@ -21966,7 +22011,7 @@ var Agent = class {
21966
22011
  break;
21967
22012
  }
21968
22013
  case "error":
21969
- await this.sendToClient({
22014
+ await sendToClient(this.connection, {
21970
22015
  sessionId,
21971
22016
  update: {
21972
22017
  sessionUpdate: "tool_call_update",
@@ -22002,19 +22047,6 @@ var Agent = class {
22002
22047
  childCost += childMessages.filter((m) => m.info.role === "assistant").reduce((sum, m) => sum + (m.info.cost ?? 0), 0);
22003
22048
  }
22004
22049
  }
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
22050
  const providers = await this.sdk.config.providers({ directory }).then((x) => x.data?.providers ?? []).catch(() => []);
22019
22051
  const provider = providers.find((p) => p.id === msg.providerID);
22020
22052
  const model = provider?.models[msg.modelID];
@@ -22025,13 +22057,9 @@ var Agent = class {
22025
22057
  _meta.childCost = childCost;
22026
22058
  _meta.childSessionCount = children.length;
22027
22059
  }
22028
- if (totalStats) {
22029
- _meta.codeChange = totalStats;
22030
- }
22031
- if (childStats) {
22032
- _meta.childCodeChange = childStats;
22033
- }
22034
- await this.sendToClient({
22060
+ this.aggregateFileDiffStats(sessionId, children, _meta);
22061
+ this.aggregateAICodeChangeStats(sessionId, children, _meta);
22062
+ await sendToClient(this.connection, {
22035
22063
  sessionId,
22036
22064
  update: {
22037
22065
  sessionUpdate: "usage_update",
@@ -22043,6 +22071,56 @@ var Agent = class {
22043
22071
  }).catch(() => {
22044
22072
  });
22045
22073
  }
22074
+ /**
22075
+ * Aggregate file diff stats (git-based) for parent + child sessions into _meta.
22076
+ * 主 session 的 session.diff 已包含子任务的变更数据,直接使用,不累加子 session。
22077
+ */
22078
+ aggregateFileDiffStats(sessionId, children, _meta) {
22079
+ const stats = this.eventHandler.getFileDiffStats(sessionId);
22080
+ let childFileDiffStats;
22081
+ for (const childId of children) {
22082
+ const cs = this.eventHandler.getFileDiffStats(childId);
22083
+ if (cs) {
22084
+ childFileDiffStats = childFileDiffStats ? {
22085
+ additions: childFileDiffStats.additions + cs.additions,
22086
+ deletions: childFileDiffStats.deletions + cs.deletions,
22087
+ files: childFileDiffStats.files + cs.files
22088
+ } : { ...cs };
22089
+ }
22090
+ }
22091
+ const totalFileDiffStats = stats ?? childFileDiffStats;
22092
+ if (totalFileDiffStats) {
22093
+ _meta.fileDiffStats = totalFileDiffStats;
22094
+ }
22095
+ if (childFileDiffStats) {
22096
+ _meta.childFileDiffStats = childFileDiffStats;
22097
+ }
22098
+ }
22099
+ /**
22100
+ * Aggregate AI code change stats (tool-based) for parent + child sessions into _meta.
22101
+ */
22102
+ aggregateAICodeChangeStats(sessionId, children, _meta) {
22103
+ const parentStats = this.eventHandler.getAICodeChangeStats(sessionId);
22104
+ let childAICodeChangeStats;
22105
+ for (const childId of children) {
22106
+ const cs = this.eventHandler.getAICodeChangeStats(childId);
22107
+ if (cs) {
22108
+ childAICodeChangeStats = childAICodeChangeStats ? {
22109
+ additions: childAICodeChangeStats.additions + cs.additions,
22110
+ deletions: childAICodeChangeStats.deletions + cs.deletions,
22111
+ files: childAICodeChangeStats.files + cs.files
22112
+ } : { ...cs };
22113
+ }
22114
+ }
22115
+ const totalAICodeChangeStats = parentStats ? childAICodeChangeStats ? {
22116
+ additions: parentStats.additions + childAICodeChangeStats.additions,
22117
+ deletions: parentStats.deletions + childAICodeChangeStats.deletions,
22118
+ files: parentStats.files + childAICodeChangeStats.files
22119
+ } : parentStats : childAICodeChangeStats;
22120
+ if (totalAICodeChangeStats) {
22121
+ _meta.aiCodeChange = totalAICodeChangeStats;
22122
+ }
22123
+ }
22046
22124
  convertPromptParts(parts) {
22047
22125
  const result = [];
22048
22126
  for (const part of parts) {
@@ -22168,6 +22246,91 @@ function parseModelSelection(modelId, providers) {
22168
22246
  }
22169
22247
 
22170
22248
  // src/index.ts
22249
+ var nativeFetch = (input, init) => {
22250
+ return new Promise((resolve, reject) => {
22251
+ let url2;
22252
+ let requestMethod;
22253
+ let requestHeaders;
22254
+ let requestBody = null;
22255
+ if (typeof input === "string") {
22256
+ url2 = new URL(input);
22257
+ } else if (input instanceof URL) {
22258
+ url2 = input;
22259
+ } else {
22260
+ url2 = new URL(input.url);
22261
+ requestMethod = input.method;
22262
+ requestHeaders = input.headers;
22263
+ requestBody = input.body;
22264
+ }
22265
+ const method = (init?.method ?? requestMethod ?? "GET").toUpperCase();
22266
+ const mergedHeaders = new Headers(init?.headers ?? requestHeaders ?? void 0);
22267
+ const headerObj = {};
22268
+ mergedHeaders.forEach((value, key) => {
22269
+ headerObj[key] = value;
22270
+ });
22271
+ const body = init?.body !== void 0 ? init.body : requestBody;
22272
+ const signal = init?.signal;
22273
+ if (signal?.aborted) {
22274
+ return reject(
22275
+ signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted.", "AbortError")
22276
+ );
22277
+ }
22278
+ const mod = url2.protocol === "https:" ? https : http;
22279
+ const req = mod.request(
22280
+ url2,
22281
+ {
22282
+ method,
22283
+ headers: headerObj
22284
+ // Note: do NOT set `timeout` here. Leaving it unset means
22285
+ // node:http will not apply any inactivity-based abort.
22286
+ },
22287
+ (res) => {
22288
+ const respHeaders = new Headers();
22289
+ for (const [k, v] of Object.entries(res.headers)) {
22290
+ if (v == null) continue;
22291
+ if (Array.isArray(v)) {
22292
+ for (const item of v) respHeaders.append(k, item);
22293
+ } else {
22294
+ respHeaders.set(k, String(v));
22295
+ }
22296
+ }
22297
+ const webStream = import_node_stream.Readable.toWeb(res);
22298
+ resolve(
22299
+ new Response(webStream, {
22300
+ status: res.statusCode ?? 200,
22301
+ statusText: res.statusMessage ?? "",
22302
+ headers: respHeaders
22303
+ })
22304
+ );
22305
+ }
22306
+ );
22307
+ req.on("error", reject);
22308
+ if (signal) {
22309
+ signal.addEventListener(
22310
+ "abort",
22311
+ () => {
22312
+ req.destroy(
22313
+ signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted.", "AbortError")
22314
+ );
22315
+ },
22316
+ { once: true }
22317
+ );
22318
+ }
22319
+ if (body == null) {
22320
+ req.end();
22321
+ } else if (typeof body === "string") {
22322
+ req.end(body);
22323
+ } else if (body instanceof Uint8Array || Buffer.isBuffer(body)) {
22324
+ req.end(body);
22325
+ } else if (body instanceof URLSearchParams) {
22326
+ req.end(body.toString());
22327
+ } else if (typeof body.pipeTo === "function") {
22328
+ import_node_stream.Readable.fromWeb(body).pipe(req);
22329
+ } else {
22330
+ req.end(String(body));
22331
+ }
22332
+ });
22333
+ };
22171
22334
  function parseArgs(args) {
22172
22335
  let server = "http://localhost:4096";
22173
22336
  let cwd = process.cwd();
@@ -22207,7 +22370,10 @@ async function main() {
22207
22370
  initLogger();
22208
22371
  if (log) setLogEnabled(true);
22209
22372
  sysLog("starting", { server, cwd });
22210
- const sdk = createOpencodeClient({ baseUrl: server });
22373
+ const sdk = createOpencodeClient({
22374
+ baseUrl: server,
22375
+ fetch: nativeFetch
22376
+ });
22211
22377
  try {
22212
22378
  await sdk.global.health();
22213
22379
  sysLog("server_connected");