@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 +5 -0
- package/README.md +6 -1
- package/dist/index.cjs +81 -31
- package/dist/index.cjs.map +3 -3
- package/package.json +1 -1
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
|
|
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
|
|
377
|
+
const buffer2 = Buffer.alloc(size);
|
|
378
378
|
let fd;
|
|
379
379
|
try {
|
|
380
380
|
fd = fs.openSync(command, "r");
|
|
381
|
-
fs.readSync(fd,
|
|
381
|
+
fs.readSync(fd, buffer2, 0, size, 0);
|
|
382
382
|
fs.closeSync(fd);
|
|
383
383
|
} catch (e) {
|
|
384
384
|
}
|
|
385
|
-
return shebangCommand(
|
|
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
|
|
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
|
-
|
|
15863
|
-
|
|
15864
|
-
const chunks =
|
|
15865
|
-
|
|
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
|
|
20317
|
-
|
|
20318
|
-
|
|
20319
|
-
|
|
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
|
-
|
|
20324
|
-
|
|
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
|
-
|
|
20340
|
-
|
|
20341
|
-
|
|
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
|
|
20760
|
-
|
|
20761
|
-
|
|
20762
|
-
|
|
20763
|
-
|
|
20764
|
-
|
|
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
|
});
|