@steerable/agent-shell 0.6.53 → 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 (47) 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/i18n.d.ts +23 -0
  5. package/dist/i18n.js +87 -0
  6. package/dist/image-attachment.d.ts +31 -4
  7. package/dist/image-attachment.js +13 -18
  8. package/dist/json-store.js +3 -6
  9. package/dist/local-backend/portable-bundle.js +8 -7
  10. package/dist/local-backend/router.js +2 -2
  11. package/dist/local-backend/skills/10-goal/SKILL.md +1 -1
  12. package/dist/local-backend/skills/11-loop/SKILL.md +1 -1
  13. package/dist/local-backend/skills/12-create-skill/SKILL.md +2 -2
  14. package/dist/local-script-registry.js +2 -2
  15. package/dist/mcp-server-registry.d.ts +3 -3
  16. package/dist/mcp-server-registry.js +2 -2
  17. package/dist/product-config.d.ts +10 -14
  18. package/dist/project-registry.d.ts +3 -3
  19. package/dist/project-registry.js +2 -2
  20. package/dist/runtime.d.ts +8 -54
  21. package/dist/runtime.js +15 -129
  22. package/dist/scenario/pack.d.ts +1 -1
  23. package/dist/scenario/registry.d.ts +4 -5
  24. package/dist/scenario/registry.js +2 -10
  25. package/dist/server/start.js +1 -2
  26. package/dist/sidecar/reverse-spawn.js +6 -15
  27. package/dist/sidecar/supervisor.d.ts +0 -2
  28. package/dist/sidecar/supervisor.js +7 -30
  29. package/dist/storage/index.js +1 -1
  30. package/dist/storage/write-lease-error.d.ts +2 -11
  31. package/dist/storage/write-lease-error.js +3 -8
  32. package/package.json +6 -13
  33. package/dist/app-update-electron.d.ts +0 -5
  34. package/dist/app-update-electron.js +0 -66
  35. package/dist/app-update.d.ts +0 -92
  36. package/dist/app-update.js +0 -213
  37. package/dist/host/ipc.d.ts +0 -11
  38. package/dist/host/ipc.js +0 -20
  39. package/dist/main.d.ts +0 -1
  40. package/dist/main.js +0 -967
  41. package/dist/preload-default.d.ts +0 -1
  42. package/dist/preload-default.js +0 -9
  43. package/dist/preload.cjs +0 -425
  44. package/dist/preload.d.ts +0 -20
  45. package/dist/preload.js +0 -442
  46. package/dist/single-instance.d.ts +0 -11
  47. package/dist/single-instance.js +0 -21
package/dist/brand.js CHANGED
@@ -34,7 +34,7 @@ const SHELL_BRAND = {
34
34
  displayName: 'Steerable Shell',
35
35
  title: 'Steerable Shell',
36
36
  agentName: 'Agent',
37
- tagline: '一款本地桌面 AI 伙伴',
37
+ tagline: 'A local desktop AI partner',
38
38
  defaultAgentId: LOCAL_ASSISTANT_AGENT_ID,
39
39
  };
40
40
  let productBrand = null;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 包装配注册表(阶段 2.3)——纯构建期组合的宿主侧机制。
3
3
  *
4
- * 宿主(host/runtime.ts、main.ts、server/index.ts)不再静态 import 任何
4
+ * 宿主(host/runtime.ts、server/index.ts)不再静态 import 任何
5
5
  * 包代码:产品组装根(products/<id>/active.ts)把包的装配函数注册进本
6
6
  * 注册表,宿主在装配期逐个调用。产品的 tsc 编译单元只 include 自己的包,
7
7
  * 他包代码物理上不进产品 dist。
@@ -10,11 +10,10 @@
10
10
  * - import 期(active.ts 顶层):迁移/种子/品牌等无宿主依赖的注册;
11
11
  * - 装配期(createHostRuntime 内):服务/工具/路由等需要宿主能力的部分,
12
12
  * 经 {@link PackAssemblyDeps} 注入。
