@harmonyos-arkts/opencode-acp 0.0.3 → 0.0.4

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
@@ -19631,9 +19631,11 @@ var SessionManager = class {
19631
19631
  /**
19632
19632
  * Load an existing session into the manager.
19633
19633
  * Called by agent.loadSession() / agent.unstable_resumeSession().
19634
+ * Returns the session state plus the raw Session object from OpenCode.
19634
19635
  */
19635
19636
  async load(sessionId, cwd, mcpServers, model) {
19636
- await this.sdk.session.get({ sessionID: sessionId, directory: cwd }, { throwOnError: true });
19637
+ const response = await this.sdk.session.get({ sessionID: sessionId, directory: cwd }, { throwOnError: true });
19638
+ const session = response.data;
19637
19639
  const state = {
19638
19640
  id: sessionId,
19639
19641
  cwd,
@@ -19642,7 +19644,7 @@ var SessionManager = class {
19642
19644
  model
19643
19645
  };
19644
19646
  this.sessions.set(sessionId, state);
19645
- return state;
19647
+ return { state, session };
19646
19648
  }
19647
19649
  /**
19648
19650
  * Auto-register a session discovered via SSE events.
@@ -20401,7 +20403,7 @@ function sysLog(action, data) {
20401
20403
  write("system", action, data);
20402
20404
  }
20403
20405
  function sanitize(obj, depth = 0) {
20404
- if (!obj || depth > 2) return obj;
20406
+ if (!obj || depth > 5) return obj;
20405
20407
  const result = {};
20406
20408
  for (const [key, val] of Object.entries(obj)) {
20407
20409
  if (typeof val === "string" && val.length > 2e3) {
@@ -20414,33 +20416,6 @@ function sanitize(obj, depth = 0) {
20414
20416
  }
20415
20417
  return result;
20416
20418
  }
20417
- function acpAssembled(action, data) {
20418
- if (!enabled) return;
20419
- const entry = {
20420
- ts: (/* @__PURE__ */ new Date()).toISOString(),
20421
- cat: "acp.out",
20422
- action,
20423
- ...data && { data: sanitizeAssembled(data) }
20424
- };
20425
- try {
20426
- (0, import_fs.appendFileSync)(logFile, JSON.stringify(entry) + "\n");
20427
- } catch {
20428
- }
20429
- }
20430
- function sanitizeAssembled(obj, depth = 0) {
20431
- if (!obj || depth > 2) return obj;
20432
- const result = {};
20433
- for (const [key, val] of Object.entries(obj)) {
20434
- if (typeof val === "string" && val.length > 1e4) {
20435
- result[key] = val.slice(0, 1e4) + `... [${val.length} chars total]`;
20436
- } else if (val && typeof val === "object" && !Array.isArray(val)) {
20437
- result[key] = sanitizeAssembled(val, depth + 1);
20438
- } else {
20439
- result[key] = val;
20440
- }
20441
- }
20442
- return result;
20443
- }
20444
20419
 
20445
20420
  // src/event-handler.ts
20446
20421
  var QUESTION_TIMEOUT_MS = 6e4;
@@ -20459,130 +20434,31 @@ var EventHandler = class {
20459
20434
  permissionQueues = /* @__PURE__ */ new Map();
20460
20435
  questionQueues = /* @__PURE__ */ new Map();
20461
20436
  started = false;
20462
- messageBuffers = /* @__PURE__ */ new Map();
20463
- /** Index: sessionId Set<messageId> for O(1) flush lookup. */
20464
- sessionBufferIndex = /* @__PURE__ */ new Map();
20465
- messageMetaCache = /* @__PURE__ */ new Map();
20437
+ /** partID { type, ignored }, built from message.part.updated SSE events. */
20438
+ partMetaIndex = /* @__PURE__ */ new Map();
20466
20439
  /** Track tool call counts per child session to detect completion. */
20467
20440
  childToolCounts = /* @__PURE__ */ new Map();
20441
+ /** Cumulative code change stats per session, aggregated from session.diff SSE events. */
20442
+ diffStats = /* @__PURE__ */ new Map();
20468
20443
  /**
20469
- * Wrapper around connection.sessionUpdate that logs what's sent to the ACP client.
20470
- * This is the single point where we record all outgoing traffic.
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.
20471
20446
  */
20472
20447
  async sendToClient(params) {
20473
20448
  const updateType = params.update.sessionUpdate;
20474
- if (updateType !== "agent_message_chunk" && updateType !== "agent_thought_chunk") {
20475
- this.flushMessageBuffer(params.sessionId, `boundary:${updateType}`);
20476
- acpOut(`sessionUpdate.${updateType}`, {
20477
- sessionId: params.sessionId.slice(0, 12),
20478
- ...updateType === "tool_call" || updateType === "tool_call_update" ? {
20479
- toolCallId: params.update.toolCallId,
20480
- tool: params.update.title,
20481
- kind: params.update.kind,
20482
- status: params.update.status
20483
- } : updateType === "session_info_update" ? {
20484
- title: params.update.title,
20485
- _meta: params.update._meta
20486
- } : {}
20487
- });
20488
- }
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
+ });
20489
20454
  return this.connection.sessionUpdate(params);
