@steerable/agent-shell 0.6.52 → 0.6.55

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.
Files changed (60) hide show
  1. package/dist/brand.js +1 -1
  2. package/dist/host/pack-assembly.d.ts +6 -16
  3. package/dist/host/pack-assembly.js +3 -4
  4. package/dist/host/runtime.js +2 -0
  5. package/dist/i18n.d.ts +23 -0
  6. package/dist/i18n.js +87 -0
  7. package/dist/image-attachment.d.ts +31 -4
  8. package/dist/image-attachment.js +13 -18
  9. package/dist/json-store.js +3 -6
  10. package/dist/local-backend/portable-bundle.d.ts +16 -2
  11. package/dist/local-backend/portable-bundle.js +29 -16
  12. package/dist/local-backend/portable-service.js +25 -11
  13. package/dist/local-backend/router.js +56 -17
  14. package/dist/local-backend/skills/10-goal/SKILL.md +1 -1
  15. package/dist/local-backend/skills/11-loop/SKILL.md +1 -1
  16. package/dist/local-backend/skills/12-create-skill/SKILL.md +2 -2
  17. package/dist/local-executor.d.ts +22 -0
  18. package/dist/local-executor.js +118 -1
  19. package/dist/local-script-registry.js +2 -2
  20. package/dist/mcp-executor.d.ts +42 -10
  21. package/dist/mcp-executor.js +248 -41
  22. package/dist/mcp-server-registry.d.ts +59 -11
  23. package/dist/mcp-server-registry.js +193 -28
  24. package/dist/product-config.d.ts +24 -14
  25. package/dist/project-registry.d.ts +3 -3
  26. package/dist/project-registry.js +2 -2
  27. package/dist/runtime.d.ts +8 -54
  28. package/dist/runtime.js +15 -129
  29. package/dist/scenario/pack.d.ts +1 -1
  30. package/dist/scenario/registry.d.ts +4 -5
  31. package/dist/scenario/registry.js +2 -10
  32. package/dist/server/start.js +1 -2
  33. package/dist/shell-exec.d.ts +62 -0
  34. package/dist/shell-exec.js +147 -0
  35. package/dist/shell-process.d.ts +3 -0
  36. package/dist/shell-process.js +137 -0
  37. package/dist/sidecar/boot.js +4 -0
  38. package/dist/sidecar/reverse-spawn.js +6 -15
  39. package/dist/sidecar/supervisor.d.ts +0 -2
  40. package/dist/sidecar/supervisor.js +7 -30
  41. package/dist/storage/index.js +1 -1
  42. package/dist/storage/write-lease-error.d.ts +2 -11
  43. package/dist/storage/write-lease-error.js +3 -8
  44. package/dist/tool-router.js +21 -6
  45. package/package.json +7 -14
  46. package/dist/app-update-electron.d.ts +0 -5
  47. package/dist/app-update-electron.js +0 -66
  48. package/dist/app-update.d.ts +0 -92
  49. package/dist/app-update.js +0 -213
  50. package/dist/host/ipc.d.ts +0 -11
  51. package/dist/host/ipc.js +0 -20
  52. package/dist/main.d.ts +0 -1
  53. package/dist/main.js +0 -967
  54. package/dist/preload-default.d.ts +0 -1
  55. package/dist/preload-default.js +0 -9
  56. package/dist/preload.cjs +0 -425
  57. package/dist/preload.d.ts +0 -20
  58. package/dist/preload.js +0 -442
  59. package/dist/single-instance.d.ts +0 -11
  60. package/dist/single-instance.js +0 -21
@@ -5,11 +5,11 @@
5
5
  * 持久化在 userData/agent-mcp-servers.json。已启用服务的工具会经
6
6
  * ToolRouter 以 `mcp__<serverKey>__<toolName>` 一等工具身份暴露给模型。
7
7
  *
