@harmonyos-arkts/opencode-acp 0.0.7 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,11 @@ All notable changes to this project will be documented in this file.
6
6
 
7
7
  ### Added
8
8
 
9
+ - 日志老化机制:基于 harmony-code 的日志管理策略
10
+ - 文件大小轮转:单个日志文件超过 5MB 时自动重命名(加时间戳后缀)
11
+ - 存留期清理:启动时删除超过 7 天的日志文件
12
+ - 数量上限:最多保留 30 个日志文件
13
+ - 缓冲写入:日志条目缓冲后每 500ms 或每 50 条批量刷盘,减少 I/O 开销
9
14
  - MCP extMethod API:通过 ACP `extMethod("mcp/*")` 向客户端暴露 MCP 服务器运行时管理能力
10
15
  - `mcp/status` — 获取所有 MCP 服务器连接状态
11
16
  - `mcp/add` — 动态添加 MCP 服务器(local/remote)
package/README.md CHANGED
@@ -128,7 +128,12 @@ Options:
128
128
 
129
129
  ## Traffic Logging
130
130
 
131
- All communication is logged to `~/.harmony-acp/logs/` as JSONL files (auto-rotated, max 30 files).
131
+ All communication is logged to `~/.harmony-acp/logs/` as JSONL files with automatic rotation and aging:
132
+
133
+ - **Size rotation**: log files are renamed with a timestamp when they exceed 5MB
134
+ - **Age cleanup**: files older than 7 days are deleted on startup
135
+ - **Count cap**: at most 30 log files are retained
136
+ - **Buffered writes**: log entries are buffered and flushed every 500ms or every 50 entries for performance
132
137
 
133
138
  ### Log Categories
134
139
 
