@aipanel/core 1.2.9 → 1.2.10

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.
@@ -1,18 +1,12 @@
1
1
  import { LOG_PREFIX } from "../common/constants.mjs";
2
2
  import {
3
+ LEVEL_NAMES,
3
4
  LogLevel,
4
5
  getConfig,
5
6
  getTimestamp,
6
7
  formatContext,
7
8
  formatValue
8
9
  } from "../common/logger-core.mjs";
9
- const LEVEL_NAMES = {
10
- [LogLevel.DEBUG]: "DEBUG",
11
- [LogLevel.INFO]: "INFO ",
12
- [LogLevel.WARN]: "WARN ",
13
- [LogLevel.ERROR]: "ERROR",
14
- [LogLevel.NONE]: "NONE "
15
- };
16
10
  const C = {
17
11
  dim: "color: #888",
18
12
  bright: "font-weight: bold",
@@ -39,7 +33,7 @@ function log(level, message, context, ...args) {
39
33
  segments.push("%c%s");
40
34
  styles.push(C.dim, getTimestamp());
41
35
  }
42
- segments.push(`%c${LEVEL_NAMES[level]}`);
36
+ segments.push(`%c${LEVEL_NAMES[level].padEnd(5)}`);
43
37
  styles.push(LEVEL_COLORS[level]);
44
38
  segments.push(`%c${LOG_PREFIX}`);
45
39
  styles.push(C.bright);
@@ -0,0 +1,15 @@
1
+ /**
2
+ * 浏览器存储辅助(window.localStorage/sessionStorage,仅 client 端;通过 @aipanel/core/client 导出)
3
+ */
4
+ /**
5
+ * 安全读取浏览器存储(JSON 解码;不可用/异常返回 null)
6
+ */
7
+ export declare function storageGet<T>(area: "local" | "session", key: string): T | null;
8
+ /**
9
+ * 安全写入浏览器存储(JSON 序列化;异常忽略)
10
+ */
11
+ export declare function storageSet(area: "local" | "session", key: string, value: unknown): void;
12
+ /**
13
+ * 安全删除浏览器存储项
14
+ */
15
+ export declare function storageRemove(area: "local" | "session", key: string): void;
@@ -0,0 +1,33 @@
1
+ function storageArea(area) {
2
+ try {
3
+ if (typeof window === "undefined") return void 0;
4
+ return area === "local" ? window.localStorage : window.sessionStorage;
5
+ } catch {
6
+ return void 0;
7
+ }
8
+ }
9
+ function storageGet(area, key) {
10
+ try {
11
+ const raw = storageArea(area)?.getItem(key);
12
+ return raw == null ? null : JSON.parse(raw);
13
+ } catch {
14
+ return null;
15
+ }
16
+ }
17
+ function storageSet(area, key, value) {
18
+ try {
19
+ storageArea(area)?.setItem(key, JSON.stringify(value));
20
+ } catch {
21
+ }
22
+ }
23
+ function storageRemove(area, key) {
24
+ try {
25
+ storageArea(area)?.removeItem(key);
26
+ } catch {
27
+ }
28
+ }
29
+ export {
30
+ storageGet,
31
+ storageRemove,
32
+ storageSet
33
+ };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * 浏览器主题辅助(依赖 window.matchMedia,仅 client 端使用;通过 @aipanel/core/client 导出)
3
+ */
4
+ import type { AIPanelWidgetTheme } from "../common/types";
5
+ /**
6
+ * 当前系统主题(仅浏览器有效;SSR/无 window 环境返回 light)
7
+ */
8
+ export declare function getSystemTheme(): "light" | "dark";
9
+ /**
10
+ * 挂件主题偏好解析:auto 按系统主题折算为 light/dark
11
+ * @param theme - 挂件主题偏好(AIPanelWidgetTheme)
12
+ * @param system - 系统主题(默认当前系统)
13
+ * @returns 实际应用主题
14
+ */
15
+ export declare function resolveWidgetTheme(theme: AIPanelWidgetTheme, system?: "light" | "dark"): "light" | "dark";
@@ -0,0 +1,11 @@
1
+ function getSystemTheme() {
2
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return "light";
3
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
4
+ }
5
+ function resolveWidgetTheme(theme, system = getSystemTheme()) {
6
+ return theme === "auto" ? system : theme;
7
+ }
8
+ export {
9
+ getSystemTheme,
10
+ resolveWidgetTheme
11
+ };
package/es/client.d.ts CHANGED
@@ -1 +1,3 @@
1
1
  export * from "./client/logger";
2
+ export * from "./client/storage";
3
+ export * from "./client/theme";
package/es/client.mjs CHANGED
@@ -1 +1,3 @@
1
1
  export * from "./client/logger.mjs";
2
+ export * from "./client/storage.mjs";
3
+ export * from "./client/theme.mjs";
@@ -122,6 +122,8 @@ export declare const BRIDGE_SCRIPT_PATH = "/__aipanel_bridge__.js";
122
122
  export declare const LOGS_API_PATH = "/__aipanel_process_logs__";
123
123
  /** Widget 样式路径 */
124
124
  export declare const WIDGET_STYLE_PATH = "/__aipanel_widget__.css";
125
+ /** Vue DevTools API 路径 */
126
+ export declare const VUE_DEVTOOLS_API_PATH = "/__aipanel_vue_devtools__";
125
127
  /** ==================== 文本处理 ==================== */
126
128
  /** 元素文本最大显示长度 */
127
129
  export declare const MAX_TEXT_LENGTH = 100;
@@ -138,16 +140,6 @@ export declare const PAGE_CONTEXT_MAX_TEXT_LENGTH = 10000;
138
140
  */
139
141
  export declare const SEVERITY_ERROR = 1;
140
142
  export declare const SEVERITY_WARN = 2;
141
- /** ==================== VS Code 扩展桥接 ==================== */
142
- /** VS Code 扩展 HTTP 服务端口(vscode-extension ↔ provider 通信协议) */
143
- export declare const VSCODE_EXTENSION_PORT = 51939;
144
- /** VS Code 扩展 HTTP 路由:健康检查 */
145
- export declare const VSCODE_ROUTE_HEALTH = "/health";
146
- /** VS Code 扩展 HTTP 路由:文件格式化 */
147
- export declare const VSCODE_ROUTE_FORMAT = "/format";
148
- /** VS Code 扩展相关环境变量名 */
149
- export declare const ENV_VSCODE_MODE = "AIPANEL_VSCODE_MODE";
150
- export declare const ENV_VSCODE_PORT = "AIPANEL_VSCODE_PORT";
151
143
  /** ==================== Vue DevTools API ==================== */
152
144
  /** Vue DevTools API action 名称 */
153
145
  export declare const VUE_DEVTOOLS_ACTIONS: {
@@ -159,3 +151,31 @@ export declare const VUE_DEVTOOLS_ACTIONS: {
159
151
  readonly GET_ROUTER_INFO: "getRouterInfo";
160
152
  };
161
153
  export type VueDevtoolsAction = (typeof VUE_DEVTOOLS_ACTIONS)[keyof typeof VUE_DEVTOOLS_ACTIONS];
154
+ /** ==================== 写类工具名单 ==================== */
155
+ /** 会修改文件的工具名(host 插件与编辑后自动诊断共用的单一来源) */
156
+ export declare const MUTATING_TOOLS: ReadonlySet<string>;
157
+ /** ==================== 挂件主题 ==================== */
158
+ /** 挂件主题可选值(对应 AIPanelWidgetTheme) */
159
+ export declare const WIDGET_THEME_MODES: readonly ["auto", "light", "dark"];
160
+ /** ==================== OpenCode 环境变量名 ==================== */
161
+ /** OpenCode 相关环境变量名(opencode provider 写、es/plugins 与 dsh-plugin 读,统一引用防止字面量漂移) */
162
+ export declare const OPENCODE_ENV: {
163
+ readonly CONFIG_DIR: "OPENCODE_CONFIG_DIR";
164
+ readonly CONTEXT_API_URL: "OPENCODE_CONTEXT_API_URL";
165
+ readonly VITE_LOGS_API_URL: "OPENCODE_VITE_LOGS_API_URL";
166
+ readonly LOG_FILES_JSON: "OPENCODE_LOG_FILES_JSON";
167
+ readonly VERBOSE: "OPENCODE_VERBOSE";
168
+ readonly ENABLE_LINT: "OPENCODE_ENABLE_LINT";
169
+ readonly VUE_DEVTOOLS_API_URL: "OPENCODE_VUE_DEVTOOLS_API_URL";
170
+ readonly WORKSPACE: "OPENCODE_WORKSPACE";
171
+ };
172
+ /** ==================== SSE 事件流消息类型 ==================== */
173
+ /** 服务端 → 客户端 SSE 信封 type 字段(vite endpoints 写、client useServerSSE 读) */
174
+ export declare const SSE_EVENT_TYPES: {
175
+ readonly CONNECTED: "CONNECTED";
176
+ readonly STATUS_SYNC: "STATUS_SYNC";
177
+ readonly TASK_UPDATE: "TASK_UPDATE";
178
+ readonly SESSION_EVENT: "SESSION_EVENT";
179
+ readonly CLEAR_ELEMENTS: "CLEAR_ELEMENTS";
180
+ };
181
+ export type SSEEventType = (typeof SSE_EVENT_TYPES)[keyof typeof SSE_EVENT_TYPES];
@@ -66,17 +66,13 @@ const WARMUP_API_PATH = "/__aipanel_warmup__";
66
66
  const BRIDGE_SCRIPT_PATH = "/__aipanel_bridge__.js";
67
67
  const LOGS_API_PATH = "/__aipanel_process_logs__";
68
68
  const WIDGET_STYLE_PATH = "/__aipanel_widget__.css";
69
+ const VUE_DEVTOOLS_API_PATH = "/__aipanel_vue_devtools__";
69
70
  const MAX_TEXT_LENGTH = 100;
70
71
  const CONTEXT_MARKER = "[\u5143\u7D20\u4E0A\u4E0B\u6587]";
71
72
  const PAGE_CONTEXT_MARKER = "__AIPANEL_CONTEXT__";
72
73
  const PAGE_CONTEXT_MAX_TEXT_LENGTH = 1e4;
73
74
  const SEVERITY_ERROR = 1;
74
75
  const SEVERITY_WARN = 2;
75
- const VSCODE_EXTENSION_PORT = 51939;
76
- const VSCODE_ROUTE_HEALTH = "/health";
77
- const VSCODE_ROUTE_FORMAT = "/format";
78
- const ENV_VSCODE_MODE = "AIPANEL_VSCODE_MODE";
79
- const ENV_VSCODE_PORT = "AIPANEL_VSCODE_PORT";
80
76
  const VUE_DEVTOOLS_ACTIONS = {
81
77
  GET_COMPONENT_TREE: "getComponentTree",
82
78
  GET_COMPONENT_STATE: "getComponentState",
@@ -85,6 +81,25 @@ const VUE_DEVTOOLS_ACTIONS = {
85
81
  TOGGLE_APP: "toggleApp",
86
82
  GET_ROUTER_INFO: "getRouterInfo"
87
83
  };
84
+ const MUTATING_TOOLS = /* @__PURE__ */ new Set(["write", "edit", "apply_patch"]);
85
+ const WIDGET_THEME_MODES = ["auto", "light", "dark"];
86
+ const OPENCODE_ENV = {
87
+ CONFIG_DIR: "OPENCODE_CONFIG_DIR",
88
+ CONTEXT_API_URL: "OPENCODE_CONTEXT_API_URL",
89
+ VITE_LOGS_API_URL: "OPENCODE_VITE_LOGS_API_URL",
90
+ LOG_FILES_JSON: "OPENCODE_LOG_FILES_JSON",
91
+ VERBOSE: "OPENCODE_VERBOSE",
92
+ ENABLE_LINT: "OPENCODE_ENABLE_LINT",
93
+ VUE_DEVTOOLS_API_URL: "OPENCODE_VUE_DEVTOOLS_API_URL",
94
+ WORKSPACE: "OPENCODE_WORKSPACE"
95
+ };
96
+ const SSE_EVENT_TYPES = {
97
+ CONNECTED: "CONNECTED",
98
+ STATUS_SYNC: "STATUS_SYNC",
99
+ TASK_UPDATE: "TASK_UPDATE",
100
+ SESSION_EVENT: "SESSION_EVENT",
101
+ CLEAR_ELEMENTS: "CLEAR_ELEMENTS"
102
+ };
88
103
  export {
89
104
  AIPANEL_CACHE_DIR,
90
105
  AUTO_OPEN_DELAY,
@@ -99,8 +114,6 @@ export {
99
114
  DEFAULT_PROXY_PORT,
100
115
  DEFAULT_RETRIES,
101
116
  DEFAULT_WEB_PORT,
102
- ENV_VSCODE_MODE,
103
- ENV_VSCODE_PORT,
104
117
  EXT_BROADCAST,
105
118
  EXT_MSG,
106
119
  HOST_EVENTS_API_PATH,
@@ -111,7 +124,9 @@ export {
111
124
  MAX_PORT_TRIES,
112
125
  MAX_TEXT_LENGTH,
113
126
  MCP_API_PATH,
127
+ MUTATING_TOOLS,
114
128
  NOTIFICATION_DURATION,
129
+ OPENCODE_ENV,
115
130
  PAGE_CONTEXT_MARKER,
116
131
  PAGE_CONTEXT_MAX_TEXT_LENGTH,
117
132
  RETRY_DELAY,
@@ -124,13 +139,13 @@ export {
124
139
  SEVERITY_ERROR,
125
140
  SEVERITY_WARN,
126
141
  SSE_EVENTS_PATH,
142
+ SSE_EVENT_TYPES,
127
143
  START_API_PATH,
128
- VSCODE_EXTENSION_PORT,
129
- VSCODE_ROUTE_FORMAT,
130
- VSCODE_ROUTE_HEALTH,
131
144
  VUE_DEVTOOLS_ACTIONS,
145
+ VUE_DEVTOOLS_API_PATH,
132
146
  WARMUP_API_PATH,
133
147
  WIDGET_MSG,
134
148
  WIDGET_SCRIPT_PATH,
135
- WIDGET_STYLE_PATH
149
+ WIDGET_STYLE_PATH,
150
+ WIDGET_THEME_MODES
136
151
  };
@@ -16,6 +16,30 @@ export interface ChromeMcpOptions {
16
16
  args?: string[];
17
17
  /** 透传给 chrome-devtools-mcp 进程的额外环境变量(合并到 process.env 之上) */
18
18
  env?: Record<string, string>;
19
+ /** 项目边界策略(默认缺失 = 现状行为) */
20
+ project?: ChromeProjectOptions;
21
+ }
22
+ /**
23
+ * 项目边界策略(chrome-devtools-mcp 工具层的“本项目内操作”可配选项,默认均为现状)。
24
+ * 所有项只会收窄/放宽我们的边界判定,不改变白名单的权威性。
25
+ */
26
+ export interface ChromeProjectOptions {
27
+ /**
28
+ * 白名单条目:除自动项目页外,可开启/可见/可操作的额外页面。每条三种写法:
29
+ * - 精确 origin:"https://example.com"(前缀匹配该 origin 下任意路径)
30
+ * - glob 通配符:"https://*.example.com/**"(picomatch,* 不跨 /,放开路径需 /**)
31
+ * - 正则字面量:"/^https:\\/\\/app\\.example\\.com\\//"(以 / 起止、可带 flags,对完整 URL 匹配)
32
+ */
33
+ allowOrigins?: string[];
34
+ /** 是否允许扩展页(chrome-extension://)纳入可操作页面;启用时自动注入 --experimental-include-all-pages */
35
+ includeExtensionPages?: boolean;
36
+ /** 工具面选择:在默认白名单上按名字调整 */
37
+ tools?: {
38
+ /** 从默认白名单隐藏(如 ["upload_file"]) */
39
+ deny?: string[];
40
+ /** 仅可开启官方二级目录中的工具(如 click_at);非目录名将被忽略并告警 */
41
+ extra?: string[];
42
+ };
19
43
  }
20
44
  /**
21
45
  * 插件配置选项
@@ -2,6 +2,7 @@
2
2
  * 通用类型(Provider 无关)
3
3
  * Provider 专属类型已下沉至 @aipanel/provider-opencode。
4
4
  */
5
+ import type { SessionStatus } from "./provider";
5
6
  /**
6
7
  * 展示模式类型
7
8
  */
@@ -123,9 +124,9 @@ export type AIPanelWidgetTheme = "light" | "dark" | "auto";
123
124
  */
124
125
  export type ServiceStatus = "idle" | "starting" | "ready" | "partial" | "failed";
125
126
  /**
126
- * Session 状态类型
127
+ * Session 状态类型(别名:以 ./provider 的 SessionStatus 为单一来源)
127
128
  */
128
- export type AIPanelSessionStatusType = "idle" | "running" | "streaming" | "completed";
129
+ export type AIPanelSessionStatusType = SessionStatus;
129
130
  /**
130
131
  * Session 思考状态
131
132
  */
@@ -146,19 +147,9 @@ export interface AIPanelWidgetSession {
146
147
  url?: string;
147
148
  }
148
149
  /**
149
- * 挂件选中的元素
150
+ * 挂件选中的元素(别名:与 host 端 SelectedElement 同构,单一来源为本文件上方的 SelectedElement)
150
151
  */
151
- export interface AIPanelSelectedElement {
152
- /** 节点唯一 id(与 SelectedElement.id 同源,`@节点[n<id>]` 标记与上下文注入共用) */
153
- id?: string;
154
- filePath: string | null;
155
- line: number | null;
156
- column: number | null;
157
- innerText: string;
158
- description?: string;
159
- /** 用户选中节点时的页面 URL(AIPanel 附加;host 端上下文注入用) */
160
- previewPageUrl?: string;
161
- }
152
+ export type AIPanelSelectedElement = SelectedElement;
162
153
  /**
163
154
  * 单条代码诊断(1-based 行列坐标)——AIPanel 诊断工具(run_diagnostics 等)的
164
155
  * canonical 持久化/展示共用协议:宿主插件写 tool/result.meta,client 插件据此渲染卡片。
@@ -1,6 +1,3 @@
1
- /**
2
- * @fileoverview 通用工具函数
3
- */
4
1
  /**
5
2
  * 取(或生成)元素的节点唯一 id:优先复用已赋值的 id,否则生成随机 id 并写回元素。
6
3
  * 同一引用在会话标记(`@节点[n<id>]`)与上下文注入里使用同一个 id;
@@ -42,3 +39,29 @@ export declare function base64Decode(base64: string): string;
42
39
  * 支持多种常见响应格式
43
40
  */
44
41
  export declare function extractTextFromResponse(data: unknown): string | null;
42
+ /**
43
+ * 生成节点提及标记 `@节点[n<id>]`(与 ensureNodeId 分配的 n<hex> id 体系一致)
44
+ */
45
+ export declare function toNodeMention(id: string): string;
46
+ /**
47
+ * 从文本中提取全部节点提及 id(去重、保序)
48
+ */
49
+ export declare function parseNodeMentions(text: string): string[];
50
+ /**
51
+ * 构造 iframe postMessage 信封:{ type, ...data }(AIPanel 挂件与 dsh-client 共用同一形状)
52
+ */
53
+ export declare function widgetEnvelope(type: string, data?: Record<string, unknown>): Record<string, unknown>;
54
+ /** 重试配置 */
55
+ export interface RetryOptions {
56
+ /** 总尝试次数(默认 core DEFAULT_RETRIES) */
57
+ attempts?: number;
58
+ /** 失败后连续尝试间隔毫秒(默认 core RETRY_DELAY) */
59
+ delayMs?: number;
60
+ /** 每次失败、重试前回调(attempt 为 1-based) */
61
+ onRetry?: (attempt: number, error: unknown) => void;
62
+ }
63
+ /**
64
+ * 带线性重试的执行包裹:执行 fn 直到成功或尝试完毕;最后一次失败原样抛出。
65
+ * 环境无关(仅使用 setTimeout),放于 common 供浏览器与 node 共用。
66
+ */
67
+ export declare function withRetries<T>(fn: (attempt: number) => Promise<T>, options?: RetryOptions): Promise<T>;
@@ -1,3 +1,4 @@
1
+ import { DEFAULT_RETRIES, RETRY_DELAY } from "./constants.mjs";
1
2
  function ensureNodeId(element) {
2
3
  if (element.id) return element.id;
3
4
  const random = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID().replace(/-/g, "").slice(0, 8) : Math.random().toString(36).slice(2, 10);
@@ -50,11 +51,51 @@ function extractTextFromResponse(data) {
50
51
  }
51
52
  return null;
52
53
  }
54
+ const NODE_MENTION_RE = /@节点\[(n[0-9a-z]+)\]/g;
55
+ function toNodeMention(id) {
56
+ return `@\u8282\u70B9[${id}]`;
57
+ }
58
+ function parseNodeMentions(text) {
59
+ const out = [];
60
+ const seen = /* @__PURE__ */ new Set();
61
+ for (const m of text.matchAll(NODE_MENTION_RE)) {
62
+ const id = m[1];
63
+ if (!seen.has(id)) {
64
+ seen.add(id);
65
+ out.push(id);
66
+ }
67
+ }
68
+ return out;
69
+ }
70
+ function widgetEnvelope(type, data) {
71
+ return { type, ...data };
72
+ }
73
+ async function withRetries(fn, options = {}) {
74
+ const attempts = options.attempts ?? DEFAULT_RETRIES;
75
+ const delayMs = options.delayMs ?? RETRY_DELAY;
76
+ let lastError;
77
+ for (let i = 0; i < attempts; i++) {
78
+ try {
79
+ return await fn(i);
80
+ } catch (e) {
81
+ lastError = e;
82
+ if (i < attempts - 1) {
83
+ options.onRetry?.(i + 1, e);
84
+ if (delayMs > 0) await sleep(delayMs);
85
+ }
86
+ }
87
+ }
88
+ throw lastError instanceof Error ? lastError : new Error(String(lastError));
89
+ }
53
90
  export {
54
91
  base64Decode,
55
92
  base64Encode,
56
93
  ensureNodeId,
57
94
  extractTextFromResponse,
95
+ parseNodeMentions,
58
96
  sleep,
59
- truncate
97
+ toNodeMention,
98
+ truncate,
99
+ widgetEnvelope,
100
+ withRetries
60
101
  };
@@ -1,5 +1,10 @@
1
1
  /** 是否为可诊断的源码文件(供宿主钩子过滤 edit/write 目标) */
2
2
  export declare function isJsFile(filePath: string): boolean;
3
+ /**
4
+ * run_diagnostics 工具描述(单一来源,供 opencode / dsh 两侧插件引用):
5
+ * 只声明能力与支持的文件类型,不涉及内部使用的检查工具。
6
+ */
7
+ export declare const DIAGNOSTICS_TOOL_DESCRIPTION: string;
3
8
  /** LSP 风格诊断项(供宿主写入 metadata.diagnostics 等结构化输出) */
4
9
  export interface DiagnosticItem {
5
10
  /** 所属文件路径(相对/绝对,按来源解析);跨文件诊断(全量模式)时必有 */
@@ -19,6 +19,19 @@ const JS_EXTENSIONS = /* @__PURE__ */ new Set([
19
19
  function isJsFile(filePath) {
20
20
  return JS_EXTENSIONS.has(path.extname(filePath));
21
21
  }
22
+ const DIAGNOSTICS_TOOL_DESCRIPTION = [
23
+ "\u8FD0\u884C ESLint \u4E0E TypeScript \u7C7B\u578B\u8BCA\u65AD\uFF0C\u8FD4\u56DE\u8BCA\u65AD\u7ED3\u679C\u3002",
24
+ "",
25
+ "**\u652F\u6301\u7684\u6587\u4EF6\u7C7B\u578B**\uFF1A",
26
+ `- ESLint\uFF1AJavaScript / TypeScript / Vue \u6E90\u7801\uFF08${[...JS_EXTENSIONS].map((e) => `*${e}`).join(" ")}\uFF09`,
27
+ "- TypeScript \u7C7B\u578B\u68C0\u67E5\uFF1A*.ts *.tsx *.vue",
28
+ "",
29
+ "**\u4F55\u65F6\u4F7F\u7528\u6B64\u5DE5\u5177**\uFF1A",
30
+ "- \u521A\u5B8C\u6210\u4EE3\u7801\u4FEE\u6539\uFF0C\u60F3\u9A8C\u8BC1\u662F\u5426\u6709 ESLint \u9519\u8BEF\u6216\u7C7B\u578B\u9519\u8BEF",
31
+ "- \u5728\u63D0\u4EA4\u4EE3\u7801\u524D\u8FDB\u884C\u8D28\u91CF\u68C0\u67E5",
32
+ "- \u6392\u67E5\u7F16\u8F91\u5668\u672A\u663E\u793A\u4F46\u5B9E\u9645\u5B58\u5728\u7684\u7C7B\u578B\u95EE\u9898",
33
+ "- \u4E0D\u4F20\u53C2\u6570\u53EF\u5168\u91CF\u8BCA\u65AD\u6574\u4E2A\u9879\u76EE"
34
+ ].join("\n");
22
35
  let ESLintClass;
23
36
  function loadESLint(workspace) {
24
37
  if (ESLintClass) return;
@@ -34,7 +47,12 @@ function loadESLint(workspace) {
34
47
  }
35
48
  async function lintFiles(pattern, cwd, warnLimit = 5) {
36
49
  loadESLint(cwd);
37
- if (!ESLintClass) return {};
50
+ if (!ESLintClass) {
51
+ return {
52
+ text: `[ESLint] \u672A\u8FD0\u884C\uFF1A\u65E0\u6CD5\u5728 workspace "${cwd}" \u89E3\u6790\u5230 eslint\uFF08\u4EC5\u663E\u793A TypeScript \u8BCA\u65AD\uFF09`,
53
+ diagnostics: []
54
+ };
55
+ }
38
56
  try {
39
57
  const eslint = new ESLintClass({ cwd });
40
58
  const results = await eslint.lintFiles(pattern);
@@ -82,7 +100,10 @@ async function lintFiles(pattern, cwd, warnLimit = 5) {
82
100
  return { text: lines.length > 0 ? lines.join("\n") : void 0, diagnostics };
83
101
  } catch (err) {
84
102
  log.warn("ESLint failed", { pattern, error: err.message });
85
- return {};
103
+ return {
104
+ text: `[ESLint] \u8FD0\u884C\u5931\u8D25\uFF1A${err.message}\uFF08\u4EC5\u663E\u793A TypeScript \u8BCA\u65AD\uFF09`,
105
+ diagnostics: []
106
+ };
86
107
  }
87
108
  }
88
109
  let _vueTscBin;
@@ -255,6 +276,7 @@ function formatDiagnosticsSections(title, eslintOutput, tscOutput) {
255
276
  ` + parts.join("\n\n");
256
277
  }
257
278
  export {
279
+ DIAGNOSTICS_TOOL_DESCRIPTION,
258
280
  findAllTsconfigDirs,
259
281
  findTsconfigDir,
260
282
  formatDiagnosticsSections,
@@ -3,8 +3,10 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
  import { LOG_PREFIX } from "../common/constants.mjs";
5
5
  import {
6
+ LEVEL_NAMES,
6
7
  LogLevel,
7
8
  getConfig,
9
+ getTimestamp,
8
10
  formatContext,
9
11
  formatValue,
10
12
  generateTraceId as _generateTraceId
@@ -28,21 +30,6 @@ const LEVEL_COLORS = {
28
30
  [LogLevel.ERROR]: COLORS.red,
29
31
  [LogLevel.NONE]: COLORS.reset
30
32
  };
31
- const LEVEL_NAMES = {
32
- [LogLevel.DEBUG]: "DEBUG",
33
- [LogLevel.INFO]: "INFO",
34
- [LogLevel.WARN]: "WARN",
35
- [LogLevel.ERROR]: "ERROR",
36
- [LogLevel.NONE]: "NONE"
37
- };
38
- function getTimestamp() {
39
- const now = /* @__PURE__ */ new Date();
40
- const hours = String(now.getHours()).padStart(2, "0");
41
- const minutes = String(now.getMinutes()).padStart(2, "0");
42
- const seconds = String(now.getSeconds()).padStart(2, "0");
43
- const ms = String(now.getMilliseconds()).padStart(3, "0");
44
- return `${hours}:${minutes}:${seconds}.${ms}`;
45
- }
46
33
  function getCallerInfo(depth = 3) {
47
34
  const stack = new Error().stack;
48
35
  if (!stack) return "";
@@ -26,3 +26,44 @@ export declare function isPortAvailable(port: number, hostname?: string): Promis
26
26
  * 从 startPort 开始寻找可用端口
27
27
  */
28
28
  export declare function findAvailablePort(startPort: number, hostname?: string, maxTries?: number): Promise<number>;
29
+ /**
30
+ * 可等待的进程接口(ResultPromise 等匹配就够)
31
+ */
32
+ export interface WaitableProcess {
33
+ exitCode: number | null | undefined;
34
+ }
35
+ /**
36
+ * 轮询等待服务准备就绪(HTTP 状态码 < 500 即当作 ready);超时或进程退出时 reject
37
+ * @param url - 检查的服务 URL
38
+ * @param timeout - 超时毫秒数
39
+ * @param proc - 可选进程,提前退出时直接失败
40
+ */
41
+ export declare function waitForServer(url: string, timeout?: number, proc?: WaitableProcess): Promise<void>;
42
+ /**
43
+ * 从指定目录往上查找 .git 根目录(找不到时返回起始目录)
44
+ */
45
+ export declare function findGitRoot(startDir: string, maxDepth?: number): string;
46
+ /**
47
+ * 检查某 CLI 是否安装(运行 <bin> --version,退出码 0 即存在)
48
+ */
49
+ export declare function checkCliInstalled(bin: string): Promise<boolean>;
50
+ /**
51
+ * 获取某 CLI 版本号(<bin> --version 的第一行,败路返回 null)
52
+ */
53
+ export declare function getCliVersion(bin: string): Promise<string | null>;
54
+ /** 孤儿进程清理配置(提供商的 check/kill 统一实现) */
55
+ export interface KillOrphanCliOptions {
56
+ /** 进程命令行/参数中需匹配的子串(如 opencode / dsh) */
57
+ match: string;
58
+ /** Windows wmic 按名称查询的进程名(如 opencode.exe / node.exe) */
59
+ winName: string;
60
+ /** 超时毫秒(默认 5000) */
61
+ timeout?: number;
62
+ /** 运行名(仅用于日志,默认等于 match) */
63
+ label?: string;
64
+ }
65
+ /**
66
+ * 清理被 reparent 到 init(PPID=1)的孤儿进程(win: wmic+taskkill;unix: ps+kill)
67
+ * @returns 被成功结束的进程数
68
+ */
69
+ export declare function killOrphanCliProcesses(bin: string, options: KillOrphanCliOptions): Promise<number>;