8
- * 存储通过 {@link McpServerKvStore} 接口注入:main.ts 用 electron-store
9
- * 实现,单测用内存实现——本模块不 import electron,保持纯 Node 可测。
8
+ * 存储通过 {@link McpServerKvStore} 接口注入:宿主用 json-store 实现,
9
+ * 单测用内存实现。
10
10
  */
11
11
  import { randomUUID } from 'node:crypto';
12
- import { mcpExecutor } from './mcp-executor.js';
12
+ import { MAX_MCP_SERVER_TOOLS, mcpExecutor, } from './mcp-executor.js';
13
13
  const STORE_KEY = 'mcpServers';
14
14
  /** 工具名只允许 [a-z0-9-];中文名会被剥光,退化为 srv-<id前6位>。 */
15
15
  export function sanitizeServerKey(name, id) {
@@ -19,15 +19,90 @@ export function sanitizeServerKey(name, id) {
19
19
  .replace(/^-+|-+$/g, '');
20
20
  return cleaned || `srv-${id.slice(0, 6)}`;
21
21
  }
22
+ function normalizeStoredEntry(entry) {
23
+ if ('transport' in entry)
24
+ return entry;
25
+ const legacy = entry;
26
+ return { ...legacy, transport: 'stdio' };
27
+ }
28
+ function normalizeTransportInput(input) {
29
+ if (input.transport === 'streamable-http') {
30
+ const raw = input;
31
+ if (typeof raw.command === 'string' && raw.command.trim()) {
32
+ throw new Error('Streamable HTTP 配置不能包含 command');
33
+ }
34
+ const url = parseMcpHttpUrl(input.url);
35
+ const headers = { ...(input.headers ?? {}) };
36
+ if (Object.keys(headers).some((name) => name.toLowerCase() === 'authorization')) {
37
+ throw new Error('Authorization 不允许明文保存;请使用 bearerTokenEnvVar 或 headersFromEnv');
38
+ }
39
+ const bearerTokenEnvVar = input.bearerTokenEnvVar?.trim() || undefined;
40
+ return {
41
+ transport: 'streamable-http',
42
+ url,
43
+ headers,
44
+ headersFromEnv: { ...(input.headersFromEnv ?? {}) },
45
+ bearerTokenEnvVar,
46
+ reconnect: input.reconnect ? { ...input.reconnect } : undefined,
47
+ };
48
+ }
49
+ if ('url' in input && typeof input.url === 'string' && input.url.trim()) {
50
+ throw new Error('stdio 配置不能包含 url');
51
+ }
52
+ const command = input.command.trim();
53
+ if (!command)
54
+ throw new Error('启动命令不能为空');
55
+ return {
56
+ transport: 'stdio',
57
+ command,
58
+ args: (input.args ?? []).map(String),
59
+ env: { ...(input.env ?? {}) },
60
+ cwd: input.cwd?.trim() || undefined,
61
+ };
62
+ }
63
+ function parseMcpHttpUrl(raw) {
64
+ let url;
65
+ try {
66
+ url = new URL(raw.trim());
67
+ }
68
+ catch {
69
+ throw new Error('MCP HTTP URL 无效');
70
+ }
71
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
72
+ throw new Error('MCP HTTP URL 必须使用 http 或 https');
73
+ }
74
+ if (url.username || url.password) {
75
+ throw new Error('MCP HTTP URL 不能包含凭据');
76
+ }
77
+ return url.toString();
78
+ }
79
+ function stringRecord(value) {
80
+ if (!value || typeof value !== 'object' || Array.isArray(value))
81
+ return {};
82
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
83
+ key,
84
+ String(item),
85
+ ]));
86
+ }
87
+ function stringValue(value) {
88
+ return typeof value === 'string' ? value.trim() : '';
89
+ }
22
90
  export class McpServerRegistry {
23
91
  store;
92
+ executor;
24
93
  /** serverId → 最近一次 listTools 的结果(内存缓存,重启后由后台刷新重建)。 */
25
94
  toolCache = new Map();
26
- constructor(store) {
95
+ constructor(store, executor = mcpExecutor) {
27
96
  this.store = store;
97
+ this.executor = executor;
28
98
  }
29
99
  list() {
30
- return this.store.get(STORE_KEY) ?? [];
100
+ const stored = this.store.get(STORE_KEY) ?? [];
101
+ const migrated = stored.map(normalizeStoredEntry);
102
+ if (migrated.some((entry, index) => entry !== stored[index])) {
103
+ this.store.set(STORE_KEY, migrated);
104
+ }
105
+ return migrated;
31
106
  }
32
107
  get(idOrName) {
33
108
  const needle = idOrName.trim().toLowerCase();
@@ -43,24 +118,18 @@ export class McpServerRegistry {
43
118
  }
44
119
  create(input) {
45
120
  const name = input.name.trim();
46
- const command = input.command.trim();
47
121
  if (!name)
48
122
  throw new Error('服务名称不能为空');
49
- if (!command)
50
- throw new Error('启动命令不能为空');
51
123
  if (this.get(name))
52
124
  throw new Error(`已存在同名服务「${name}」`);
53
125
  const now = new Date().toISOString();
54
126
  const entry = {
55
127
  id: randomUUID(),
56
128
  name,
57
- command,
58
- args: (input.args ?? []).map((a) => String(a)),
59
- env: { ...(input.env ?? {}) },
60
- cwd: input.cwd?.trim() || undefined,
61
129
  enabled: input.enabled ?? true,
62
130
  createdAt: now,
63
131
  updatedAt: now,
132
+ ...normalizeTransportInput(input),
64
133
  };
65
134
  this.store.set(STORE_KEY, [...this.list(), entry]);
66
135
  return entry;
@@ -77,18 +146,39 @@ export class McpServerRegistry {
77
146
  const nameClash = servers.some((s) => s.id !== id && s.name.toLowerCase() === nextName.toLowerCase());
78
147
  if (nameClash)
79
148
  throw new Error(`已存在同名服务「${nextName}」`);
149
+ const transport = updates.transport ?? current.transport;
150
+ const transportInput = transport === 'stdio'
151
+ ? {
152
+ name: nextName,
153
+ transport: 'stdio',
154
+ command: updates.command ?? (current.transport === 'stdio' ? current.command : ''),
155
+ args: updates.args ?? (current.transport === 'stdio' ? current.args : []),
156
+ env: updates.env ?? (current.transport === 'stdio' ? current.env : {}),
157
+ cwd: updates.cwd ?? (current.transport === 'stdio' ? current.cwd : undefined),
158
+ }
159
+ : {
160
+ name: nextName,
161
+ transport: 'streamable-http',
162
+ url: updates.url ?? (current.transport === 'streamable-http' ? current.url : ''),
163
+ headers: updates.headers ??
164
+ (current.transport === 'streamable-http' ? current.headers : {}),
165
+ headersFromEnv: updates.headersFromEnv ??
166
+ (current.transport === 'streamable-http' ? current.headersFromEnv : {}),
167
+ bearerTokenEnvVar: updates.bearerTokenEnvVar ??
168
+ (current.transport === 'streamable-http'
169
+ ? current.bearerTokenEnvVar
170
+ : undefined),
171
+ reconnect: updates.reconnect ??
172
+ (current.transport === 'streamable-http' ? current.reconnect : undefined),
173
+ };
80
174
  const next = {
81
- ...current,
175
+ id: current.id,
82
176
  name: nextName,
83
- command: updates.command !== undefined ? updates.command.trim() : current.command,
84
- args: updates.args !== undefined ? updates.args.map((a) => String(a)) : current.args,
85
- env: updates.env !== undefined ? { ...updates.env } : current.env,
86
- cwd: updates.cwd !== undefined ? updates.cwd.trim() || undefined : current.cwd,
87
177
  enabled: updates.enabled !== undefined ? updates.enabled : current.enabled,
178
+ createdAt: current.createdAt,
88
179
  updatedAt: new Date().toISOString(),
180
+ ...normalizeTransportInput(transportInput),
89
181
  };
90
- if (!next.command)
91
- throw new Error('启动命令不能为空');
92
182
  servers[idx] = next;
93
183
  this.store.set(STORE_KEY, servers);
94
184
  // 配置变了,旧工具缓存不可信
@@ -131,10 +221,27 @@ export class McpServerRegistry {
131
221
  for (const [name, cfgRaw] of Object.entries(container)) {
132
222
  const cfg = (cfgRaw ?? {});
133
223
  const command = typeof cfg.command === 'string' ? cfg.command.trim() : '';
134
- if (!command || this.get(name)) {
224
+ const url = typeof cfg.url === 'string' ? cfg.url.trim() : '';
225
+ if ((!command && !url) || (command && url) || this.get(name)) {
135
226
  result.skipped.push(name);
136
227
  continue;
137
228
  }
229
+ if (url) {
230
+ const headers = stringRecord(cfg.headers);
231
+ const headersFromEnv = stringRecord(cfg.envHttpHeaders ?? cfg.env_http_headers ?? cfg.headersFromEnv);
232
+ const bearerTokenEnvVar = stringValue(cfg.bearerTokenEnvVar ?? cfg.bearer_token_env_var) || undefined;
233
+ const entry = this.create({
234
+ name: name.trim(),
235
+ transport: 'streamable-http',
236
+ url,
237
+ headers,
238
+ headersFromEnv,
239
+ bearerTokenEnvVar,
240
+ enabled: true,
241
+ });
242
+ result.added.push(entry);
243
+ continue;
244
+ }
138
245
  const env = {};
139
246
  if (cfg.env && typeof cfg.env === 'object' && !Array.isArray(cfg.env)) {
140
247
  for (const [k, v] of Object.entries(cfg.env)) {
@@ -160,12 +267,48 @@ export class McpServerRegistry {
160
267
  return clash ? `${base}-${entry.id.slice(0, 4)}` : base;
161
268
  }
162
269
  toExecutorConfig(entry) {
163
- return {
164
- command: entry.command,
165
- args: entry.args,
166
- env: entry.env,
167
- cwd: entry.cwd,
270
+ return entry.transport === 'stdio'
271
+ ? {
272
+ transport: 'stdio',
273
+ command: entry.command,
274
+ args: entry.args,
275
+ env: entry.env,
276
+ cwd: entry.cwd,
277
+ }
278
+ : {
279
+ transport: 'streamable-http',
280
+ url: entry.url,
281
+ headers: entry.headers,
282
+ headersFromEnv: entry.headersFromEnv,
283
+ bearerTokenEnvVar: entry.bearerTokenEnvVar,
284
+ reconnect: entry.reconnect,
285
+ };
286
+ }
287
+ toPublicEntry(entry) {
288
+ const common = {
289
+ id: entry.id,
290
+ name: entry.name,
291
+ transport: entry.transport,
292
+ enabled: entry.enabled,
293
+ createdAt: entry.createdAt,
294
+ updatedAt: entry.updatedAt,
168
295
  };
296
+ return entry.transport === 'stdio'
297
+ ? {
298
+ ...common,
299
+ command: entry.command,
300
+ args: [...entry.args],
301
+ cwd: entry.cwd,
302
+ envKeys: Object.keys(entry.env).sort(),
303
+ }
304
+ : {
305
+ ...common,
306
+ url: entry.url,
307
+ headerNames: Object.keys(entry.headers).sort(),
308
+ headersFromEnv: { ...entry.headersFromEnv },
309
+ bearerTokenEnvVar: entry.bearerTokenEnvVar,
310
+ reconnect: entry.reconnect,
311
+ };
169
312
  }
170
313
  getCachedTools(serverId) {
171
314
  return this.toolCache.get(serverId) ?? null;
@@ -211,8 +354,24 @@ export class McpServerRegistry {
211
354
  const entry = this.list().find((s) => s.id === serverId);
212
355
  if (!entry)
213
356
  throw new Error('服务不存在');
214
- const result = await mcpExecutor.listTools(this.toExecutorConfig(entry));
215
- const cached = result.success
357
+ const applyChanged = (tools) => {
358
+ if (tools.length > MAX_MCP_SERVER_TOOLS)
359
+ return;
360
+ this.toolCache.set(serverId, {
361
+ tools: tools.map((tool) => ({ ...tool })),
362
+ fetchedAt: new Date().toISOString(),
363
+ error: null,
364
+ });
365
+ };
366
+ const result = await this.executor.listTools(this.toExecutorConfig(entry), applyChanged, (error) => {
367
+ this.toolCache.set(serverId, {
368
+ tools: [],
369
+ fetchedAt: new Date().toISOString(),
370
+ error,
371
+ });
372
+ });
373
+ const overCap = (result.tools?.length ?? 0) > MAX_MCP_SERVER_TOOLS;
374
+ const cached = result.success && !overCap
216
375
  ? {
217
376
  tools: (result.tools ?? []).map((t) => ({
218
377
  name: t.name,
@@ -222,7 +381,13 @@ export class McpServerRegistry {
222
381
  fetchedAt: new Date().toISOString(),
223
382
  error: null,
224
383
  }
225
- : { tools: [], fetchedAt: new Date().toISOString(), error: result.error ?? '未知错误' };
384
+ : {
385
+ tools: [],
386
+ fetchedAt: new Date().toISOString(),
387
+ error: overCap
388
+ ? `服务工具数超过每服务 ${MAX_MCP_SERVER_TOOLS} 个的上限`
389
+ : result.error ?? '未知错误',
390
+ };
226
391
  this.toolCache.set(serverId, cached);
227
392
  return cached;
228
393
  }
@@ -15,13 +15,6 @@ export interface ProductLinks {
15
15
  /** 帮助菜单「访问官网」URL。 */
16
16
  website?: string;
17
17
  }
18
- export interface ProductUpdates {
19
- /**
20
- * electron-updater generic 通道根地址(目录,不含 latest*.yml)。
21
- * 打包配置里的 publishUrl 由产品组装根注入到这里。空则不检查更新。
22
- */
23
- feedUrl?: string;
24
- }
25
18
  export interface ProductConfig {
26
19
  /**
27
20
  * 云端遥测端点(insights flush 的 API base)。空 = 不上报(中性默认);
@@ -31,13 +24,8 @@ export interface ProductConfig {
31
24
  /** 帮助菜单链接;缺省的项不渲染。 */
32
25
  links?: ProductLinks;
33
26
  /**
34
- * 桌面自动更新。缺省不检查。只在已打包的应用里生效,除非
35
- * `DEEPPATH_APP_UPDATE=1`。`DEEPPATH_UPDATE_FEED_URL` 可覆盖 feedUrl。
36
- */
37
- updates?: ProductUpdates;
38
- /**
39
- * BS 模式的 userData 目录名(~/ 下)。CS 模式由 Electron 按
40
- * productName 分目录,不经此字段。
27
+ * 未设置 `DEEPPATH_USER_DATA_DIR` 时的 userData 目录名(~/ 下)。
28
+ * Tauri 宿主总是注入该环境变量,不经此字段。
41
29
  */
42
30
  dataDirName?: string;
43
31
  /**
@@ -58,6 +46,11 @@ export interface ProductConfig {
58
46
  * STEERABLE_APPROVAL=0 仍是调试逃生口。
59
47
  */
60
48
  approval?: 'host' | 'off';
49
+ /**
50
+ * 白名单外出网询问:host = 弹宿主审批;off = 自动加入当前代理会话。
51
+ * 缺省 host。自动放行仅存活到代理进程退出,不修改持久白名单。
52
+ */
53
+ egressApproval?: 'host' | 'off';
61
54
  /**
62
55
  * 对话模式。缺省 `['agent','plan']`。只留一种时渲染层不显示切换。
63
56
  */
@@ -100,11 +93,20 @@ export interface ProductConfig {
100
93
  */
101
94
  builtinMcp?: Array<{
102
95
  name: string;
96
+ transport?: 'stdio';
103
97
  command: string;
104
98
  args?: string[];
105
99
  env?: Record<string, string>;
106
100
  cwd?: string;
107
101
  enabled?: boolean;
102
+ } | {
103
+ name: string;
104
+ transport: 'streamable-http';
105
+ url: string;
106
+ headers?: Record<string, string>;
107
+ headersFromEnv?: Record<string, string>;
108
+ bearerTokenEnvVar?: string;
109
+ enabled?: boolean;
108
110
  }>;
109
111
  /**
110
112
  * P3.1 多智能体编排六件套 (agent_spawn / agent_wait / agent_send / agent_interrupt / agent_close / agent_list)。
@@ -116,6 +118,14 @@ export interface ProductConfig {
116
118
  maxDepth?: number;
117
119
  maxParallel?: number;
118
120
  };
121
+ /**
122
+ * 界面语言。缺省只有 `en`。译文在应用层语言包,不在本包。
123
+ * `locales` 是这个产品装进包里的语言;多于一种时设置页才出现切换。
124
+ */
125
+ i18n?: {
126
+ locales: string[];
127
+ defaultLocale: string;
128
+ };
119
129
  }
120
130
  /** shell 内置智能体:只有产品显式 `true` 才开。 */
121
131
  export declare function isShellBuiltinAgentEnabled(id: 'local-assistant' | 'all-round-assistant', config?: ProductConfig): boolean;
@@ -8,8 +8,8 @@
8
8
  * 源文件夹及其子目录同样可写。
9
9
  *
10
10
  * 持久化在 userData/agent-projects.json。存储通过 {@link ProjectKvStore}
11
- * 接口注入:main.ts 用 electron-store 实现,单测用内存实现——本模块不
12
- * import electron,保持纯 Node 可测(与 mcp-server-registry.ts 同一模式)。
11
+ * 接口注入:宿主用 json-store 实现,单测用内存实现(与
12
+ * mcp-server-registry.ts 同一模式)。
13
13
  */
14
14
  export interface ProjectRecord {
15
15
  id: string;
@@ -38,7 +38,7 @@ export interface CreateProjectInput {
38
38
  folderPath: string;
39
39
  sourceFolders?: string[];
40
40
  }
41
- /** 最小 KV 存储接口,避免本模块直接依赖 electron-store。 */
41
+ /** 最小 KV 存储接口,避免本模块直接依赖具体存储实现。 */
42
42
  export interface ProjectKvStore {
43
43
  get(key: 'projects'): ProjectRecord[] | undefined;
44
44
  set(key: 'projects', value: ProjectRecord[]): void;
@@ -8,8 +8,8 @@
8
8
  * 源文件夹及其子目录同样可写。
9
9
  *
10
10
  * 持久化在 userData/agent-projects.json。存储通过 {@link ProjectKvStore}
11
- * 接口注入:main.ts 用 electron-store 实现,单测用内存实现——本模块不
12
- * import electron,保持纯 Node 可测(与 mcp-server-registry.ts 同一模式)。
11
+ * 接口注入:宿主用 json-store 实现,单测用内存实现(与
12
+ * mcp-server-registry.ts 同一模式)。
13
13
  */
14
14
  import { randomUUID } from 'node:crypto';
15
15
  const STORE_KEY = 'projects';
package/dist/runtime.d.ts CHANGED
@@ -1,5 +1,3 @@
1
- /** Electron 主进程里 `process.versions.electron` 有值;普通 Node(BS server、vitest)没有。 */
2
- export declare function isElectronRuntime(): boolean;
3
1
  /** 应用数据目录(SQLite、JSON store、用户技能目录的根)。 */
4
2
  export declare function getUserDataDir(): string;
5
3
  /**
@@ -15,59 +13,15 @@ export declare function getDocumentsDir(): string;
15
13
  export declare function setAppRootDir(dir: string): void;
16
14
  export declare function getAppRootDir(): string;
17
15
  /**
18
- * 注入 preload bundle 绝对路径(产品组装根/入口调用)。重复注入不同值
19
- * 抛错(组装期笔误,fail fast——与 setAppRootDir 同语义)。
20
- */
21
- export declare function setPreloadPath(file: string): void;
22
- /**
23
- * 解析窗口应加载的 preload bundle:产品注入优先,其次 shell 自带默认
24
- * (shellDir 一般为 shell main.js 所在目录)。
25
- */
26
- export declare function getPreloadPath(shellDir: string): string;
27
- /**
28
- * 产品 web 产物目录(2.3):产品入口(products/<id>/{main,server}.ts)
29
- * 用本函数把 DEEPPATH_WEB_DIST 注入环境,shell 的 main/server 据此定位
30
- * web dist。双平面探测:源码平面入口在 products/<id>/(web dist 在同级
31
- * web/dist);编译平面入口在 products/<id>/dist/products/<id>/(web dist
32
- * 在仓库根的 products/<id>/web/dist)。两处都没有返回 null(打包后的
33
- * CS 走 app.getAppPath()/web-dist 约定,不经本函数)。
16
+ * 产品 web 产物目录(2.3):产品入口(products/<id>/server.ts)用本函数
17
+ * 把 DEEPPATH_WEB_DIST 注入环境,shell server 据此定位 web dist。双平面
18
+ * 探测:源码平面入口在 products/<id>/(web dist 在同级 web/dist);编译
19
+ * 平面入口在 products/<id>/dist/products/<id>/(web dist 在仓库根的
20
+ * products/<id>/web/dist)。两处都没有返回 null(打包后的 Tauri 宿主直接
21
+ * 注入 DEEPPATH_WEB_DIST,不经本函数)。
34
22
  */
35
23
  export declare function resolveProductWebDist(entryUrl: string): string | null;
36
- /** 用系统默认应用打开本地路径(Electron `shell.openPath` 的跨宿主等价物)。 */
24
+ /** 用系统默认应用打开本地路径。返回空串表示成功,否则是错误消息。 */
37
25
  export declare function shellOpenPath(target: string): Promise<string>;
38
- /** 用系统浏览器打开 URL(Electron `shell.openExternal` 的跨宿主等价物)。 */
26
+ /** 用系统浏览器打开 URL。 */
39
27
  export declare function shellOpenExternal(url: string): Promise<void>;
40
- /**
41
- * 注册应用退出钩子(sidecar 的兜底清理)。BS 下没有 app 生命周期——
42
- * 宿主退出由 server 的 SIGINT/SIGTERM 钩子统一负责,这里 no-op。
43
- */
44
- export declare function onAppWillQuit(listener: () => void): void;
45
- export declare function offAppWillQuit(listener: () => void): void;
46
- export interface NativeImageInstance {
47
- isEmpty(): boolean;
48
- getSize(): {
49
- width: number;
50
- height: number;
51
- };
52
- crop(rect: {
53
- x: number;
54
- y: number;
55
- width: number;
56
- height: number;
57
- }): NativeImageInstance;
58
- resize(o: {
59
- width?: number;
60
- height?: number;
61
- quality?: string;
62
- }): NativeImageInstance;
63
- toPNG(): Buffer;
64
- toJPEG(quality: number): Buffer;
65
- }
66
- export interface NativeImageLike {
67
- createFromPath(p: string): NativeImageInstance;
68
- }
69
- /**
70
- * Electron `nativeImage`(图片解码/缩放)。BS / 单测等无 Electron 环境返回
71
- * null——调用方按"不支持图片解码"降级(image-attachment 已有该分支)。
72
- */
73
- export declare function getNativeImage(): NativeImageLike | null;