@dev-anywhere/proxy 0.1.9 → 0.2.1
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/{chunk-BMVYMCKF.js → chunk-3ZUZ22V6.js} +2 -2
- package/dist/{chunk-7XMJMVIL.js → chunk-4YQ2JUM7.js} +41 -6
- package/dist/chunk-4YQ2JUM7.js.map +1 -0
- package/dist/chunk-7UOPAMX7.js +220 -0
- package/dist/chunk-7UOPAMX7.js.map +1 -0
- package/dist/chunk-NBRBO5GS.js +1032 -0
- package/dist/chunk-NBRBO5GS.js.map +1 -0
- package/dist/chunk-NQDJ6QAM.js +18 -0
- package/dist/chunk-NQDJ6QAM.js.map +1 -0
- package/dist/chunk-OBYEKZWC.js +104 -0
- package/dist/chunk-OBYEKZWC.js.map +1 -0
- package/dist/chunk-PWG6K5QB.js +204 -0
- package/dist/chunk-PWG6K5QB.js.map +1 -0
- package/dist/{chunk-DCDXAM76.js → chunk-RIQ6OL7X.js} +9 -6
- package/dist/chunk-RIQ6OL7X.js.map +1 -0
- package/dist/{chunk-6O6JTF24.js → chunk-WUBRUO3G.js} +1 -1
- package/dist/index.js +5 -5
- package/dist/{relay-token-Z4JZFPQ5.js → relay-token-RKAVVQHE.js} +5 -4
- package/dist/{relay-token-Z4JZFPQ5.js.map → relay-token-RKAVVQHE.js.map} +1 -1
- package/dist/serve.js +538 -431
- package/dist/serve.js.map +1 -1
- package/dist/session-worker.js +99 -32
- package/dist/session-worker.js.map +1 -1
- package/dist/{terminal-FJAIRC73.js → terminal-YO2D2OJU.js} +194 -151
- package/dist/terminal-YO2D2OJU.js.map +1 -0
- package/package.json +3 -3
- package/dist/chunk-2JUB4LDU.js +0 -84
- package/dist/chunk-2JUB4LDU.js.map +0 -1
- package/dist/chunk-7XMJMVIL.js.map +0 -1
- package/dist/chunk-DCDXAM76.js.map +0 -1
- package/dist/chunk-ORZTFYXR.js +0 -123
- package/dist/chunk-ORZTFYXR.js.map +0 -1
- package/dist/chunk-QFYI6AMN.js +0 -870
- package/dist/chunk-QFYI6AMN.js.map +0 -1
- package/dist/chunk-U5T7ZYXT.js +0 -346
- package/dist/chunk-U5T7ZYXT.js.map +0 -1
- package/dist/terminal-FJAIRC73.js.map +0 -1
- /package/dist/{chunk-BMVYMCKF.js.map → chunk-3ZUZ22V6.js.map} +0 -0
- /package/dist/{chunk-6O6JTF24.js.map → chunk-WUBRUO3G.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
DESIRED_RELAY_PATH
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-PWG6K5QB.js";
|
|
5
5
|
|
|
6
6
|
// src/common/daemon-env.ts
|
|
7
7
|
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
@@ -33,4 +33,4 @@ export {
|
|
|
33
33
|
setDesiredDaemonRelay,
|
|
34
34
|
daemonRelayArgs
|
|
35
35
|
};
|
|
36
|
-
//# sourceMappingURL=chunk-
|
|
36
|
+
//# sourceMappingURL=chunk-3ZUZ22V6.js.map
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
atomicWriteFileSync
|
|
4
|
+
} from "./chunk-NQDJ6QAM.js";
|
|
2
5
|
import {
|
|
3
6
|
sessionPaths
|
|
4
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-PWG6K5QB.js";
|
|
5
8
|
|
|
6
9
|
// src/common/seq-counter.ts
|
|
7
|
-
import { mkdirSync, readFileSync,
|
|
10
|
+
import { mkdirSync, readFileSync, existsSync } from "fs";
|
|
11
|
+
var RESERVATION_BATCH = 100;
|
|
8
12
|
var SeqCounter = class {
|
|
9
13
|
seq = 0;
|
|
14
|
+
// 已持久化的高水位; seq <= reservedUpTo 时 next() 不需要写盘
|
|
15
|
+
reservedUpTo = 0;
|
|
10
16
|
filePath;
|
|
11
17
|
constructor(sessionId, baseDir) {
|
|
12
18
|
const dir = baseDir ?? sessionPaths(sessionId).dir;
|
|
@@ -16,12 +22,22 @@ var SeqCounter = class {
|
|
|
16
22
|
}
|
|
17
23
|
next() {
|
|
18
24
|
this.seq++;
|
|
19
|
-
this.
|
|
25
|
+
if (this.seq > this.reservedUpTo) {
|
|
26
|
+
this.reservedUpTo = this.seq + RESERVATION_BATCH - 1;
|
|
27
|
+
this.save();
|
|
28
|
+
}
|
|
20
29
|
return this.seq;
|
|
21
30
|
}
|
|
22
31
|
current() {
|
|
23
32
|
return this.seq;
|
|
24
33
|
}
|
|
34
|
+
// 显式持久化当前 seq, 不再保留 batch——优雅退出时调用, 让重启续号尽量贴近真实进度。
|
|
35
|
+
flush() {
|
|
36
|
+
if (this.seq !== this.reservedUpTo) {
|
|
37
|
+
this.reservedUpTo = this.seq;
|
|
38
|
+
this.save();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
25
41
|
load() {
|
|
26
42
|
if (!existsSync(this.filePath)) return;
|
|
27
43
|
const content = readFileSync(this.filePath, "utf-8").trim();
|
|
@@ -30,11 +46,28 @@ var SeqCounter = class {
|
|
|
30
46
|
throw new Error(`Corrupt seq file: ${this.filePath} contains "${content}"`);
|
|
31
47
|
}
|
|
32
48
|
this.seq = parsed;
|
|
49
|
+
this.reservedUpTo = parsed;
|
|
33
50
|
}
|
|
34
51
|
save() {
|
|
35
|
-
|
|
52
|
+
atomicWriteFileSync(this.filePath, String(this.reservedUpTo));
|
|
36
53
|
}
|
|
37
54
|
};
|
|
55
|
+
var seqCounterCache = /* @__PURE__ */ new Map();
|
|
56
|
+
function getSeqCounterFor(sessionId, baseDir) {
|
|
57
|
+
let counter = seqCounterCache.get(sessionId);
|
|
58
|
+
if (!counter) {
|
|
59
|
+
counter = new SeqCounter(sessionId, baseDir);
|
|
60
|
+
seqCounterCache.set(sessionId, counter);
|
|
61
|
+
}
|
|
62
|
+
return counter;
|
|
63
|
+
}
|
|
64
|
+
function disposeSeqCounter(sessionId) {
|
|
65
|
+
const counter = seqCounterCache.get(sessionId);
|
|
66
|
+
if (counter) {
|
|
67
|
+
counter.flush();
|
|
68
|
+
seqCounterCache.delete(sessionId);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
38
71
|
|
|
39
72
|
// src/common/stream-json-schema.ts
|
|
40
73
|
import { z } from "zod";
|
|
@@ -150,6 +183,8 @@ export {
|
|
|
150
183
|
ControlRequestEventSchema,
|
|
151
184
|
StreamJsonEventSchema,
|
|
152
185
|
IGNORED_EVENT_TYPES,
|
|
153
|
-
SeqCounter
|
|
186
|
+
SeqCounter,
|
|
187
|
+
getSeqCounterFor,
|
|
188
|
+
disposeSeqCounter
|
|
154
189
|
};
|
|
155
|
-
//# sourceMappingURL=chunk-
|
|
190
|
+
//# sourceMappingURL=chunk-4YQ2JUM7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/common/seq-counter.ts","../src/common/stream-json-schema.ts"],"sourcesContent":["import { mkdirSync, readFileSync, existsSync } from \"node:fs\";\nimport { atomicWriteFileSync } from \"./atomic-write.js\";\nimport { sessionPaths } from \"./paths.js\";\n\n// 一次预留多少个 seq, 减少 sync writeFileSync 频次。每 N 次 next() 才写一次磁盘,\n// 写的是 reservation 高水位 (seq + N), 不是 current seq。\n// 重启后 load() 读到 reservation, 当前 seq 直接跳到 reservation, 后续 next() 从那里\n// 续——已用但未持久化的 [reservation - N + 1 .. reservation] 区间被\"浪费\", 但 wire 上\n// 不会发出 collision 的 seq, 接收方 (relay/web) 看不到回退/重复。\nconst RESERVATION_BATCH = 100;\n\n/**\n * 轻量级 per-session seq 计数器,持久化到文件\n *\n * 仅存储一个递增整数,proxy 重启后能接续 (按 reservation batch 跳跃, 不保证 contiguous)。\n */\nexport class SeqCounter {\n private seq: number = 0;\n // 已持久化的高水位; seq <= reservedUpTo 时 next() 不需要写盘\n private reservedUpTo: number = 0;\n private readonly filePath: string;\n\n constructor(sessionId: string, baseDir?: string) {\n const dir = baseDir ?? sessionPaths(sessionId).dir;\n mkdirSync(dir, { recursive: true });\n this.filePath = `${dir}/seq`;\n this.load();\n }\n\n next(): number {\n this.seq++;\n if (this.seq > this.reservedUpTo) {\n this.reservedUpTo = this.seq + RESERVATION_BATCH - 1;\n this.save();\n }\n return this.seq;\n }\n\n current(): number {\n return this.seq;\n }\n\n // 显式持久化当前 seq, 不再保留 batch——优雅退出时调用, 让重启续号尽量贴近真实进度。\n flush(): void {\n if (this.seq !== this.reservedUpTo) {\n this.reservedUpTo = this.seq;\n this.save();\n }\n }\n\n private load(): void {\n if (!existsSync(this.filePath)) return;\n const content = readFileSync(this.filePath, \"utf-8\").trim();\n const parsed = parseInt(content, 10);\n if (isNaN(parsed) || parsed < 0) {\n throw new Error(`Corrupt seq file: ${this.filePath} contains \"${content}\"`);\n }\n // 重启续号: 直接跳到上次预留的高水位, 把已用未持久化的区间作废, 避免 wire collision。\n this.seq = parsed;\n this.reservedUpTo = parsed;\n }\n\n private save(): void {\n atomicWriteFileSync(this.filePath, String(this.reservedUpTo));\n }\n}\n\n// per-session 进程内缓存。production 路径 (event-bridge / hook-event-router /\n// worker-registry) 之前每条 envelope 都 new SeqCounter(sessionId).next(), 每次\n// 都 readFileSync + writeFileSync——hot path 双写加倍。改走 getSeqCounterFor 共享\n// 同一实例, 配合 reservation batch 把磁盘写从 100 Hz 降到 ~1 Hz。\nconst seqCounterCache = new Map<string, SeqCounter>();\n\nexport function getSeqCounterFor(sessionId: string, baseDir?: string): SeqCounter {\n let counter = seqCounterCache.get(sessionId);\n if (!counter) {\n counter = new SeqCounter(sessionId, baseDir);\n seqCounterCache.set(sessionId, counter);\n }\n return counter;\n}\n\n// session 终止时调用, 把 counter 从缓存里摘掉并 flush 当前值到盘。\nexport function disposeSeqCounter(sessionId: string): void {\n const counter = seqCounterCache.get(sessionId);\n if (counter) {\n counter.flush();\n seqCounterCache.delete(sessionId);\n }\n}\n","import { z } from \"zod\";\n\n// Claude CLI stream-json 输出的事件结构定义。\n// 设计原则:\n// - 已知字段严格校验,未知字段 passthrough 保证前向兼容\n// - content block 单独 parse,一个未知 block type 不会导致整条 assistant 事件被丢弃\n// - 新 event/block 类型加到这里后,forwardEvent 添加对应分支即可\n// - 基于 ~/.claude-2.1.116 采样 fixture 验证\n\nconst TextBlockSchema = z\n .object({\n type: z.literal(\"text\"),\n text: z.string(),\n })\n .passthrough();\n\n// Opus extended thinking 的 thinking 字段可能是空字符串(明文被 Anthropic 服务端 redact),\n// 只在 signature 里保留加密内容。signature 始终存在于 thinking block。\nconst ThinkingBlockSchema = z\n .object({\n type: z.literal(\"thinking\"),\n thinking: z.string(),\n signature: z.string().optional(),\n })\n .passthrough();\n\nconst ToolUseBlockSchema = z\n .object({\n type: z.literal(\"tool_use\"),\n id: z.string(),\n name: z.string(),\n input: z.record(z.string(), z.unknown()),\n })\n .passthrough();\n\n// tool_result 的 content 形状多变(string / array of nested blocks),proxy 不解析内容本身\nconst ToolResultBlockSchema = z\n .object({\n type: z.literal(\"tool_result\"),\n tool_use_id: z.string(),\n content: z.unknown(),\n is_error: z.boolean().optional(),\n })\n .passthrough();\n\n// 已知 block 类型的 discriminated union;未知 type 会 safeParse 失败,调用方应跳过而非丢整个事件\nexport const KnownContentBlockSchema = z.discriminatedUnion(\"type\", [\n TextBlockSchema,\n ThinkingBlockSchema,\n ToolUseBlockSchema,\n ToolResultBlockSchema,\n]);\n\n// event 级别 schema。message.content 用 z.array(z.unknown()),不在此层 narrow content block 类型,\n// 让调用方逐 block 跑 KnownContentBlockSchema.safeParse 以宽容未知 block\nconst AssistantEventSchema = z\n .object({\n type: z.literal(\"assistant\"),\n message: z\n .object({\n content: z.array(z.unknown()),\n })\n .passthrough(),\n })\n .passthrough();\n\nconst UserEventSchema = z\n .object({\n type: z.literal(\"user\"),\n message: z\n .object({\n content: z.array(z.unknown()),\n })\n .passthrough(),\n })\n .passthrough();\n\nconst ResultEventSchema = z\n .object({\n type: z.literal(\"result\"),\n subtype: z.string(),\n is_error: z.boolean().optional(),\n })\n .passthrough();\n\n// --include-partial-messages 开启时出现的增量事件。我们消费 content_block_delta 中的\n// text_delta / thinking_delta,其余内层 event(message_start/message_delta/content_block_start 等)忽略。\n// signature_delta 存在但 proxy 不转发 —— signature 是 Anthropic replay 用的加密态,对 UI 无意义。\nconst TextDeltaSchema = z\n .object({\n type: z.literal(\"text_delta\"),\n text: z.string(),\n })\n .passthrough();\n\nconst ThinkingDeltaSchema = z\n .object({\n type: z.literal(\"thinking_delta\"),\n thinking: z.string(),\n })\n .passthrough();\n\nconst SignatureDeltaSchema = z\n .object({\n type: z.literal(\"signature_delta\"),\n signature: z.string(),\n })\n .passthrough();\n\nexport const ContentBlockDeltaSchema = z\n .object({\n type: z.literal(\"content_block_delta\"),\n index: z.number(),\n delta: z.discriminatedUnion(\"type\", [\n TextDeltaSchema,\n ThinkingDeltaSchema,\n SignatureDeltaSchema,\n ]),\n })\n .passthrough();\n\nconst StreamEventSchema = z\n .object({\n type: z.literal(\"stream_event\"),\n event: z.object({ type: z.string() }).passthrough(),\n })\n .passthrough();\n\n// permission-prompt-tool=stdio 模式下,claude 发 control_request 要求审批,\n// proxy handleControlRequest 需写回 control_response 解除工具阻塞。\n// subtype 目前只见过 \"can_use_tool\";request.display_name / permission_suggestions /\n// decision_reason / decision_reason_type / tool_use_id 是 CLI 辅助字段,审批决策只看 tool_name + input。\nconst CanUseToolRequestSchema = z\n .object({\n subtype: z.literal(\"can_use_tool\"),\n tool_name: z.string(),\n input: z.record(z.string(), z.unknown()),\n })\n .passthrough();\n\nexport const ControlRequestEventSchema = z\n .object({\n type: z.literal(\"control_request\"),\n request_id: z.string(),\n request: CanUseToolRequestSchema,\n })\n .passthrough();\n\n// 审批结果回写 shape。proxy 自己写入 stdin,主要用于单元测试断言 wire shape 合法。\nexport const ControlResponseEventSchema = z\n .object({\n type: z.literal(\"control_response\"),\n response: z\n .object({\n subtype: z.literal(\"success\"),\n request_id: z.string(),\n response: z.discriminatedUnion(\"behavior\", [\n z\n .object({\n behavior: z.literal(\"allow\"),\n updatedInput: z.record(z.string(), z.unknown()),\n })\n .passthrough(),\n z\n .object({\n behavior: z.literal(\"deny\"),\n message: z.string(),\n })\n .passthrough(),\n ]),\n })\n .passthrough(),\n })\n .passthrough();\n\n// control_response 是 proxy 写回 claude stdin 的方向,不会出现在 stdout fixture 里;\n// 但 control-request scenario 的采样脚本也把写回 shape 留在 fixture 里用于 round-trip 验证。\n// 并入 union 让通用 canary \"every event is known\" 测试能覆盖它。\nexport const StreamJsonEventSchema = z.discriminatedUnion(\"type\", [\n AssistantEventSchema,\n UserEventSchema,\n ResultEventSchema,\n StreamEventSchema,\n ControlRequestEventSchema,\n ControlResponseEventSchema,\n]);\n\n// schema discriminatedUnion 未覆盖但 proxy 明确静默忽略的 event type。\n// forwardEvent 遇到这些 type 不发 warn,测试也按这份名单判断\"safeParse 失败是否在意\"。\n// 新增一种忽略 type 时只改这里一处。\nexport const IGNORED_EVENT_TYPES: ReadonlySet<string> = new Set([\"system\", \"rate_limit_event\"]);\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
CONFIG_PATH,
|
|
4
|
+
LOG_DIR
|
|
5
|
+
} from "./chunk-PWG6K5QB.js";
|
|
6
|
+
|
|
7
|
+
// ../../packages/shared/dist/logger.js
|
|
8
|
+
import { lstatSync, mkdirSync, readdirSync, renameSync, statSync, symlinkSync, unlinkSync } from "fs";
|
|
9
|
+
import { homedir } from "os";
|
|
10
|
+
import { basename, join } from "path";
|
|
11
|
+
import pino from "pino";
|
|
12
|
+
var DEFAULT_LOG_DIR = `${homedir()}/.dev-anywhere/logs`;
|
|
13
|
+
var DEFAULT_LOG_RETENTION = 50;
|
|
14
|
+
var PROCESS_LOG_RUN_ID = sanitizeRunId(`${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${process.pid}`);
|
|
15
|
+
function sanitizeRunId(runId) {
|
|
16
|
+
return runId.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
17
|
+
}
|
|
18
|
+
function linkLatestLog(logDir, name, filePath, runId) {
|
|
19
|
+
const latestPath = join(logDir, `${name}.log`);
|
|
20
|
+
try {
|
|
21
|
+
const stat = lstatSync(latestPath);
|
|
22
|
+
if (stat.isSymbolicLink()) {
|
|
23
|
+
unlinkSync(latestPath);
|
|
24
|
+
} else {
|
|
25
|
+
renameSync(latestPath, join(logDir, `${name}-legacy-${runId}.log`));
|
|
26
|
+
}
|
|
27
|
+
} catch (err) {
|
|
28
|
+
const code = err.code;
|
|
29
|
+
if (code !== "ENOENT")
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
symlinkSync(basename(filePath), latestPath);
|
|
34
|
+
} catch {
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function resolveRetention(retention) {
|
|
38
|
+
if (retention === void 0)
|
|
39
|
+
return DEFAULT_LOG_RETENTION;
|
|
40
|
+
return Number.isFinite(retention) && retention >= 0 ? Math.floor(retention) : DEFAULT_LOG_RETENTION;
|
|
41
|
+
}
|
|
42
|
+
function pruneOldLogs(logDir, name, currentFilePath, retention) {
|
|
43
|
+
const keep = resolveRetention(retention);
|
|
44
|
+
if (keep === 0)
|
|
45
|
+
return;
|
|
46
|
+
const currentFileName = basename(currentFilePath);
|
|
47
|
+
const prefix = `${name}-`;
|
|
48
|
+
const candidates = readdirSync(logDir).filter((entry) => entry.startsWith(prefix) && entry.endsWith(".log") && entry !== currentFileName).map((entry) => {
|
|
49
|
+
const path = join(logDir, entry);
|
|
50
|
+
try {
|
|
51
|
+
return { path, mtimeMs: statSync(path).mtimeMs };
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}).filter((entry) => entry !== null).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
56
|
+
for (const stale of candidates.slice(Math.max(0, keep - 1))) {
|
|
57
|
+
try {
|
|
58
|
+
unlinkSync(stale.path);
|
|
59
|
+
} catch {
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
var loggerMetaMap = /* @__PURE__ */ new WeakMap();
|
|
64
|
+
function buildPinoLogger(options) {
|
|
65
|
+
const { name, level = "info", logDir = DEFAULT_LOG_DIR, retention, stdout = false, silent = false, sync = false } = options;
|
|
66
|
+
if (silent) {
|
|
67
|
+
return { logger: pino({ level: "silent" }), destination: null };
|
|
68
|
+
}
|
|
69
|
+
mkdirSync(logDir, { recursive: true });
|
|
70
|
+
const runId = PROCESS_LOG_RUN_ID;
|
|
71
|
+
const filePath = join(logDir, `${name}-${runId}.log`);
|
|
72
|
+
linkLatestLog(logDir, name, filePath, runId);
|
|
73
|
+
pruneOldLogs(logDir, name, filePath, retention);
|
|
74
|
+
const destination = pino.destination({ dest: filePath, sync });
|
|
75
|
+
const streams = [{ stream: destination }];
|
|
76
|
+
if (stdout) {
|
|
77
|
+
streams.unshift({ stream: process.stdout });
|
|
78
|
+
}
|
|
79
|
+
return { logger: pino({ level }, pino.multistream(streams)), destination };
|
|
80
|
+
}
|
|
81
|
+
function createLogger(options) {
|
|
82
|
+
let real = null;
|
|
83
|
+
const meta = { materialized: false, destination: null };
|
|
84
|
+
const ensure = () => {
|
|
85
|
+
if (!real) {
|
|
86
|
+
const built = buildPinoLogger(options);
|
|
87
|
+
real = built.logger;
|
|
88
|
+
meta.materialized = true;
|
|
89
|
+
meta.destination = built.destination;
|
|
90
|
+
}
|
|
91
|
+
return real;
|
|
92
|
+
};
|
|
93
|
+
const proxy = new Proxy(/* @__PURE__ */ Object.create(null), {
|
|
94
|
+
get(_target, prop) {
|
|
95
|
+
const target = ensure();
|
|
96
|
+
const value = Reflect.get(target, prop, target);
|
|
97
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
98
|
+
},
|
|
99
|
+
set(_target, prop, value) {
|
|
100
|
+
return Reflect.set(ensure(), prop, value);
|
|
101
|
+
},
|
|
102
|
+
has(_target, prop) {
|
|
103
|
+
return Reflect.has(ensure(), prop);
|
|
104
|
+
},
|
|
105
|
+
ownKeys() {
|
|
106
|
+
return Reflect.ownKeys(ensure());
|
|
107
|
+
},
|
|
108
|
+
getOwnPropertyDescriptor(_target, prop) {
|
|
109
|
+
return Reflect.getOwnPropertyDescriptor(ensure(), prop);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
loggerMetaMap.set(proxy, meta);
|
|
113
|
+
return proxy;
|
|
114
|
+
}
|
|
115
|
+
async function flushLogger(logger, timeoutMs = 200) {
|
|
116
|
+
const meta = loggerMetaMap.get(logger);
|
|
117
|
+
if (!meta || !meta.materialized)
|
|
118
|
+
return;
|
|
119
|
+
const dest = meta.destination;
|
|
120
|
+
if (!dest)
|
|
121
|
+
return;
|
|
122
|
+
if (dest.fd == null || dest.fd < 0) {
|
|
123
|
+
const opened = await new Promise((resolve) => {
|
|
124
|
+
const timer = setTimeout(() => resolve(false), timeoutMs);
|
|
125
|
+
dest.once?.("ready", () => {
|
|
126
|
+
clearTimeout(timer);
|
|
127
|
+
resolve(true);
|
|
128
|
+
});
|
|
129
|
+
dest.once?.("error", () => {
|
|
130
|
+
clearTimeout(timer);
|
|
131
|
+
resolve(false);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
if (!opened)
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
dest.flushSync?.();
|
|
139
|
+
} catch {
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/common/logger.ts
|
|
144
|
+
import { existsSync, readFileSync } from "fs";
|
|
145
|
+
|
|
146
|
+
// src/common/runtime-env.ts
|
|
147
|
+
var VALID_LOG_LEVELS = [
|
|
148
|
+
"trace",
|
|
149
|
+
"debug",
|
|
150
|
+
"info",
|
|
151
|
+
"warn",
|
|
152
|
+
"error",
|
|
153
|
+
"fatal",
|
|
154
|
+
"silent"
|
|
155
|
+
];
|
|
156
|
+
function parseLogLevel(value) {
|
|
157
|
+
if (!value) return void 0;
|
|
158
|
+
if (VALID_LOG_LEVELS.includes(value)) return value;
|
|
159
|
+
throw new Error(
|
|
160
|
+
`Invalid LOG_LEVEL=${JSON.stringify(value)}; expected one of ${VALID_LOG_LEVELS.join(", ")}`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
function parsePort(value, source) {
|
|
164
|
+
if (!value) return void 0;
|
|
165
|
+
const port = Number(value);
|
|
166
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
167
|
+
throw new Error(`Invalid ${source}=${JSON.stringify(value)}: expected TCP port 1-65535`);
|
|
168
|
+
}
|
|
169
|
+
return port;
|
|
170
|
+
}
|
|
171
|
+
function nonEmpty(value) {
|
|
172
|
+
return value && value.length > 0 ? value : void 0;
|
|
173
|
+
}
|
|
174
|
+
function loadProxyRuntimeEnv(env2 = process.env) {
|
|
175
|
+
return {
|
|
176
|
+
relayUrl: nonEmpty(env2.RELAY_URL),
|
|
177
|
+
relayProxyToken: nonEmpty(env2.RELAY_PROXY_TOKEN),
|
|
178
|
+
hookPort: parsePort(env2.DEV_ANYWHERE_HOOK_PORT, "DEV_ANYWHERE_HOOK_PORT"),
|
|
179
|
+
claudeBin: nonEmpty(env2.CLAUDE_BIN),
|
|
180
|
+
codexBin: nonEmpty(env2.CODEX_BIN),
|
|
181
|
+
logLevel: parseLogLevel(env2.LOG_LEVEL),
|
|
182
|
+
isVitest: !!env2.VITEST
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/common/logger.ts
|
|
187
|
+
var env = loadProxyRuntimeEnv();
|
|
188
|
+
function readConfigLogLevel() {
|
|
189
|
+
if (env.logLevel) return void 0;
|
|
190
|
+
if (!existsSync(CONFIG_PATH)) return void 0;
|
|
191
|
+
try {
|
|
192
|
+
const raw = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
|
|
193
|
+
if (typeof raw.logLevel !== "string") return void 0;
|
|
194
|
+
return VALID_LOG_LEVELS.includes(raw.logLevel) ? raw.logLevel : void 0;
|
|
195
|
+
} catch {
|
|
196
|
+
return void 0;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
var overrideLevel = env.logLevel ?? readConfigLogLevel();
|
|
200
|
+
var serviceLogger = createLogger({
|
|
201
|
+
name: "service",
|
|
202
|
+
level: overrideLevel ?? "info",
|
|
203
|
+
logDir: LOG_DIR,
|
|
204
|
+
silent: env.isVitest
|
|
205
|
+
});
|
|
206
|
+
var terminalLogger = createLogger({
|
|
207
|
+
name: "terminal",
|
|
208
|
+
level: overrideLevel ?? "debug",
|
|
209
|
+
logDir: LOG_DIR,
|
|
210
|
+
silent: env.isVitest
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
export {
|
|
214
|
+
flushLogger,
|
|
215
|
+
VALID_LOG_LEVELS,
|
|
216
|
+
loadProxyRuntimeEnv,
|
|
217
|
+
serviceLogger,
|
|
218
|
+
terminalLogger
|
|
219
|
+
};
|
|
220
|
+
//# sourceMappingURL=chunk-7UOPAMX7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../packages/shared/src/logger.ts","../src/common/logger.ts","../src/common/runtime-env.ts"],"sourcesContent":["import {\n lstatSync,\n mkdirSync,\n readdirSync,\n renameSync,\n statSync,\n symlinkSync,\n unlinkSync,\n} from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { basename, join } from \"node:path\";\nimport pino from \"pino\";\n\nexport type { Logger } from \"pino\";\n\nexport interface CreateLoggerOptions {\n name: string;\n level?: string;\n logDir?: string;\n retention?: number;\n stdout?: boolean;\n silent?: boolean;\n // 同步落盘:sonic-boom 默认异步 open + 异步 write,测试里需要在断言前看到文件,\n // 或在 afterEach 删目录前确保后台 worker 已经退出,必须开同步。生产保留异步以避免热路径阻塞。\n sync?: boolean;\n}\n\nconst DEFAULT_LOG_DIR = `${homedir()}/.dev-anywhere/logs`;\nconst DEFAULT_LOG_RETENTION = 50;\n\nconst PROCESS_LOG_RUN_ID = sanitizeRunId(\n `${new Date().toISOString().replace(/[:.]/g, \"-\")}-${process.pid}`,\n);\n\nfunction sanitizeRunId(runId: string): string {\n return runId.replace(/[^a-zA-Z0-9._-]/g, \"_\");\n}\n\nfunction linkLatestLog(logDir: string, name: string, filePath: string, runId: string): void {\n const latestPath = join(logDir, `${name}.log`);\n\n try {\n const stat = lstatSync(latestPath);\n if (stat.isSymbolicLink()) {\n unlinkSync(latestPath);\n } else {\n renameSync(latestPath, join(logDir, `${name}-legacy-${runId}.log`));\n }\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code !== \"ENOENT\") return;\n }\n\n try {\n symlinkSync(basename(filePath), latestPath);\n } catch {\n // 日志本体仍然写入 run-specific 文件;latest 链接失败不应阻塞服务启动。\n }\n}\n\nfunction resolveRetention(retention: number | undefined): number {\n if (retention === undefined) return DEFAULT_LOG_RETENTION;\n return Number.isFinite(retention) && retention >= 0\n ? Math.floor(retention)\n : DEFAULT_LOG_RETENTION;\n}\n\nfunction pruneOldLogs(\n logDir: string,\n name: string,\n currentFilePath: string,\n retention: number | undefined,\n): void {\n const keep = resolveRetention(retention);\n if (keep === 0) return;\n\n const currentFileName = basename(currentFilePath);\n const prefix = `${name}-`;\n const candidates = readdirSync(logDir)\n .filter(\n (entry) => entry.startsWith(prefix) && entry.endsWith(\".log\") && entry !== currentFileName,\n )\n .map((entry) => {\n const path = join(logDir, entry);\n try {\n return { path, mtimeMs: statSync(path).mtimeMs };\n } catch {\n return null;\n }\n })\n .filter((entry): entry is { path: string; mtimeMs: number } => entry !== null)\n .sort((a, b) => b.mtimeMs - a.mtimeMs);\n\n for (const stale of candidates.slice(Math.max(0, keep - 1))) {\n try {\n unlinkSync(stale.path);\n } catch {\n // 日志清理失败不能影响主进程启动。\n }\n }\n}\n\n// SonicBoom 实例的最小结构契约(pino.destination 返回它,但 pino 类型只暴露 DestinationStream\n// 接口,没有 fd / flushSync / once,所以这里手写一个结构类型用于 flushLogger)。\ninterface SonicLikeDestination {\n fd?: number;\n flushSync?: () => void;\n once?: (event: string, cb: (...args: unknown[]) => void) => void;\n}\n\ninterface LoggerMeta {\n materialized: boolean;\n destination: SonicLikeDestination | null;\n}\n\nconst loggerMetaMap = new WeakMap<pino.Logger, LoggerMeta>();\n\nfunction buildPinoLogger(options: CreateLoggerOptions): {\n logger: pino.Logger;\n destination: SonicLikeDestination | null;\n} {\n const {\n name,\n level = \"info\",\n logDir = DEFAULT_LOG_DIR,\n retention,\n stdout = false,\n silent = false,\n sync = false,\n } = options;\n\n if (silent) {\n return { logger: pino({ level: \"silent\" }), destination: null };\n }\n\n mkdirSync(logDir, { recursive: true });\n\n const runId = PROCESS_LOG_RUN_ID;\n const filePath = join(logDir, `${name}-${runId}.log`);\n linkLatestLog(logDir, name, filePath, runId);\n pruneOldLogs(logDir, name, filePath, retention);\n const destination = pino.destination({ dest: filePath, sync }) as unknown as SonicLikeDestination;\n const streams: pino.StreamEntry[] = [{ stream: destination as pino.DestinationStream }];\n\n if (stdout) {\n streams.unshift({ stream: process.stdout });\n }\n\n return { logger: pino({ level }, pino.multistream(streams)), destination };\n}\n\n// 返回一个 lazy proxy:调用 createLogger 本身不触发 mkdirSync / pino.destination\n// 等任何文件 IO,只有第一次实际访问 logger 的方法/属性时才构造底层 pino Logger。\n// 这样 `dev-anywhere -v` / `dev-anywhere init` 等不需要写日志的命令路径不会\n// 落地空 log 文件,也避免异步 SonicBoom 在 process.exit 时未 ready 的 race。\nexport function createLogger(options: CreateLoggerOptions): pino.Logger {\n let real: pino.Logger | null = null;\n const meta: LoggerMeta = { materialized: false, destination: null };\n const ensure = (): pino.Logger => {\n if (!real) {\n const built = buildPinoLogger(options);\n real = built.logger;\n meta.materialized = true;\n meta.destination = built.destination;\n }\n return real;\n };\n\n const proxy = new Proxy(Object.create(null) as pino.Logger, {\n get(_target, prop) {\n const target = ensure();\n const value = Reflect.get(target, prop, target);\n return typeof value === \"function\" ? value.bind(target) : value;\n },\n set(_target, prop, value) {\n return Reflect.set(ensure(), prop, value);\n },\n has(_target, prop) {\n return Reflect.has(ensure(), prop);\n },\n ownKeys() {\n return Reflect.ownKeys(ensure());\n },\n getOwnPropertyDescriptor(_target, prop) {\n return Reflect.getOwnPropertyDescriptor(ensure(), prop);\n },\n });\n\n loggerMetaMap.set(proxy, meta);\n return proxy;\n}\n\n// 进程退出前等 sonic-boom 真正落盘。`pino.flush(cb)` 在 destination 还没 ready\n// (fs.open 异步未完成)时会立刻回调 err=undefined 撒谎成功,但文件还是空的,所以\n// 这里直接走 sonic-boom 的 ready 事件 + flushSync 路径。\n// - 未实例化(lazy proxy 没被访问过) → no-op,不会触发文件 IO 副作用。\n// - silent / stdout-only(destination 为 null) → no-op。\n// - timeoutMs 是兜底,确保异常情况下不会卡住进程退出。\nexport async function flushLogger(logger: pino.Logger, timeoutMs = 200): Promise<void> {\n const meta = loggerMetaMap.get(logger);\n if (!meta || !meta.materialized) return;\n const dest = meta.destination;\n if (!dest) return;\n\n if (dest.fd == null || dest.fd < 0) {\n const opened = await new Promise<boolean>((resolve) => {\n const timer = setTimeout(() => resolve(false), timeoutMs);\n dest.once?.(\"ready\", () => {\n clearTimeout(timer);\n resolve(true);\n });\n dest.once?.(\"error\", () => {\n clearTimeout(timer);\n resolve(false);\n });\n });\n if (!opened) return;\n }\n\n try {\n dest.flushSync?.();\n } catch {\n // 文件描述符已关闭、磁盘满等极端情况下吞掉异常,避免把退出路径变成崩溃路径。\n }\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { createLogger } from \"@dev-anywhere/shared/logger\";\nimport { CONFIG_PATH, LOG_DIR } from \"./paths.js\";\nimport { loadProxyRuntimeEnv, VALID_LOG_LEVELS } from \"./runtime-env.js\";\n\nconst env = loadProxyRuntimeEnv();\n\n// 直接读 config.json 而不走 config.ts loadConfig:loadConfig 依赖 logger.ts,\n// 反向引用会形成循环。这里只取 logLevel 字段做 best-effort 降级,全量 schema 校验仍由\n// config.ts 在第一次 loadConfig 时执行。\nfunction readConfigLogLevel(): string | undefined {\n if (env.logLevel) return undefined;\n if (!existsSync(CONFIG_PATH)) return undefined;\n try {\n const raw = JSON.parse(readFileSync(CONFIG_PATH, \"utf-8\")) as { logLevel?: unknown };\n if (typeof raw.logLevel !== \"string\") return undefined;\n return (VALID_LOG_LEVELS as readonly string[]).includes(raw.logLevel)\n ? raw.logLevel\n : undefined;\n } catch {\n return undefined;\n }\n}\n\n// 三级 precedence:LOG_LEVEL env > config.logLevel > 各 logger 自己的默认。\nconst overrideLevel = env.logLevel ?? readConfigLogLevel();\n\nexport const serviceLogger = createLogger({\n name: \"service\",\n level: overrideLevel ?? \"info\",\n logDir: LOG_DIR,\n silent: env.isVitest,\n});\n\nexport const terminalLogger = createLogger({\n name: \"terminal\",\n level: overrideLevel ?? \"debug\",\n logDir: LOG_DIR,\n silent: env.isVitest,\n});\n","// 单一入口集中读取 proxy 运行时关心的环境变量,类型化输出,避免 process.env.X\n// 散落在多个文件里各自做 parseInt / 空串判断 / undefined 兜底。\n//\n// 范围:用户面对的运行时旋钮 + 构建/测试模式标志。\n// 不包含:proxy → provider/hook 子进程之间的 plumbing(DEV_ANYWHERE_HOOK_TOKEN /\n// HOOK_URL / HOOK_MARKER / HOOK_EVENT / SESSION_ID 等),这些是内部传参,不是用户旋钮,\n// 留在各自消费点。\n\nexport const VALID_LOG_LEVELS = [\n \"trace\",\n \"debug\",\n \"info\",\n \"warn\",\n \"error\",\n \"fatal\",\n \"silent\",\n] as const;\nexport type LogLevel = (typeof VALID_LOG_LEVELS)[number];\n\ninterface ProxyRuntimeEnv {\n // RELAY_URL —— 覆盖 config.relays[name].url;用于一次性指向另一个 relay。\n relayUrl: string | undefined;\n // RELAY_PROXY_TOKEN —— 覆盖 config.relays[name].proxyToken。\n relayProxyToken: string | undefined;\n // DEV_ANYWHERE_HOOK_PORT —— 覆盖按 profile 推导的 hook server 端口。\n hookPort: number | undefined;\n // CLAUDE_BIN / CODEX_BIN —— 覆盖 config.agentCli 里的 CLI 可执行文件路径。\n claudeBin: string | undefined;\n codexBin: string | undefined;\n // LOG_LEVEL —— 用户最高优先级;config.logLevel 是次优先;都缺则各 logger 自己 default。\n logLevel: LogLevel | undefined;\n // VITEST —— 测试运行器存在则把 logger 静默,避免污染 vitest 输出。\n isVitest: boolean;\n // NODE_ENV 故意不在这里:env.ts 把它读成 top-level const 让 tsup 静态折叠 + dead-code\n // elimination dev 分支。走函数调用会破坏这个 build-time 优化。\n}\n\nfunction parseLogLevel(value: string | undefined): LogLevel | undefined {\n if (!value) return undefined;\n if ((VALID_LOG_LEVELS as readonly string[]).includes(value)) return value as LogLevel;\n throw new Error(\n `Invalid LOG_LEVEL=${JSON.stringify(value)}; expected one of ${VALID_LOG_LEVELS.join(\", \")}`,\n );\n}\n\nfunction parsePort(value: string | undefined, source: string): number | undefined {\n if (!value) return undefined;\n const port = Number(value);\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n throw new Error(`Invalid ${source}=${JSON.stringify(value)}: expected TCP port 1-65535`);\n }\n return port;\n}\n\nfunction nonEmpty(value: string | undefined): string | undefined {\n return value && value.length > 0 ? value : undefined;\n}\n\nexport function loadProxyRuntimeEnv(env: NodeJS.ProcessEnv = process.env): ProxyRuntimeEnv {\n return {\n relayUrl: nonEmpty(env.RELAY_URL),\n relayProxyToken: nonEmpty(env.RELAY_PROXY_TOKEN),\n hookPort: parsePort(env.DEV_ANYWHERE_HOOK_PORT, \"DEV_ANYWHERE_HOOK_PORT\"),\n claudeBin: nonEmpty(env.CLAUDE_BIN),\n codexBin: nonEmpty(env.CODEX_BIN),\n logLevel: parseLogLevel(env.LOG_LEVEL),\n isVitest: !!env.VITEST,\n };\n}\n"],"mappings":"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","names":["env"]}
|