package/dist/index.cjs CHANGED
@@ -374,15 +374,15 @@ var require_readShebang = __commonJS({
374
374
  var shebangCommand = require_shebang_command();
375
375
  function readShebang(command) {
376
376
  const size = 150;
377
- const buffer = Buffer.alloc(size);
377
+ const buffer2 = Buffer.alloc(size);
378
378
  let fd;
379
379
  try {
380
380
  fd = fs.openSync(command, "r");
381
- fs.readSync(fd, buffer, 0, size, 0);
381
+ fs.readSync(fd, buffer2, 0, size, 0);
382
382
  fs.closeSync(fd);
383
383
  } catch (e) {
384
384
  }
385
- return shebangCommand(buffer.toString());
385
+ return shebangCommand(buffer2.toString());
386
386
  }
387
387
  module2.exports = readShebang;
388
388
  }
@@ -15846,7 +15846,7 @@ var createSseClient = ({ onRequest, onSseError, onSseEvent, responseTransformer,
15846
15846
  if (!response.body)
15847
15847
  throw new Error("No body in SSE response");
15848
15848
  const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
15849
- let buffer = "";
15849
+ let buffer2 = "";
15850
15850
  const abortHandler = () => {
15851
15851
  try {
15852
15852
  reader.cancel();
@@ -15859,10 +15859,10 @@ var createSseClient = ({ onRequest, onSseError, onSseEvent, responseTransformer,
15859
15859
  const { done, value } = await reader.read();
15860
15860
  if (done)
15861
15861
  break;
15862
- buffer += value;
15863
- buffer = buffer.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
15864
- const chunks = buffer.split("\n\n");
15865
- buffer = chunks.pop() ?? "";
15862
+ buffer2 += value;
15863
+ buffer2 = buffer2.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
15864
+ const chunks = buffer2.split("\n\n");
15865
+ buffer2 = chunks.pop() ?? "";
15866
15866
  for (const chunk of chunks) {
15867
15867
  const lines = chunk.split("\n");
15868
15868
  const dataLines = [];
@@ -20298,9 +20298,15 @@ var import_fs = require("fs");
20298
20298
  var import_path = require("path");
20299
20299
  var import_os = require("os");
20300
20300
  var LOG_DIR = (0, import_path.join)((0, import_os.homedir)(), ".harmony-acp", "logs");
20301
+ var MAX_LOG_SIZE_BYTES = 5 * 1024 * 1024;
20302
+ var MAX_LOG_AGE_DAYS = 7;
20301
20303
  var MAX_LOG_FILES = 30;
20304
+ var FLUSH_INTERVAL_MS = 500;
20305
+ var BUFFER_SIZE_LIMIT = 50;
20302
20306
  var logFile = "";
20303
20307
  var enabled = false;
20308
+ var buffer = [];
20309
+ var flushTimer = null;
20304
20310
  function initLogger() {
20305
20311
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
20306
20312
  logFile = (0, import_path.join)(LOG_DIR, `${ts}.log`);
@@ -20310,24 +20316,72 @@ function initLogger() {
20310
20316
  }
20311
20317
  function setLogEnabled(v) {
20312
20318
  enabled = v;
20319
+ if (!v) flush();
20313
20320
  }
20314
20321
  function cleanOldLogs() {
20315
20322
  try {
20316
- const files = (0, import_fs.readdirSync)(LOG_DIR).filter((f) => f.endsWith(".log")).map((f) => ({
20317
- name: f,
20318
- path: (0, import_path.join)(LOG_DIR, f),
20319
- mtime: (0, import_fs.statSync)((0, import_path.join)(LOG_DIR, f)).mtime.getTime()
20320
- })).sort((a, b) => b.mtime - a.mtime);
20321
- for (let i = MAX_LOG_FILES; i < files.length; i++) {
20323
+ const now = Date.now();
20324
+ const maxAgeMs = MAX_LOG_AGE_DAYS * 24 * 60 * 60 * 1e3;
20325
+ const files = (0, import_fs.readdirSync)(LOG_DIR).filter((f) => f.endsWith(".log")).map((f) => {
20326
+ const full = (0, import_path.join)(LOG_DIR, f);
20322
20327
  try {
20323
- const { unlinkSync } = require("fs");
20324
- unlinkSync(files[i].path);
20328
+ return { name: f, path: full, mtime: (0, import_fs.statSync)(full).mtime.getTime() };
20329
+ } catch {
20330
+ return null;
20331
+ }
20332
+ }).filter((e) => e !== null);
20333
+ for (const f of files) {
20334
+ if (now - f.mtime > maxAgeMs) {
20335
+ try {
20336
+ (0, import_fs.unlinkSync)(f.path);
20337
+ } catch {
20338
+ }
20339
+ }
20340
+ }
20341
+ const surviving = files.filter((f) => now - f.mtime <= maxAgeMs).sort((a, b) => b.mtime - a.mtime);
20342
+ for (let i = MAX_LOG_FILES; i < surviving.length; i++) {
20343
+ try {
20344
+ (0, import_fs.unlinkSync)(surviving[i].path);
20325
20345
  } catch {
20326
20346
  }
20327
20347
  }
20328
20348
  } catch {
20329
20349
  }
20330
20350
  }
20351
+ function rotateIfNeeded() {
20352
+ try {
20353
+ const stat = (0, import_fs.statSync)(logFile);
20354
+ if (stat.size >= MAX_LOG_SIZE_BYTES) {
20355
+ const now = /* @__PURE__ */ new Date();
20356
+ const pad = (n) => String(n).padStart(2, "0");
20357
+ const ts = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
20358
+ const baseName = logFile.replace(/\.log$/, "");
20359
+ (0, import_fs.renameSync)(logFile, `${baseName}.${ts}.log`);
20360
+ }
20361
+ } catch {
20362
+ }
20363
+ }
20364
+ function flush() {
20365
+ if (flushTimer) {
20366
+ clearTimeout(flushTimer);
20367
+ flushTimer = null;
20368
+ }
20369
+ if (buffer.length === 0) return;
20370
+ const data = buffer.join("");
20371
+ buffer = [];
20372
+ try {
20373
+ (0, import_fs.appendFileSync)(logFile, data);
20374
+ rotateIfNeeded();
20375
+ } catch {
20376
+ }
20377
+ }
20378
+ function scheduleFlush() {
20379
+ if (flushTimer) return;
20380
+ flushTimer = setTimeout(() => {
20381
+ flushTimer = null;
20382
+ flush();
20383
+ }, FLUSH_INTERVAL_MS);
20384
+ }
20331
20385
  function write(category, action, data) {
20332
20386
  if (!enabled) return;
20333
20387
  const entry = {
@@ -20336,9 +20390,11 @@ function write(category, action, data) {
20336
20390
  action,
20337
20391
  ...data && { data }
20338
20392
  };
20339
- try {
20340
- (0, import_fs.appendFileSync)(logFile, JSON.stringify(entry) + "\n");
20341
- } catch {
20393
+ buffer.push(JSON.stringify(entry) + "\n");
20394
+ if (buffer.length >= BUFFER_SIZE_LIMIT) {
20395
+ flush();
20396
+ } else {
20397
+ scheduleFlush();
20342
20398
  }
20343
20399
  }
20344
20400
  function acpIn(method, params) {
@@ -20540,7 +20596,6 @@ async function emitSessionError(connection, sessionId, payload, options) {
20540
20596
  // src/event-handler.ts
20541
20597
  var SUBSCRIPTION_RETRY_MS = 3e3;
20542
20598
  var SUBSCRIPTION_ERROR_BROADCAST_MS = 3e4;
20543
- var QUESTION_TIMEOUT_MS = 3e5;
20544
20599
  var permissionOptions = [
20545
20600
  { optionId: "once", kind: "allow_once", name: "Allow once" },
20546
20601
  { optionId: "always", kind: "allow_always", name: "Always allow" },
@@ -20756,17 +20811,12 @@ var EventHandler = class {
20756
20811
  const sessionId = resolved.sessionId;
20757
20812
  const prev = this.questionQueues.get(q.sessionID) ?? Promise.resolve();
20758
20813
  const next = prev.then(async () => {
20759
- const extResult = await Promise.race([
20760
- this.connection.extMethod("questionAsked", {
20761
- sessionId,
20762
- questionId: q.id,
20763
- questions: q.questions,
20764
- ...q.tool && { tool: q.tool }
20765
- }),
20766
- new Promise(
20767
- (_, reject) => setTimeout(() => reject(new Error("questionAsked timeout")), QUESTION_TIMEOUT_MS)
20768
- )
20769
- ]).catch((err) => {
20814
+ const extResult = await this.connection.extMethod("questionAsked", {
20815
+ sessionId,
20816
+ questionId: q.id,
20817
+ questions: q.questions,
20818
+ ...q.tool && { tool: q.tool }
20819
+ }).catch((err) => {
20770
20820
  console.error("[event-handler] extMethod questionAsked failed:", err?.message ?? err);
20771
20821
  return void 0;
20772
20822
  });