13
- * 入口差异:CS(main.ts)额外消费 handle.ipc,BS(server/index.ts)额外
14
- * 消费 handle.httpRoutes——包的入口面贡献由宿主循环驱动,宿主代码里
15
- * 不出现任何包名。
13
+ * server/index.ts 消费 handle.httpRoutes——包的入口面贡献由宿主循环驱动,
14
+ * 宿主代码里不出现任何包名。
16
15
  */
17
- import type { HttpRouteContribution, IpcContribution, ToolContribution } from '../scenario/pack.js';
16
+ import type { HttpRouteContribution, ToolContribution } from '../scenario/pack.js';
18
17
  import type { PackDbAccess } from '../storage/driver.js';
19
18
  /** 宿主注入包的装配能力面。包不 import 宿主装配模块,全部经回调注入。 */
20
19
  export interface PackAssemblyDeps {
@@ -31,7 +30,7 @@ export interface PackAssemblyDeps {
31
30
  name: string;
32
31
  folderPath: string;
33
32
  } | null>;
34
- /** 面向全部用户面的事件广播(CS=所有窗口 IPC,BS=SSE 总线)。 */
33
+ /** 面向全部用户面的事件广播(SSE 总线)。 */
35
34
  readonly broadcast: (channel: string, payload: unknown) => void;
36
35
  /** 工具贡献注册口(toolRouter.registerToolContributions 的收窄面)。 */
37
36
  readonly registerTools: (tools: readonly ToolContribution[]) => void;
@@ -61,20 +60,11 @@ export interface PackAssemblyDeps {
61
60
  /** 装配期日志行(进宿主日志)。 */
62
61
  readonly onLog: (line: string) => void;
63
62
  }
64
- /** CS 宿主的窗口能力:打开一个加载应用内路由的独立窗口。 */
65
- export interface PackHostWindowCapabilities {
66
- /** route 如 '/<pack>-debug-log';title 为窗口副标题(宿主拼品牌名)。 */
67
- openRouteWindow(route: string, options: {
68
- title: string;
69
- }): Promise<void>;
70
- }
71
63
  /** 装配产物:宿主按入口面消费。全部槽位可选。 */
72
64
  export interface PackAssemblyHandle {
73
65
  /** 宿主关停钩子(进程退出时按装配逆序调用)。 */
74
66
  readonly dispose?: () => void | Promise<void>;
75
- /** CS:包 IPC 贡献(需要宿主窗口能力时经 caps 注入)。 */
76
- readonly ipc?: (caps: PackHostWindowCapabilities) => readonly IpcContribution[];
77
- /** BS:/host/<packId>/* 路由贡献。 */
67
+ /** /host/<packId>/* 路由贡献。 */
78
68
  readonly httpRoutes?: () => readonly HttpRouteContribution[];
79
69
  }
80
70
  export type PackAssembly = (deps: PackAssemblyDeps) => PackAssemblyHandle | void;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 包装配注册表(阶段 2.3)——纯构建期组合的宿主侧机制。
3
3
  *
4
- * 宿主(host/runtime.ts、main.ts、server/index.ts)不再静态 import 任何
4
+ * 宿主(host/runtime.ts、server/index.ts)不再静态 import 任何
5
5
  * 包代码:产品组装根(products/<id>/active.ts)把包的装配函数注册进本
6
6
  * 注册表,宿主在装配期逐个调用。产品的 tsc 编译单元只 include 自己的包,
7
7
  * 他包代码物理上不进产品 dist。
@@ -10,9 +10,8 @@
10
10
  * - import 期(active.ts 顶层):迁移/种子/品牌等无宿主依赖的注册;
11
11
  * - 装配期(createHostRuntime 内):服务/工具/路由等需要宿主能力的部分,
12
12
  * 经 {@link PackAssemblyDeps} 注入。
13
- * 入口差异:CS(main.ts)额外消费 handle.ipc,BS(server/index.ts)额外
14
- * 消费 handle.httpRoutes——包的入口面贡献由宿主循环驱动,宿主代码里
15
- * 不出现任何包名。
13
+ * server/index.ts 消费 handle.httpRoutes——包的入口面贡献由宿主循环驱动,
14
+ * 宿主代码里不出现任何包名。
16
15
  */
17
16
  const assemblies = new Map();
18
17
  /** 注册包的装配函数(产品组装根在 import 期调用;重复注册同包抛错)。 */
package/dist/i18n.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * 主进程界面文案。与渲染层同一规则:英文是源句,应用层 registerLocale 注入译文。
3
+ * 本模块不依赖 Electron,菜单在 app ready 时读取。
4
+ */
5
+ export declare const SOURCE_LOCALE = "en";
6
+ export declare function normalizeLocale(raw: string | null | undefined): string | null;
7
+ export declare function resolveLocale(options: {
8
+ supported: readonly string[];
9
+ defaultLocale: string;
10
+ saved: string | null;
11
+ system: string | null;
12
+ }): string;
13
+ export declare function registerLocale(locale: string, messages: Record<string, string>): void;
14
+ export declare function configureI18n(options: {
15
+ locales: readonly string[];
16
+ defaultLocale: string;
17
+ systemLocale?: string | null;
18
+ savedLocale?: string | null;
19
+ }): void;
20
+ export declare function getLocale(): string;
21
+ export declare function setLocale(locale: string): boolean;
22
+ export declare function t(message: string, params?: Record<string, string | number>): string;
23
+ export declare function resetI18nForTests(): void;
package/dist/i18n.js ADDED
@@ -0,0 +1,87 @@
1
+ /**
2
+ * 主进程界面文案。与渲染层同一规则:英文是源句,应用层 registerLocale 注入译文。
3
+ * 本模块不依赖 Electron,菜单在 app ready 时读取。
4
+ */
5
+ export const SOURCE_LOCALE = 'en';
6
+ export function normalizeLocale(raw) {
7
+ if (!raw)
8
+ return null;
9
+ const lower = raw.trim().toLowerCase().replace(/_/g, '-');
10
+ if (!lower)
11
+ return null;
12
+ if (lower.startsWith('zh'))
13
+ return 'zh';
14
+ if (lower.startsWith('en'))
15
+ return 'en';
16
+ const primary = lower.split('-')[0];
17
+ return primary || null;
18
+ }
19
+ export function resolveLocale(options) {
20
+ const supported = options.supported.filter((locale) => locale.length > 0);
21
+ const list = supported.length > 0 ? supported : [SOURCE_LOCALE];
22
+ const pick = (value) => {
23
+ if (!value)
24
+ return null;
25
+ if (list.includes(value))
26
+ return value;
27
+ const normalized = normalizeLocale(value);
28
+ if (normalized && list.includes(normalized))
29
+ return normalized;
30
+ return null;
31
+ };
32
+ return (pick(options.saved) ??
33
+ pick(options.system) ??
34
+ pick(options.defaultLocale) ??
35
+ list[0] ??
36
+ SOURCE_LOCALE);
37
+ }
38
+ function formatMessage(template, params) {
39
+ if (!params)
40
+ return template;
41
+ return template.replace(/\{(\w+)\}/g, (whole, name) => {
42
+ const value = params[name];
43
+ return value === undefined ? whole : String(value);
44
+ });
45
+ }
46
+ const catalogs = {};
47
+ let locales = [SOURCE_LOCALE];
48
+ let defaultLocale = SOURCE_LOCALE;
49
+ let current = SOURCE_LOCALE;
50
+ export function registerLocale(locale, messages) {
51
+ const normalized = locale.trim();
52
+ if (!normalized || normalized === SOURCE_LOCALE)
53
+ return;
54
+ catalogs[normalized] = { ...catalogs[normalized], ...messages };
55
+ }
56
+ export function configureI18n(options) {
57
+ const next = [...new Set(options.locales.filter((locale) => locale.length > 0))];
58
+ locales = next.length > 0 ? next : [SOURCE_LOCALE];
59
+ defaultLocale = options.defaultLocale || SOURCE_LOCALE;
60
+ current = resolveLocale({
61
+ supported: locales,
62
+ defaultLocale,
63
+ saved: options.savedLocale ?? null,
64
+ system: options.systemLocale ?? null,
65
+ });
66
+ }
67
+ export function getLocale() {
68
+ return current;
69
+ }
70
+ export function setLocale(locale) {
71
+ if (!locales.includes(locale))
72
+ return false;
73
+ current = locale;
74
+ return true;
75
+ }
76
+ export function t(message, params) {
77
+ const table = current === SOURCE_LOCALE ? undefined : catalogs[current];
78
+ const template = table?.[message] ?? message;
79
+ return formatMessage(template, params);
80
+ }
81
+ export function resetI18nForTests() {
82
+ for (const key of Object.keys(catalogs))
83
+ delete catalogs[key];
84
+ locales = [SOURCE_LOCALE];
85
+ defaultLocale = SOURCE_LOCALE;
86
+ current = SOURCE_LOCALE;
87
+ }
@@ -1,4 +1,3 @@
1
- import type { NativeImageLike } from './runtime.js';
2
1
  import type { LlmImage } from './llm/types.js';
3
2
  /** Refuse to read source files larger than this (10 MB). */
4
3
  export declare const IMAGE_MAX_SOURCE_BYTES: number;
@@ -6,6 +5,34 @@ export declare const IMAGE_MAX_SOURCE_BYTES: number;
6
5
  export declare const IMAGE_MAX_DIMENSION = 1568;
7
6
  /** Refuse the encoded payload if it still exceeds this after re-encode (5 MB). */
8
7
  export declare const IMAGE_MAX_ENCODED_BYTES: number;
8
+ /** A decoded raster that can be cropped, resized, and re-encoded synchronously. */
9
+ export interface DecodedImage {
10
+ isEmpty(): boolean;
11
+ getSize(): {
12
+ width: number;
13
+ height: number;
14
+ };
15
+ crop(rect: {
16
+ x: number;
17
+ y: number;
18
+ width: number;
19
+ height: number;
20
+ }): DecodedImage;
21
+ resize(o: {
22
+ width?: number;
23
+ height?: number;
24
+ quality?: string;
25
+ }): DecodedImage;
26
+ toPNG(): Buffer;
27
+ toJPEG(quality: number): Buffer;
28
+ }
29
+ /**
30
+ * Optional image decoder. Without one, images pass through as the original
31
+ * bytes and crop / resize / transcode requests are refused.
32
+ */
33
+ export interface ImageDecoder {
34
+ createFromPath(p: string): DecodedImage;
35
+ }
9
36
  export interface ImageAttachmentInput {
10
37
  path: string;
11
38
  name?: string;
@@ -65,9 +92,9 @@ export declare function computeTargetSize(width: number, height: number, maxDime
65
92
  * tool result. `_image` is consumed by the CoreLoop as an image content part;
66
93
  * it is not a path or model-visible base64 string.
67
94
  */
68
- export declare function processViewImage(input: ViewImageInput, nativeImage?: NativeImageLike | null): ViewImageResult;
95
+ export declare function processViewImage(input: ViewImageInput, decoder?: ImageDecoder | null): ViewImageResult;
69
96
  /**
70
- * Process a batch of image attachments. Synchronous: `nativeImage` decode /
97
+ * Process a batch of image attachments. Synchronous: decoder decode /
71
98
  * resize / encode are all synchronous, and the byte check uses `statSync`.
72
99
  */
73
- export declare function processImageAttachments(files: ImageAttachmentInput[]): ProcessedImageAttachments;
100
+ export declare function processImageAttachments(files: ImageAttachmentInput[], decoder?: ImageDecoder | null): ProcessedImageAttachments;
@@ -3,9 +3,9 @@
3
3
  * actually see, replacing the old behavior of dropping only the file *path*
4
4
  * into the prompt text.
5
5
  *
6
- * Runs in the Electron main process and uses `nativeImage` to decode /
7
- * downscale — no native dependency, works in the packaged app. Two caps are
8
- * enforced before the image ever reaches the model:
6
+ * Decoding / downscaling runs only when an {@link ImageDecoder} is supplied;
7
+ * otherwise the original bytes pass through under the encoded cap. These caps
8
+ * are enforced before the image ever reaches the model:
9
9
  *
10
10
  * - source bytes (`IMAGE_MAX_SOURCE_BYTES`) — refuse to read huge files;
11
11
  * - long-edge px (`IMAGE_MAX_DIMENSION`) — downscale so the provider's
@@ -17,7 +17,6 @@
17
17
  * line so the caller can inject it into the model-visible context — the model
18
18
  * should know an image was attached even when it was too large to send.
19
19
  */
20
- import { getNativeImage } from './runtime.js';
21
20
  import { readFileSync, statSync } from 'fs';
22
21
  import { basename, extname } from 'path';
23
22
  /** Refuse to read source files larger than this (10 MB). */
@@ -74,7 +73,7 @@ export function computeTargetSize(width, height, maxDimension = IMAGE_MAX_DIMENS
74
73
  * tool result. `_image` is consumed by the CoreLoop as an image content part;
75
74
  * it is not a path or model-visible base64 string.
76
75
  */
77
- export function processViewImage(input, nativeImage = getNativeImage()) {
76
+ export function processViewImage(input, decoder = null) {
78
77
  if (!isImagePath(input.path)) {
79
78
  return {
80
79
  success: false,
@@ -104,10 +103,10 @@ export function processViewImage(input, nativeImage = getNativeImage()) {
104
103
  needsFollowup: true,
105
104
  };
106
105
  }
107
- if (!nativeImage) {
106
+ if (!decoder) {
108
107
  return viewImageWithoutDecoder(input, sourceBytes, maxEdge);
109
108
  }
110
- let rendered = nativeImage.createFromPath(input.path);
109
+ let rendered = decoder.createFromPath(input.path);
111
110
  if (rendered.isEmpty()) {
112
111
  return { success: false, error: '不是可识别的图片', needsFollowup: true };
113
112
  }
@@ -262,16 +261,15 @@ function formatMb(bytes) {
262
261
  return (bytes / (1024 * 1024)).toFixed(1).replace(/\.0$/, '');
263
262
  }
264
263
  /**
265
- * Process a batch of image attachments. Synchronous: `nativeImage` decode /
264
+ * Process a batch of image attachments. Synchronous: decoder decode /
266
265
  * resize / encode are all synchronous, and the byte check uses `statSync`.
267
266
  */
268
- export function processImageAttachments(files) {
267
+ export function processImageAttachments(files, decoder = null) {
269
268
  const images = [];
270
269
  const notes = [];
271
270
  for (const file of files) {
272
271
  const label = file.name || basename(file.path);
273
- // Existence + source-size guards run before the decoder so they're
274
- // exercisable in a non-Electron (test) host too.
272
+ // Existence + source-size guards run before the decoder.
275
273
  let sourceBytes = 0;
276
274
  try {
277
275
  sourceBytes = statSync(file.path).size;
@@ -284,12 +282,9 @@ export function processImageAttachments(files) {
284
282
  notes.push(`- ${label}:源文件 ${formatMb(sourceBytes)}MB 超过 ${formatMb(IMAGE_MAX_SOURCE_BYTES)}MB 上限,未附加`);
285
283
  continue;
286
284
  }
287
- const nativeImage = getNativeImage();
288
- if (!nativeImage) {
289
- // Tauri and the browser server run plain Node, which has no Electron
290
- // nativeImage. The bytes are already a PNG/JPEG the provider can read;
291
- // pass them through when they fit the encoded cap. Resize stays on the
292
- // Electron path.
285
+ if (!decoder) {
286
+ // The bytes are already an image the provider can read; pass them
287
+ // through when they fit the encoded cap.
293
288
  if (sourceBytes > IMAGE_MAX_ENCODED_BYTES) {
294
289
  notes.push(`- ${label}:源文件 ${formatMb(sourceBytes)}MB 超过 ${formatMb(IMAGE_MAX_ENCODED_BYTES)}MB,当前环境不能缩放,未附加`);
295
290
  continue;
@@ -307,7 +302,7 @@ export function processImageAttachments(files) {
307
302
  notes.push(`- ${label}(原图)`);
308
303
  continue;
309
304
  }
310
- const image = nativeImage.createFromPath(file.path);
305
+ const image = decoder.createFromPath(file.path);
311
306
  if (image.isEmpty()) {
312
307
  notes.push(`- ${label}:不是可识别的图片,未附加`);
313
308
  continue;
@@ -1,10 +1,7 @@
1
1
  /**
2
- * JSON 键值存储工厂(agent-mcp-servers.json / agent-projects.json / window-state)。
3
- *
4
- * 历史上用 electron-store;它只在构造时读 `app.getPath('userData')` 当 cwd,
5
- * 而 conf(electron-store 的底层,已是本项目直接依赖)API 完全覆盖用到的
6
- * get/set + name + defaults 子集。统一走 conf + getUserDataDir(),Electron
7
- * 与 BS server 得到同一个文件路径,两种模式共享同一份数据。
2
+ * JSON 键值存储工厂(agent-mcp-servers.json / agent-projects.json /
3
+ * agent-local-scripts.json)。文件在 getUserDataDir() 下,由 conf 读写;
4
+ * 桌面与 BS server 得到同一个文件路径。
8
5
  *
9
6
  * conf@10 的 d.ts 是 ESM 风格但包本身没有 `type: module`,NodeNext 下类型
10
7
  * 解析会塌成 namespace——所以这里声明用到的最小接口并做一次强转,不依赖
@@ -5,6 +5,7 @@
5
5
  * 钥匙(模型 Key、搜索 Key、MCP 环境变量)默认不进包;`includeSecrets`
6
6
  * 为 true 时原样写入,导入方要自己确认。
7
7
  */
8
+ import { t } from '../i18n.js';
8
9
  export const PORTABLE_SCHEMA_VERSION = 1;
9
10
  export const PORTABLE_MESSAGE_LIMIT = 1000;
10
11
  export const PORTABLE_CONFIG_KIND = 'steerable-config';
@@ -64,19 +65,19 @@ export function readPortableProject(raw) {
64
65
  }
65
66
  function thinkingLabel(mode) {
66
67
  if (mode === 'hidden')
67
- return '隐藏';
68
+ return t('Hide');
68
69
  if (mode === 'full')
69
- return '完整显示';
70
+ return t('Show all');
70
71
  if (mode === 'peek')
71
- return '显示5行';
72
- return '未识别的显示档位';
72
+ return t('Show 5 lines');
73
+ return t('Unrecognized display mode');
73
74
  }
74
75
  function execLabel(policy) {
75
76
  if (policy === 'full')
76
- return '完整权限';
77
+ return t('Full access');
77
78
  if (policy === 'workspace')
78
- return '工作区';
79
- return '未识别的权限';
79
+ return t('Workspace');
80
+ return t('Unrecognized permission');
80
81
  }
81
82
  export function llmSectionFromSettings(settings, includeSecrets) {
82
83
  const apiKey = asString(settings.apiKey);
@@ -163,7 +163,7 @@ function buildLocalAgent() {
163
163
  platform: process.platform,
164
164
  hostname: 'localhost',
165
165
  shell: process.platform === 'win32' ? 'powershell' : 'zsh',
166
- osVersion: process.versions.electron || null,
166
+ osVersion: null,
167
167
  osArch: process.arch,
168
168
  isOnline: true,
169
169
  lastHeartbeat: now,
@@ -461,7 +461,7 @@ export class LocalBackendRouter {
461
461
  return { status: 200, data: { deletedChatIds } };
462
462
  }
463
463
  // ───── 项目模式:projects CRUD ─────
464
- // 项目记录存 electron-store(agent-projects.json),chat.project_id 存
464
+ // 项目记录存 json-store(agent-projects.json),chat.project_id 存
465
465
  // SQLite。删除项目不删会话——会话降级为无项目对话。
466
466
  if ((pathname === '/api/v2/projects' || pathname.startsWith('/api/v2/projects/')) &&
467
467
  !getResolvedHostTools().projects.capability) {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: goal
3
- displayName: 目标跟踪
3
+ displayName: Goal tracking
4
4
  description: 把一句话需求变成有验收标准的目标,用 todo_write 建清单、执行中更新状态、收尾逐条给出证据。适合多步骤、跨回合、需要明确「到底做完没有」的任务。
5
5
  priority: 500
6
6
  tags: [workflow, planning, goal]
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: loop
3
- displayName: 循环执行
3
+ displayName: Repeat on a schedule
4
4
  description: 按固定间隔或自选节奏重复执行同一件事(如「每 5 分钟查一次构建状态」)。支持本轮内有限次循环和 task_run 后台长循环,必须先定好间隔、终止条件与最大次数。
5
5
  priority: 500
6
6
  tags: [workflow, automation, loop]
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: create-skill
3
- displayName: 创建技能
3
+ displayName: Create a skill
4
4
  description: 按本产品的 SKILL.md 规范创建一个新的本地技能,含目录结构、frontmatter 字段、写作要求。写到项目 skills/ 目录后自动出现在 Skill 设置与 / 菜单。用户说「做个技能 / 写个 skill / 把这套流程固化下来」时使用。
5
5
  priority: 500
6
6
  tags: [skill, authoring, meta]
@@ -40,7 +40,7 @@ disable-model-invocation: true
40
40
  ```markdown
41
41
  ---
42
42
  name: my-skill
43
- displayName: 我的技能
43
+ displayName: My skill
44
44
  description: 做什么 + 什么时候用,一句话说清。
45
45
  priority: 500
46
46
  tags: [workflow]
@@ -1,9 +1,9 @@
1
- import Store from 'electron-store';
2
1
  import { randomUUID } from 'crypto';
2
+ import { createJsonStore } from './json-store.js';
3
3
  export class LocalScriptRegistry {
4
4
  store;
5
5
  constructor() {
6
- this.store = new Store({
6
+ this.store = createJsonStore({
7
7
  name: 'agent-local-scripts',
8
8
  defaults: {
9
9
  localScripts: [],
@@ -5,8 +5,8 @@
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 { type McpExecutorLike, type McpServerConfig } from './mcp-executor.js';
12
12
  interface McpServerCommon {
@@ -74,7 +74,7 @@ export interface UpdateMcpServerInput {
74
74
  reconnect?: StreamableHttpMcpServerEntry['reconnect'];
75
75
  enabled?: boolean;
76
76
  }
77
- /** 最小 KV 存储接口,避免本模块直接依赖 electron-store。 */
77
+ /** 最小 KV 存储接口,避免本模块直接依赖具体存储实现。 */
78
78
  export interface McpServerKvStore {
79
79
  get(key: 'mcpServers'): McpServerEntry[] | undefined;
80
80
  set(key: 'mcpServers', value: McpServerEntry[]): void;
@@ -5,8 +5,8 @@
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
12
  import { MAX_MCP_SERVER_TOOLS, mcpExecutor, } from './mcp-executor.js';
@@ -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
  /**
@@ -130,6 +118,14 @@ export interface ProductConfig {
130
118
  maxDepth?: number;
131
119
  maxParallel?: number;
132
120
  };
121
+ /**
122
+ * 界面语言。缺省只有 `en`。译文在应用层语言包,不在本包。
123
+ * `locales` 是这个产品装进包里的语言;多于一种时设置页才出现切换。
124
+ */
125
+ i18n?: {
126
+ locales: string[];
127
+ defaultLocale: string;
128
+ };
133
129
  }
134
130
  /** shell 内置智能体:只有产品显式 `true` 才开。 */
135
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;