@sstar/boardlinker_host 0.2.0

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/logger.js ADDED
@@ -0,0 +1,204 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ export var LogLevel;
5
+ (function (LogLevel) {
6
+ LogLevel["DEBUG"] = "debug";
7
+ LogLevel["INFO"] = "info";
8
+ LogLevel["WARN"] = "warn";
9
+ LogLevel["ERROR"] = "error";
10
+ })(LogLevel || (LogLevel = {}));
11
+ export class HostLogger {
12
+ constructor() {
13
+ this.logStream = null;
14
+ this.serviceName = 'host';
15
+ this.config = this.loadConfig();
16
+ if (this.config.enabled) {
17
+ this.initializeLogFile();
18
+ }
19
+ this.currentLogFile = '';
20
+ }
21
+ loadConfig() {
22
+ return {
23
+ enabled: process.env.BOARDLINKER_HOST_DEBUG === '1' || process.env.BOARDLINKER_DEBUG === '1',
24
+ level: process.env.BOARDLINKER_LOG_LEVEL || LogLevel.DEBUG,
25
+ dir: process.env.BOARDLINKER_LOG_DIR || this.getDefaultLogDir(),
26
+ maxSize: this.parseSize(process.env.BOARDLINKER_LOG_MAX_SIZE) || 10 * 1024 * 1024, // 10MB
27
+ maxFiles: parseInt(process.env.BOARDLINKER_LOG_MAX_FILES || '20'),
28
+ maxAge: parseInt(process.env.BOARDLINKER_LOG_MAX_AGE || '7'), // days
29
+ };
30
+ }
31
+ getDefaultLogDir() {
32
+ return path.join(os.homedir(), '.config', 'board_linker', 'logs', 'host');
33
+ }
34
+ parseSize(sizeStr) {
35
+ if (!sizeStr)
36
+ return 0;
37
+ const units = {
38
+ B: 1,
39
+ KB: 1024,
40
+ MB: 1024 * 1024,
41
+ GB: 1024 * 1024 * 1024,
42
+ };
43
+ const match = sizeStr.match(/^(\d+)(B|KB|MB|GB)?$/i);
44
+ if (!match)
45
+ return 0;
46
+ const value = parseInt(match[1]);
47
+ const unit = match[2]?.toUpperCase() || 'B';
48
+ return value * (units[unit] || 1);
49
+ }
50
+ async initializeLogFile() {
51
+ try {
52
+ // 确保日志目录存在
53
+ await fs.promises.mkdir(this.config.dir, { recursive: true });
54
+ // 生成基于启动时间的日志文件名(使用本地时间)
55
+ const now = new Date();
56
+ const year = now.getFullYear();
57
+ const month = String(now.getMonth() + 1).padStart(2, '0');
58
+ const day = String(now.getDate()).padStart(2, '0');
59
+ const hours = String(now.getHours()).padStart(2, '0');
60
+ const minutes = String(now.getMinutes()).padStart(2, '0');
61
+ const seconds = String(now.getSeconds()).padStart(2, '0');
62
+ const timestamp = `${year}-${month}-${day}T${hours}-${minutes}-${seconds}`;
63
+ this.currentLogFile = path.join(this.config.dir, `${this.serviceName}-${timestamp}.log`);
64
+ // 创建写入流
65
+ this.logStream = fs.createWriteStream(this.currentLogFile, { flags: 'a' });
66
+ // 写入启动标记
67
+ this.write(LogLevel.INFO, 'logger.initialized', {
68
+ pid: process.pid,
69
+ nodeVersion: process.version,
70
+ platform: os.platform(),
71
+ logFile: this.currentLogFile,
72
+ });
73
+ // 设置错误处理
74
+ this.logStream.on('error', (error) => {
75
+ console.error('[boardlinker-host] Logger write error:', error);
76
+ });
77
+ }
78
+ catch (error) {
79
+ console.error('[boardlinker-host] Failed to initialize logger:', error);
80
+ this.logStream = null;
81
+ }
82
+ }
83
+ write(level, event, data) {
84
+ if (!this.config.enabled || !this.logStream) {
85
+ return;
86
+ }
87
+ // 检查日志级别
88
+ if (!this.shouldLog(level)) {
89
+ return;
90
+ }
91
+ const logEntry = {
92
+ ts: Date.now(),
93
+ level,
94
+ event,
95
+ data,
96
+ pid: process.pid,
97
+ };
98
+ const logLine = JSON.stringify(logEntry) + '\n';
99
+ try {
100
+ this.logStream.write(logLine);
101
+ // 检查是否需要轮转
102
+ this.checkRotation().catch((error) => {
103
+ console.error('[boardlinker-host] Log rotation error:', error);
104
+ });
105
+ }
106
+ catch (error) {
107
+ console.error('[boardlinker-host] Logger write error:', error);
108
+ }
109
+ }
110
+ shouldLog(level) {
111
+ const levels = [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR];
112
+ const currentLevelIndex = levels.indexOf(this.config.level);
113
+ const messageLevelIndex = levels.indexOf(level);
114
+ return messageLevelIndex >= currentLevelIndex;
115
+ }
116
+ async checkRotation() {
117
+ if (!this.currentLogFile || !this.logStream) {
118
+ return;
119
+ }
120
+ try {
121
+ const stats = await fs.promises.stat(this.currentLogFile);
122
+ if (stats.size > this.config.maxSize) {
123
+ await this.rotate();
124
+ }
125
+ }
126
+ catch (error) {
127
+ // 忽略文件不存在的错误
128
+ }
129
+ }
130
+ async rotate() {
131
+ if (!this.logStream)
132
+ return;
133
+ try {
134
+ // 关闭当前文件流
135
+ this.logStream.end();
136
+ // 创建新的日志文件
137
+ await this.initializeLogFile();
138
+ // 清理旧文件
139
+ await this.cleanupOldFiles();
140
+ }
141
+ catch (error) {
142
+ console.error('[boardlinker-host] Log rotation error:', error);
143
+ }
144
+ }
145
+ async cleanupOldFiles() {
146
+ try {
147
+ const files = await fs.promises.readdir(this.config.dir);
148
+ const logFiles = files
149
+ .filter((f) => f.startsWith(`${this.serviceName}-`) && f.endsWith('.log'))
150
+ .map((f) => ({ name: f, path: path.join(this.config.dir, f) }));
151
+ // 按修改时间排序
152
+ const fileStats = await Promise.all(logFiles.map(async (f) => ({
153
+ ...f,
154
+ mtime: (await fs.promises.stat(f.path)).mtime,
155
+ })));
156
+ fileStats.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
157
+ // 删除超出数量限制的文件
158
+ const filesToDelete = fileStats.slice(this.config.maxFiles);
159
+ await Promise.all(filesToDelete.map((f) => fs.promises.unlink(f.path).catch(() => {
160
+ // 忽略删除错误
161
+ })));
162
+ // 删除超出时间限制的文件
163
+ const cutoffDate = new Date(Date.now() - this.config.maxAge * 24 * 60 * 60 * 1000);
164
+ const oldFiles = fileStats.filter((f) => f.mtime < cutoffDate);
165
+ await Promise.all(oldFiles.map((f) => fs.promises.unlink(f.path).catch(() => {
166
+ // 忽略删除错误
167
+ })));
168
+ }
169
+ catch (error) {
170
+ console.error('[boardlinker-host] Log cleanup error:', error);
171
+ }
172
+ }
173
+ close() {
174
+ if (this.logStream) {
175
+ this.write(LogLevel.INFO, 'logger.closed', { reason: 'shutdown' });
176
+ this.logStream.end();
177
+ this.logStream = null;
178
+ }
179
+ }
180
+ }
181
+ // 单例实例
182
+ let hostLogger = null;
183
+ export function getHostLogger() {
184
+ if (!hostLogger) {
185
+ hostLogger = new HostLogger();
186
+ }
187
+ return hostLogger;
188
+ }
189
+ // 便捷函数
190
+ export function logHost(level, event, data) {
191
+ getHostLogger().write(level, event, data);
192
+ }
193
+ export function logHostDebug(event, data) {
194
+ logHost(LogLevel.DEBUG, event, data);
195
+ }
196
+ export function logHostInfo(event, data) {
197
+ logHost(LogLevel.INFO, event, data);
198
+ }
199
+ export function logHostWarn(event, data) {
200
+ logHost(LogLevel.WARN, event, data);
201
+ }
202
+ export function logHostError(event, data) {
203
+ logHost(LogLevel.ERROR, event, data);
204
+ }
@@ -0,0 +1,473 @@
1
+ syntax = "proto3";
2
+
3
+ package boardlinker.agent.v1;
4
+
5
+ service AgentService {
6
+ rpc Status (StatusRequest) returns (StatusResponse);
7
+ rpc BoardUartWrite (BoardUartWriteRequest) returns (BoardUartWriteResponse);
8
+ // Host -> Agent: best-effort structured log streaming
9
+ rpc HostLogStream (stream HostLogEntry) returns (HostLogSummary);
10
+ // 板卡 UART 会话接口:多 session + 读写
11
+ rpc BoardUartListSessions (BoardUartListSessionsRequest) returns (BoardUartListSessionsResponse);
12
+ rpc BoardUartSessionWrite (BoardUartSessionWriteRequest) returns (BoardUartSessionWriteResponse);
13
+ rpc BoardUartSessionRead (BoardUartSessionReadRequest) returns (BoardUartSessionReadResponse);
14
+ rpc BoardUartOpenManual (BoardUartOpenManualRequest) returns (BoardUartOpenManualResponse);
15
+ rpc BoardUartCloseSession (BoardUartCloseSessionRequest) returns (BoardUartCloseSessionResponse);
16
+ rpc BoardUartStatus (BoardUartStatusRequest) returns (BoardUartStatusResponse);
17
+ rpc BoardUartForceClose (BoardUartForceCloseRequest) returns (BoardUartForceCloseResponse);
18
+ rpc TftpUpload (TftpUploadRequest) returns (TftpUploadResponse);
19
+ rpc TftpDownload (TftpDownloadRequest) returns (TftpDownloadResponse);
20
+ rpc TftpList (TftpListRequest) returns (TftpListResponse);
21
+ rpc TftpUserguide (TftpUserguideRequest) returns (TftpUserguideResponse);
22
+ rpc UBootBreak (UBootBreakRequest) returns (UBootBreakResponse);
23
+ rpc UBootRunCommand (UBootRunCommandRequest) returns (UBootRunCommandResponse);
24
+ rpc TunnelStart (TunnelStartRequest) returns (TunnelStartResponse);
25
+ rpc TunnelStop (TunnelStopRequest) returns (TunnelStopResponse);
26
+ // Added utility endpoints
27
+ rpc BoardUartListPorts (BoardUartListPortsRequest) returns (BoardUartListPortsResponse);
28
+ rpc ListSshCandidates (ListSshCandidatesRequest) returns (ListSshCandidatesResponse);
29
+ // NFS 文件同步与浏览
30
+ rpc NfsUpload (NfsUploadRequest) returns (NfsUploadResponse);
31
+ rpc NfsDownload (NfsDownloadRequest) returns (NfsDownloadResponse);
32
+ rpc NfsList (NfsListRequest) returns (NfsListResponse);
33
+ rpc NfsInfo (NfsInfoRequest) returns (NfsInfoResponse);
34
+ rpc NfsUserguide (NfsUserguideRequest) returns (NfsUserguideResponse);
35
+ // 板卡环境说明
36
+ rpc BoardNotes (BoardNotesRequest) returns (BoardNotesResponse);
37
+ // 固件镜像准备与 ISP 恢复烧录
38
+ rpc FirmwarePrepareImages (FirmwarePrepareImagesRequest) returns (FirmwarePrepareImagesResponse);
39
+ rpc FirmwareBurnRecover (FirmwareBurnRecoverRequest) returns (FirmwareBurnRecoverResponse);
40
+ rpc FirmwareUserGuide (FirmwareUserGuideRequest) returns (FirmwareUserGuideResponse);
41
+ }
42
+
43
+ // Files Service - 极简文件服务
44
+ service FilesService {
45
+ // 公开接口 - 对应 MCP 工具
46
+ rpc PutPath(stream PutPathChunk) returns (PutPathSummary);
47
+ rpc GetPath(GetPathRequest) returns (stream PathChunk);
48
+
49
+ // 内部接口 - 完整 CRUD 实现,但不暴露为 MCP 工具
50
+ rpc ListFiles(ListFilesRequest) returns (ListFilesResponse);
51
+ rpc RemoveFiles(RemoveFilesRequest) returns (RemoveFilesResponse);
52
+ rpc MakeDirectory(MakeDirectoryRequest) returns (MakeDirectoryResponse);
53
+ rpc GetStat(GetStatRequest) returns (GetStatResponse);
54
+ }
55
+
56
+ // Pairing service: validate that a given endpoint matches the intended Agent instance
57
+ service PairingService {
58
+ rpc PairCheck (PairCheckRequest) returns (PairCheckResponse);
59
+ }
60
+
61
+ message PairCheckRequest {
62
+ string pairCode = 1;
63
+ string clientId = 2;
64
+ int64 timestamp = 3;
65
+ }
66
+
67
+ message PairCheckResponse {
68
+ bool accepted = 1;
69
+ string agentId = 2;
70
+ string version = 3;
71
+ string startupTime = 4;
72
+ string reason = 5;
73
+ }
74
+
75
+ message StatusRequest {}
76
+ message StatusResponse {
77
+ string status = 1;
78
+ string version = 2;
79
+ int64 timestamp = 3;
80
+ repeated NetworkInterfaceInfo networkInterfaces = 4;
81
+ string error = 5;
82
+ // Network mount (CIFS/NFS) helpers for AI orchestration
83
+ string shareUsername = 6;
84
+ string sharePassword = 7;
85
+ string nfsExportPath = 8;
86
+ // Platform information
87
+ string platform = 9;
88
+ string arch = 10;
89
+ }
90
+
91
+ message NetworkInterfaceInfo {
92
+ string ip = 1;
93
+ string netmask = 2;
94
+ string broadcast = 3;
95
+ string gateway = 4;
96
+ string interface = 5;
97
+ bool internal = 6;
98
+ }
99
+
100
+ message BoardUartWriteRequest {
101
+ string sessionId = 1; // Empty means default session
102
+ bytes data = 2;
103
+ bool mock = 3;
104
+ // New fields for Smart Sync
105
+ optional string waitFor = 4;
106
+ optional int64 timeoutMs = 5;
107
+ }
108
+
109
+ message BoardUartWriteResponse {
110
+ string status = 1;
111
+ string output = 2;
112
+ string nextStep = 3;
113
+ int64 elapsedMs = 4;
114
+ int64 lastOutputGapMs = 5;
115
+ uint32 remains = 6; // 此次返回后,缓冲区中剩余待读取的数据字节数
116
+ }
117
+
118
+ // 板卡 UART 会话相关
119
+ message BoardUartSessionInfo {
120
+ string id = 1;
121
+ string label = 2;
122
+ string role = 3;
123
+ bool isDefault = 4;
124
+ bool canWrite = 5;
125
+ }
126
+
127
+ message BoardUartListSessionsRequest {}
128
+ message BoardUartListSessionsResponse { repeated BoardUartSessionInfo sessions = 1; }
129
+
130
+ message BoardUartSessionWriteRequest {
131
+ string sessionId = 1; // 为空表示默认会话
132
+ bytes data = 2;
133
+ }
134
+ message BoardUartSessionWriteResponse {
135
+ uint32 bytesWritten = 1;
136
+ uint32 remains = 2; // 此次返回后,缓冲区中剩余待读取的数据字节数
137
+ }
138
+
139
+ message BoardUartSessionReadRequest {
140
+ string sessionId = 1; // 为空表示默认会话
141
+ uint32 maxBytes = 2;
142
+ uint32 quietMs = 3;
143
+ uint32 timeoutMs = 4;
144
+ }
145
+ message BoardUartSessionReadResponse {
146
+ bytes data = 1;
147
+ uint32 bytes = 2;
148
+ bool timedOut = 3;
149
+ uint32 remains = 4; // 此次返回后,缓冲区中剩余待读取的数据字节数
150
+ }
151
+
152
+ message BoardUartOpenManualRequest {
153
+ string port = 1;
154
+ uint32 baud = 2;
155
+ }
156
+ message BoardUartOpenManualResponse {
157
+ BoardUartSessionInfo session = 1;
158
+ }
159
+
160
+ message BoardUartCloseSessionRequest { string sessionId = 1; }
161
+ message BoardUartCloseSessionResponse { bool closed = 1; }
162
+
163
+ message BoardUartAttachedTool {
164
+ string id = 1;
165
+ string source = 2;
166
+ string sessionId = 3;
167
+ string port = 4;
168
+ uint32 baud = 5;
169
+ int64 attachedAtMs = 6;
170
+ }
171
+
172
+ message BoardUartStatusRequest {}
173
+ message BoardUartStatusResponse {
174
+ string status = 1;
175
+ bool disabled = 2;
176
+ bool hasModule = 3;
177
+ string port = 4;
178
+ uint32 baud = 5;
179
+ repeated BoardUartSessionInfo sessions = 6;
180
+ repeated BoardUartAttachedTool attachedTools = 7;
181
+ bool suspended = 8;
182
+ string suspendOwner = 9;
183
+ int64 suspendedSinceMs = 10;
184
+ int64 maxSuspendMs = 11;
185
+ bool autoResumed = 12;
186
+ }
187
+
188
+ message BoardUartForceCloseRequest {}
189
+ message BoardUartForceCloseResponse { bool ok = 1; }
190
+
191
+ message BoardUartListPortsRequest {}
192
+ message BoardUartPortInfo { string path = 1; string friendlyName = 2; }
193
+ message BoardUartListPortsResponse { repeated BoardUartPortInfo ports = 1; }
194
+
195
+ message TftpUploadRequest { string fileName = 1; bytes content = 2; }
196
+ message TftpUploadResponse { string path = 1; uint64 size = 2; string sha256 = 3; }
197
+
198
+ message TftpDownloadRequest { string path = 1; }
199
+ message TftpDownloadResponse { bytes content = 1; uint64 size = 2; }
200
+
201
+ message TftpListRequest {
202
+ string basePath = 1; // 相对 TFTP 根目录的 POSIX 路径,可为空表示根目录
203
+ uint32 depth = 2; // 递归深度
204
+ }
205
+
206
+
207
+ message TftpListResponse {
208
+ repeated FileEntry entries = 1;
209
+ bool truncated = 2;
210
+ }
211
+
212
+
213
+ message TftpUserguideRequest {}
214
+ message TftpUserguideResponse {
215
+ string content = 1;
216
+ string format = 2;
217
+ }
218
+
219
+ message UBootBreakRequest { string sessionId = 1; uint32 timeoutMs = 2; bool mock = 3; }
220
+ message UBootBreakResponse { bool ok = 1; string details = 2; }
221
+
222
+ message UBootRunCommandRequest { string sessionId = 1; string command = 2; uint32 timeoutMs = 3; bool mock = 4; }
223
+ message UBootRunCommandResponse { bool ok = 1; string output = 2; }
224
+
225
+
226
+ message TunnelStartRequest { string host = 1; uint32 port = 2; string user = 3; string password = 4; string privateKeyPath = 5; uint32 localPort = 6; string forwardHost = 7; uint32 forwardPort = 8; }
227
+ message TunnelStartResponse { bool connected = 1; string details = 2; }
228
+
229
+ message TunnelStopRequest {}
230
+ message TunnelStopResponse { bool stopped = 1; }
231
+
232
+
233
+ message ListSshCandidatesRequest {}
234
+ message SshCandidate { string label = 1; string host = 2; }
235
+ message ListSshCandidatesResponse { repeated SshCandidate hosts = 1; }
236
+
237
+ // NFS 相关
238
+ message NfsUploadRequest { string remoteSubpath = 1; bytes content = 2; }
239
+ message NfsUploadResponse { string remoteSubpath = 1; uint64 size = 2; }
240
+
241
+ message NfsDownloadRequest { string remoteSubpath = 1; }
242
+ message NfsDownloadResponse { bytes content = 1; uint64 size = 2; }
243
+
244
+ enum NfsListMode {
245
+ NFS_LIST_MODE_UNSPECIFIED = 0;
246
+ NFS_LIST_MODE_SIMPLE = 1;
247
+ NFS_LIST_MODE_DETAILED = 2;
248
+ }
249
+
250
+ message NfsListRequest {
251
+ string remoteSubpath = 1;
252
+ NfsListMode mode = 2;
253
+ optional uint32 limit = 3;
254
+ }
255
+ message NfsListResponse {
256
+ repeated FileEntry entries = 1;
257
+ bool truncated = 2;
258
+ }
259
+
260
+ message NfsInfoRequest {}
261
+ message NfsInfoResponse {
262
+ bool enabled = 1;
263
+ string exportName = 2;
264
+ string description = 3;
265
+ }
266
+
267
+ message NfsUserguideRequest {}
268
+ message NfsUserguideResponse {
269
+ string content = 1;
270
+ string format = 2;
271
+ }
272
+
273
+ // 板卡说明文档
274
+ message BoardNotesRequest {
275
+ string board = 1;
276
+ string section = 2;
277
+ }
278
+ message BoardNotesResponse {
279
+ string content = 1;
280
+ string format = 2;
281
+ }
282
+
283
+ // Files 相关消息定义
284
+
285
+ enum FileType {
286
+ FILE_TYPE_UNSPECIFIED = 0;
287
+ FILE_TYPE_FILE = 1;
288
+ FILE_TYPE_DIRECTORY = 2;
289
+ }
290
+
291
+ // 统一路径传输
292
+ message PutPathChunk {
293
+ oneof kind {
294
+ PutPathMeta meta = 1;
295
+ FileContentChunk data = 2;
296
+ }
297
+ }
298
+
299
+ message PutPathMeta {
300
+ FileType src_type = 1;
301
+ string dst_path = 2; // 相对 Agent files 根目录
302
+ uint32 total_files = 3;
303
+ uint64 total_bytes = 4;
304
+ bool overwrite = 5;
305
+ }
306
+
307
+ message FileContentChunk {
308
+ string relative_path = 1; // 目录内的相对路径;单文件时为文件名
309
+ bytes content = 2;
310
+ bool is_file_complete = 3;
311
+ }
312
+
313
+ message PutPathSummary {
314
+ bool success = 1;
315
+ uint32 files_written = 2;
316
+ uint64 bytes_written = 3;
317
+ repeated string failed_files = 4;
318
+ }
319
+
320
+ message GetPathRequest {
321
+ string src_path = 1; // 相对 Agent files 根目录
322
+ bool recursive = 2; // 目录是否递归
323
+ uint32 chunk_size = 3; // 可选分块大小
324
+ bool skip_hidden = 4; // 是否跳过隐藏文件
325
+ }
326
+
327
+ message PathChunk {
328
+ FileType src_type = 1; // FILE_TYPE_FILE 或 FILE_TYPE_DIRECTORY
329
+ string relative_path = 2; // 目录内的相对路径;单文件时为文件名
330
+ bytes content = 3;
331
+ bool is_file_complete = 4; // 当前文件完成
332
+ bool is_directory_complete = 5; // 目录全部完成
333
+ uint32 files_completed = 6; // 已完成文件数
334
+ uint32 total_files = 7; // 总文件数
335
+ }
336
+
337
+ message FileEntry {
338
+ string name = 1;
339
+ string path = 2;
340
+ FileType type = 3;
341
+ uint64 size = 4;
342
+ int64 mtime = 5;
343
+ }
344
+
345
+ message ListFilesRequest {
346
+ optional string path = 1;
347
+ optional bool all = 2;
348
+ optional bool recursive = 3;
349
+ optional string pattern = 4;
350
+ }
351
+
352
+ message ListFilesResponse {
353
+ repeated FileEntry entries = 1;
354
+ uint32 total_entries = 2;
355
+ bool truncated = 3;
356
+ }
357
+
358
+ message RemoveFilesRequest {
359
+ string path = 1;
360
+ optional bool recursive = 2;
361
+ }
362
+
363
+ message RemoveFilesResponse {
364
+ bool success = 1;
365
+ uint32 deleted_count = 2;
366
+ }
367
+
368
+ message MakeDirectoryRequest {
369
+ string path = 1;
370
+ optional bool parents = 2;
371
+ }
372
+
373
+ message MakeDirectoryResponse {
374
+ bool success = 1;
375
+ string path = 2;
376
+ }
377
+
378
+ message GetStatRequest {
379
+ string path = 1;
380
+ }
381
+
382
+ message FileStat {
383
+ string path = 1;
384
+ string name = 2;
385
+ uint64 size = 3;
386
+ bool is_file = 4;
387
+ bool is_directory = 5;
388
+ int64 modified_at = 6;
389
+ optional string permissions = 7;
390
+ }
391
+
392
+ message GetStatResponse {
393
+ bool success = 1;
394
+ FileStat stat = 2;
395
+ }
396
+
397
+ // 固件镜像及 ISP 烧录相关
398
+
399
+ message FirmwarePrepareImagesRequest {
400
+ // 相对 TFTP 根目录的 images 子目录路径,例如 "images_agent" 或 "images_agent/build123"
401
+ string imagesRoot = 1;
402
+ }
403
+
404
+
405
+ message FirmwarePartitionMeta {
406
+ string name = 1;
407
+ // 分区起始地址,字符串形式(例如 "0x0", "0x200000")
408
+ string address = 2;
409
+ uint64 size = 3;
410
+ // 可选的分组名称(例如 "BOOT", "SYSTEM")
411
+ string group = 4;
412
+ }
413
+
414
+ message FirmwareStrategySegment {
415
+ string file = 1;
416
+ string offset = 2;
417
+ }
418
+
419
+ message FirmwareRecommendedStrategy {
420
+ string id = 1;
421
+ string description = 2;
422
+ repeated FirmwareStrategySegment segments = 3;
423
+ }
424
+
425
+ message FirmwareImagesMeta {
426
+ // 整体镜像类型(任意字符串;自动检测失败时为 "unknown")
427
+ string imagesType = 1;
428
+ // 文件列表,每行格式:文件路径 大小
429
+ string files = 2;
430
+ repeated FirmwarePartitionMeta partitions = 3;
431
+ repeated FirmwareRecommendedStrategy recommendedStrategies = 4;
432
+ }
433
+
434
+ message FirmwarePrepareImagesResponse {
435
+ string meta = 1; // 纯字符串格式,包含 TOON 格式分区信息
436
+ }
437
+
438
+ message FirmwareBurnRecoverRequest {
439
+ string imagesRoot = 1;
440
+ // 由 AI 构造的 ISP 工具参数数组(顺序敏感)
441
+ repeated string args = 2;
442
+ int64 timeoutMs = 3;
443
+ bool force = 4;
444
+ }
445
+
446
+ message FirmwareBurnRecoverResponse {
447
+ bool ok = 1;
448
+ int32 exitCode = 2;
449
+ string details = 3;
450
+ string logTail = 4;
451
+ }
452
+
453
+ message FirmwareUserGuideRequest {}
454
+
455
+ message FirmwareUserGuideResponse {
456
+ // Markdown 文本,内部 MAY 包含 JSON fenced block(FSM 模板、语义提示等)
457
+ string json = 1;
458
+ }
459
+
460
+ // Host -> Agent best-effort structured log streaming
461
+ message HostLogEntry {
462
+ int64 ts = 1;
463
+ string level = 2;
464
+ string event = 3;
465
+ // JSON string. Agent may parse it for UI rendering.
466
+ string data_json = 4;
467
+ int32 pid = 5;
468
+ }
469
+
470
+ message HostLogSummary {
471
+ bool accepted = 1;
472
+ string reason = 2;
473
+ }