20490
20455
  }
20491
- /**
20492
- * Accumulate a text/thought delta into the message buffer.
20493
- * The buffer is flushed as a single log entry when a boundary event occurs
20494
- * (e.g. tool_call starts, new message, session change).
20495
- */
20496
- accumulateDelta(messageId, sessionId, kind, delta) {
20497
- let buf = this.messageBuffers.get(messageId);
20498
- if (!buf) {
20499
- buf = { sessionId, text: "", thought: "" };
20500
- this.messageBuffers.set(messageId, buf);
20501
- const msgSet = this.sessionBufferIndex.get(sessionId);
20502
- if (msgSet) {
20503
- msgSet.add(messageId);
20504
- } else {
20505
- this.sessionBufferIndex.set(sessionId, /* @__PURE__ */ new Set([messageId]));
20506
- }
20507
- }
20508
- buf[kind] += delta;
20456
+ getDiffStats(sessionId) {
20457
+ return this.diffStats.get(sessionId);
20509
20458
  }
20510
- /**
20511
- * Flush accumulated message content for a given session as assembled log entries.
20512
- * Called when a boundary event (tool_call, session change, etc.) signals the
20513
- * text/thought phase is complete.
20514
- */
20515
- flushMessageBuffer(sessionId, reason) {
20516
- const msgIds = this.sessionBufferIndex.get(sessionId);
20517
- if (!msgIds) return;
20518
- for (const messageId of msgIds) {
20519
- const buf = this.messageBuffers.get(messageId);
20520
- if (!buf) continue;
20521
- if (!buf.text && !buf.thought) {
20522
- this.messageBuffers.delete(messageId);
20523
- continue;
20524
- }
20525
- if (buf.text) {
20526
- acpAssembled("sessionUpdate.agent_message_complete", {
20527
- sessionId: sessionId.slice(0, 12),
20528
- messageId: messageId.slice(0, 12),
20529
- length: buf.text.length,
20530
- text: buf.text,
20531
- flushReason: reason
20532
- });
20533
- }
20534
- if (buf.thought) {
20535
- acpAssembled("sessionUpdate.agent_thought_complete", {
20536
- sessionId: sessionId.slice(0, 12),
20537
- messageId: messageId.slice(0, 12),
20538
- length: buf.thought.length,
20539
- text: buf.thought,
20540
- flushReason: reason
20541
- });
20542
- }
20543
- this.messageBuffers.delete(messageId);
20544
- }
20545
- this.sessionBufferIndex.delete(sessionId);
20546
- }
20547
- /** Flush all remaining buffers (e.g. on stop) */
20548
- flushAllBuffers() {
20549
- for (const sessionId of this.sessionBufferIndex.keys()) {
20550
- this.flushMessageBuffer(sessionId, "stop");
20551
- }
20552
- }
20553
- /**
20554
- * Get or cache message metadata (role + parts) for a message.
20555
- * Fetches from SDK only on first delta for a message, then serves from cache.
20556
- */
20557
- async getOrCacheMessageMeta(sessionID, messageID, cwd) {
20558
- const cached2 = this.messageMetaCache.get(messageID);
20559
- if (cached2) return cached2;
20560
- const message = await this.sdk.session.message({ sessionID, messageID, directory: cwd }, { throwOnError: true }).then((x) => x.data).catch(() => void 0);
20561
- if (!message) return void 0;
20562
- const parts = /* @__PURE__ */ new Map();
20563
- for (const p of message.parts) {
20564
- parts.set(p.id, { type: p.type, ignored: p.ignored });
20565
- }
20566
- const meta3 = { role: message.info.role, parts };
20567
- this.messageMetaCache.set(messageID, meta3);
20568
- if (this.messageMetaCache.size > 50) {
20569
- const oldest = this.messageMetaCache.keys().next().value;
20570
- if (oldest) this.messageMetaCache.delete(oldest);
20571
- }
20572
- return meta3;
20573
- }
20574
- /**
20575
- * Flush previous message buffer for a session when messageId changes.
20576
- */
20577
- flushPreviousBuffer(sessionId, currentMessageId) {
20578
- const msgIds = this.sessionBufferIndex.get(sessionId);
20579
- if (!msgIds) return;
20580
- for (const messageId of msgIds) {
20581
- if (messageId !== currentMessageId) {
20582
- this.flushMessageBuffer(sessionId, "new_message");
20583
- return;
20584
- }
20585
- }
20459
+ setDiffStats(sessionId, stats) {
20460
+ if (this.diffStats.has(sessionId)) return;
20461
+ this.diffStats.set(sessionId, stats);
20586
20462
  }
20587
20463
  constructor(deps) {
20588
20464
  this.connection = deps.connection;
@@ -20598,7 +20474,6 @@ var EventHandler = class {
20598
20474
  });
20599
20475
  }
20600
20476
  stop() {
20601
- this.flushAllBuffers();
20602
20477
  this.abort.abort();
20603
20478
  }
20604
20479
  async runSubscription() {
@@ -20630,9 +20505,13 @@ var EventHandler = class {
20630
20505
  }
20631
20506
  // ─── Event Handling ──────────────────────────────────────────────
20632
20507
  async handleEvent(event) {
20508
+ if (event.type === "server.heartbeat") {
20509
+ return;
20510
+ }
20633
20511
  switch (event.type) {
20634
20512
  case "session.created":
20635
20513
  case "session.updated": {
20514
+ ocEvent(event.type, event);
20636
20515
  const props = event.properties;
20637
20516
  const info = props.info ?? props;
20638
20517
  const parentID = info.parentID;
@@ -20642,7 +20521,6 @@ var EventHandler = class {
20642
20521
  const isNew = !this.sessionManager.tryGet(id);
20643
20522
  this.sessionManager.registerDiscovered(id, parentID, title);
20644
20523
  if (isNew) {
20645
- ocEvent("session.created.child", { id, parentID, title });
20646
20524
  const agentMatch = title?.match(/@(\w+)\s+subagent/);
20647
20525
  const agentType = agentMatch?.[1];
20648
20526
  const description = title?.replace(/\s*\(@\w+\s+subagent\)\s*$/, "") ?? title;
@@ -20654,6 +20532,7 @@ var EventHandler = class {
20654
20532
  return;
20655
20533
  }
20656
20534
  case "permission.asked": {
20535
+ ocEvent("permission.asked", event);
20657
20536
  const permission = event.properties;
20658
20537
  const resolved = this.resolveSession(permission.sessionID);
20659
20538
  if (!resolved) return;
@@ -20782,27 +20661,39 @@ var EventHandler = class {
20782
20661
  return;
20783
20662
  }
20784
20663
  case "message.part.updated": {
20664
+ ocEvent("message.part.updated", event);
20785
20665
  const props = event.properties;
20786
20666
  const part = props.part;
20667
+ if (part.id) {
20668
+ this.partMetaIndex.set(part.id, { type: part.type, ignored: part.ignored });
20669
+ }
20787
20670
  const resolved = this.resolveSession(part.sessionID);
20788
20671
  if (!resolved) return;
20789
20672
  if (part.type === "tool") {
20790
20673
  await this.handleToolPart(resolved.sessionId, part);
20791
20674
  }
20675
+ if (part.type === "text" && typeof part.text === "string" && part.ignored === true) {
20676
+ await this.sendToClient({
20677
+ sessionId: resolved.sessionId,
20678
+ update: {
20679
+ sessionUpdate: "agent_message_chunk",
20680
+ messageId: part.messageID,
20681
+ content: { type: "text", text: part.text }
20682
+ }
20683
+ }).catch(() => {
20684
+ });
20685
+ }
20792
20686
  return;
20793
20687
  }
20794
20688
  case "message.part.delta": {
20689
+ ocEvent("message.part.delta", event);
20795
20690
  const props = event.properties;
20796
20691
  const resolved = this.resolveSession(props.sessionID);
20797
20692
  if (!resolved) return;
20798
20693
  const sessionId = resolved.sessionId;
20799
- this.flushPreviousBuffer(sessionId, props.messageID);
20800
- const meta3 = await this.getOrCacheMessageMeta(props.sessionID, props.messageID, resolved.cwd);
20801
- if (!meta3 || meta3.role !== "assistant") return;
20802
- const partMeta = meta3.parts.get(props.partID);
20694
+ const partMeta = this.partMetaIndex.get(props.partID);
20803
20695
  if (!partMeta) return;
20804
20696
  if (partMeta.type === "text" && props.field === "text" && partMeta.ignored !== true) {
20805
- this.accumulateDelta(props.messageID, sessionId, "text", props.delta);
20806
20697
  await this.sendToClient({
20807
20698
  sessionId,
20808
20699
  update: {
@@ -20815,7 +20706,6 @@ var EventHandler = class {
20815
20706
  return;
20816
20707
  }
20817
20708
  if (partMeta.type === "reasoning" && props.field === "text") {
20818
- this.accumulateDelta(props.messageID, sessionId, "thought", props.delta);
20819
20709
  await this.sendToClient({
20820
20710
  sessionId,
20821
20711
  update: {
@@ -20828,6 +20718,25 @@ var EventHandler = class {
20828
20718
  }
20829
20719
  return;
20830
20720
  }
20721
+ case "session.diff": {
20722
+ ocEvent("session.diff", event);
20723
+ const { sessionID, diff } = event.properties;
20724
+ const resolved = this.resolveSession(sessionID);
20725
+ if (!resolved) {
20726
+ return;
20727
+ }
20728
+ let additions = 0;
20729
+ let deletions = 0;
20730
+ for (const d of diff) {
20731
+ additions += d.additions;
20732
+ deletions += d.deletions;
20733
+ }
20734
+ this.diffStats.set(resolved.sessionId, { additions, deletions, files: diff.length });
20735
+ return;
20736
+ }
20737
+ default: {
20738
+ return;
20739
+ }
20831
20740
  }
20832
20741
  }
20833
20742
  // ─── Child Session Announcement ──────────────────────────────────
@@ -21034,6 +20943,7 @@ var EventHandler = class {
21034
20943
  const agentMatch = title.match(/@(\w+)\s+subagent/);
21035
20944
  const agentType = agentMatch?.[1];
21036
20945
  const description = title.replace(/\s*\(@\w+\s+subagent\)\s*$/, "");
20946
+ const childDiffStats = this.diffStats.get(childSessionId);
21037
20947
  await this.sendToClient({
21038
20948
  sessionId: childSessionId,
21039
20949
  update: {
@@ -21046,7 +20956,8 @@ var EventHandler = class {
21046
20956
  toolCallCount,
21047
20957
  durationMs,
21048
20958
  ...agentType && { agentType },
21049
- ...description && { description }
20959
+ ...description && { description },
20960
+ ...childDiffStats && { codeChange: childDiffStats }
21050
20961
  }
21051
20962
  }
21052
20963
  });
@@ -21068,6 +20979,49 @@ function simpleHash(str) {
21068
20979
  return hash2.toString(36);
21069
20980
  }
21070
20981
 
20982
+ // src/mcp-manager.ts
20983
+ var THROW = { throwOnError: true };
20984
+ var McpManager = class {
20985
+ constructor(sdk) {
20986
+ this.sdk = sdk;
20987
+ }
20988
+ async status(directory) {
20989
+ ocCall("mcp.status", { directory });
20990
+ const result = await this.sdk.mcp.status({ directory });
20991
+ return result.data ?? {};
20992
+ }
20993
+ async add(directory, name, config2) {
20994
+ ocCall("mcp.add", { directory, name });
20995
+ const result = await this.sdk.mcp.add({ directory, name, config: config2 }, THROW);
20996
+ return result.data ?? {};
20997
+ }
20998
+ async connect(directory, name) {
20999
+ ocCall("mcp.connect", { directory, name });
21000
+ await this.sdk.mcp.connect({ name, directory }, THROW);
21001
+ return { success: true };
21002
+ }
21003
+ async disconnect(directory, name) {
21004
+ ocCall("mcp.disconnect", { directory, name });
21005
+ await this.sdk.mcp.disconnect({ name, directory }, THROW);
21006
+ return { success: true };
21007
+ }
21008
+ async startAuth(directory, name) {
21009
+ ocCall("mcp.auth.start", { directory, name });
21010
+ const result = await this.sdk.mcp.auth.start({ name, directory }, THROW);
21011
+ return result.data ?? {};
21012
+ }
21013
+ async callbackAuth(directory, name, code) {
21014
+ ocCall("mcp.auth.callback", { directory, name });
21015
+ const result = await this.sdk.mcp.auth.callback({ name, directory, code }, THROW);
21016
+ return result.data ?? {};
21017
+ }
21018
+ async removeAuth(directory, name) {
21019
+ ocCall("mcp.auth.remove", { directory, name });
21020
+ await this.sdk.mcp.auth.remove({ name, directory }, THROW);
21021
+ return { success: true };
21022
+ }
21023
+ };
21024
+
21071
21025
  // src/auth-provider.ts
21072
21026
  var AuthProviderManager = class {
21073
21027
  sdk;
@@ -21117,22 +21071,30 @@ var Agent = class {
21117
21071
  sessionManager;
21118
21072
  eventHandler;
21119
21073
  authProvider;
21074
+ mcpManager;
21120
21075
  /**
21121
21076
  * Wrapper around connection.sessionUpdate that logs what's sent to the ACP client.
21122
21077
  */
21123
21078
  async sendToClient(params) {
21124
21079
  const updateType = params.update.sessionUpdate;
21125
- if (updateType !== "agent_message_chunk" && updateType !== "agent_thought_chunk") {
21126
- acpOut(`sessionUpdate.${updateType}`, {
21127
- sessionId: params.sessionId.slice(0, 12),
21128
- ...updateType === "tool_call" || updateType === "tool_call_update" ? {
21129
- toolCallId: params.update.toolCallId,
21130
- tool: params.update.title,
21131
- kind: params.update.kind,
21132
- status: params.update.status
21133
- } : {}
21134
- });
21135
- }
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
+ });
21136
21098
  return this.connection.sessionUpdate(params);
21137
21099
  }
21138
21100
  constructor(config2) {
@@ -21140,6 +21102,7 @@ var Agent = class {
21140
21102
  this.sdk = config2.sdk;
21141
21103
  this.sessionManager = new SessionManager(config2.sdk);
21142
21104
  this.authProvider = new AuthProviderManager(config2.sdk);
21105
+ this.mcpManager = new McpManager(config2.sdk);
21143
21106
  }
21144
21107
  init(connection) {
21145
21108
  this.connection = connection;
@@ -21189,10 +21152,13 @@ var Agent = class {
21189
21152
  // ─── Extension Methods ────────────────────────────────────────────
21190
21153
  async extMethod(method, params) {
21191
21154
  acpIn("extMethod", { method });
21192
- if (!method.startsWith("provider/")) {
21193
- throw new Error(`Unknown extMethod: ${method}`);
21155
+ if (method.startsWith("provider/")) {
21156
+ return this.handleProviderMethod(method, params);
21157
+ }
21158
+ if (method.startsWith("mcp/")) {
21159
+ return this.handleMcpMethod(method, params);
21194
21160
  }
21195
- return this.handleProviderMethod(method, params);
21161
+ throw new Error(`Unknown extMethod: ${method}`);
21196
21162
  }
21197
21163
  async handleProviderMethod(method, params) {
21198
21164
  const sessionId = params.sessionId;
@@ -21248,6 +21214,67 @@ var Agent = class {
21248
21214
  throw new Error(`Unknown provider extMethod: ${method}`);
21249
21215
  }
21250
21216
  }
21217
+ async handleMcpMethod(method, params) {
21218
+ const sessionId = params.sessionId;
21219
+ if (!sessionId) throw new Error("sessionId required");
21220
+ const directory = this.sessionManager.get(sessionId)?.cwd;
21221
+ if (!directory) throw new Error("Session not found");
21222
+ switch (method) {
21223
+ case "mcp/status": {
21224
+ const data = await this.mcpManager.status(directory);
21225
+ acpOut("extMethod.response", { method: "mcp/status" });
21226
+ return data;
21227
+ }
21228
+ case "mcp/add": {
21229
+ const name = params.name;
21230
+ const config2 = params.config;
21231
+ if (!name) throw new Error("Missing required parameter: name");
21232
+ if (!config2) throw new Error("Missing required parameter: config");
21233
+ const data = await this.mcpManager.add(directory, name, config2);
21234
+ acpOut("extMethod.response", { method: "mcp/add", name });
21235
+ return data;
21236
+ }
21237
+ case "mcp/connect": {
21238
+ const name = params.name;
21239
+ if (!name) throw new Error("Missing required parameter: name");
21240
+ const data = await this.mcpManager.connect(directory, name);
21241
+ acpOut("extMethod.response", { method: "mcp/connect", name });
21242
+ return data;
21243
+ }
21244
+ case "mcp/disconnect": {
21245
+ const name = params.name;
21246
+ if (!name) throw new Error("Missing required parameter: name");
21247
+ const data = await this.mcpManager.disconnect(directory, name);
21248
+ acpOut("extMethod.response", { method: "mcp/disconnect", name });
21249
+ return data;
21250
+ }
21251
+ case "mcp/auth/start": {
21252
+ const name = params.name;
21253
+ if (!name) throw new Error("Missing required parameter: name");
21254
+ const data = await this.mcpManager.startAuth(directory, name);
21255
+ acpOut("extMethod.response", { method: "mcp/auth/start", name });
21256
+ return data;
21257
+ }
21258
+ case "mcp/auth/callback": {
21259
+ const name = params.name;
21260
+ const code = params.code;
21261
+ if (!name) throw new Error("Missing required parameter: name");
21262
+ if (!code) throw new Error("Missing required parameter: code");
21263
+ const data = await this.mcpManager.callbackAuth(directory, name, code);
21264
+ acpOut("extMethod.response", { method: "mcp/auth/callback", name });
21265
+ return data;
21266
+ }
21267
+ case "mcp/auth/remove": {
21268
+ const name = params.name;
21269
+ if (!name) throw new Error("Missing required parameter: name");
21270
+ const data = await this.mcpManager.removeAuth(directory, name);
21271
+ acpOut("extMethod.response", { method: "mcp/auth/remove", name });
21272
+ return data;
21273
+ }
21274
+ default:
21275
+ throw new Error(`Unknown MCP method: ${method}`);
21276
+ }
21277
+ }
21251
21278
  async buildProviderMeta() {
21252
21279
  try {
21253
21280
  const data = await this.authProvider.listProviders(this.config.cwd);
@@ -21294,7 +21321,14 @@ var Agent = class {
21294
21321
  const directory = params.cwd;
21295
21322
  const sessionId = params.sessionId;
21296
21323
  const model = await this.defaultModel(directory);
21297
- await this.sessionManager.load(sessionId, params.cwd, params.mcpServers, model);
21324
+ const { session } = await this.sessionManager.load(sessionId, params.cwd, params.mcpServers, model);
21325
+ if (session.summary) {
21326
+ this.eventHandler.setDiffStats(sessionId, {
21327
+ additions: session.summary.additions,
21328
+ deletions: session.summary.deletions,
21329
+ files: session.summary.files
21330
+ });
21331
+ }
21298
21332
  const result = await this.loadSessionMode({
21299
21333
  cwd: directory,
21300
21334
  mcpServers: params.mcpServers,
@@ -21379,7 +21413,14 @@ var Agent = class {
21379
21413
  const sessionId = params.sessionId;
21380
21414
  const mcpServers = params.mcpServers ?? [];
21381
21415
  const model = await this.defaultModel(directory);
21382
- await this.sessionManager.load(sessionId, directory, mcpServers, model);
21416
+ const { session } = await this.sessionManager.load(sessionId, directory, mcpServers, model);
21417
+ if (session.summary) {
21418
+ this.eventHandler.setDiffStats(sessionId, {
21419
+ additions: session.summary.additions,
21420
+ deletions: session.summary.deletions,
21421
+ files: session.summary.files
21422
+ });
21423
+ }
21383
21424
  const result = await this.loadSessionMode({
21384
21425
  cwd: directory,
21385
21426
  mcpServers,
@@ -21716,7 +21757,7 @@ var Agent = class {
21716
21757
  {}
21717
21758
  )
21718
21759
  };
21719
- await this.sdk.mcp.add({ directory: params.cwd, name: server.name, config: config2 }).catch(() => {
21760
+ await this.mcpManager.add(params.cwd, server.name, config2).catch(() => {
21720
21761
  });
21721
21762
  })
21722
21763
  );
@@ -21961,10 +22002,35 @@ var Agent = class {
21961
22002
  childCost += childMessages.filter((m) => m.info.role === "assistant").reduce((sum, m) => sum + (m.info.cost ?? 0), 0);
21962
22003
  }
21963
22004
  }
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;
21964
22018
  const providers = await this.sdk.config.providers({ directory }).then((x) => x.data?.providers ?? []).catch(() => []);
21965
22019
  const provider = providers.find((p) => p.id === msg.providerID);
21966
22020
  const model = provider?.models[msg.modelID];
21967
22021
  const size = model?.limit?.context ?? 0;
22022
+ const _meta = {};
22023
+ if (childCost > 0) {
22024
+ _meta.parentCost = totalCost;
22025
+ _meta.childCost = childCost;
22026
+ _meta.childSessionCount = children.length;
22027
+ }
22028
+ if (totalStats) {
22029
+ _meta.codeChange = totalStats;
22030
+ }
22031
+ if (childStats) {
22032
+ _meta.childCodeChange = childStats;
22033
+ }
21968
22034
  await this.sendToClient({
21969
22035
  sessionId,
21970
22036
  update: {
@@ -21972,13 +22038,7 @@ var Agent = class {
21972
22038
  used: msg.tokens.input + (msg.tokens.cache?.read ?? 0),
21973
22039
  size,
21974
22040
  cost: { amount: totalCost + childCost, currency: "USD" },
21975
- ...childCost > 0 && {
21976
- _meta: {
21977
- parentCost: totalCost,
21978
- childCost,
21979
- childSessionCount: children.length
21980
- }
21981
- }
22041
+ ...Object.keys(_meta).length > 0 && { _meta }
21982
22042
  }
21983
22043
  }).catch(() => {
21984
22044
  });
@@ -22045,33 +22105,13 @@ var Agent = class {
22045
22105
  return { name, args: rest.join(" ").trim() };
22046
22106
  }
22047
22107
  /**
22048
- * Fetch the complete message content after prompt returns and log it.
22049
- * This ensures the response text is always captured in logs, even if
22050
- * SSE delta events arrive out of sync or are missed.
22108
+ * Fetch the complete message content after prompt returns and log tool usage.
22051
22109
  */
22052
22110
  async logMessageContent(sessionId, messageId, directory) {
22053
22111
  if (!messageId) return;
22054
22112
  const message = await this.sdk.session.message({ sessionID: sessionId, messageID: messageId, directory }).then((x) => x.data).catch(() => void 0);
22055
22113
  if (!message) return;
22056
22114
  for (const part of message.parts) {
22057
- if (part.type === "text" && part.text) {
22058
- acpAssembled("sessionUpdate.agent_message_complete", {
22059
- sessionId: sessionId.slice(0, 12),
22060
- messageId: messageId.slice(0, 12),
22061
- length: part.text.length,
22062
- text: part.text,
22063
- flushReason: "prompt_complete"
22064
- });
22065
- }
22066
- if (part.type === "reasoning" && part.text) {
22067
- acpAssembled("sessionUpdate.agent_thought_complete", {
22068
- sessionId: sessionId.slice(0, 12),
22069
- messageId: messageId.slice(0, 12),
22070
- length: part.text.length,
22071
- text: part.text,
22072
- flushReason: "prompt_complete"
22073
- });
22074
- }
22075
22115
  if (part.type === "tool") {
22076
22116
  ocCall("session.prompt.tool", {
22077
22117
  sessionId: sessionId.slice(0, 12),