@shareai-lab/kode-sdk 2.7.2 → 2.7.4

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/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
  - **Long-Running & Resumable** - Seven-stage checkpoints with Safe-Fork-Point for crash recovery
11
11
  - **Multi-Agent Collaboration** - AgentPool, Room messaging, and task delegation
12
12
  - **Enterprise Persistence** - SQLite/PostgreSQL support with unified WAL
13
- - **Cloud Sandbox** - [E2B](https://e2b.dev) integration for isolated remote code execution
13
+ - **Cloud Sandbox** - [E2B](https://e2b.dev) and OpenSandbox integration for isolated remote code execution
14
14
  - **Extensible Ecosystem** - MCP tools, custom Providers, Skills system
15
15
 
16
16
  ## Quick Start
@@ -79,6 +79,15 @@ npm run example:getting-started # Minimal chat
79
79
  npm run example:agent-inbox # Event-driven inbox
80
80
  npm run example:approval # Tool approval workflow
81
81
  npm run example:room # Multi-agent collaboration
82
+ npm run example:opensandbox # OpenSandbox basic usage
83
+ ```
84
+
85
+ OpenSandbox quick config:
86
+
87
+ ```bash
88
+ export OPEN_SANDBOX_API_KEY=... # optional (required only when auth is enabled)
89
+ export OPEN_SANDBOX_ENDPOINT=http://127.0.0.1:8080 # optional
90
+ export OPEN_SANDBOX_IMAGE=ubuntu # optional
82
91
  ```
83
92
 
84
93
  ## Architecture for Scale
@@ -142,9 +151,14 @@ See [docs/en/guides/architecture.md](./docs/en/guides/architecture.md) for detai
142
151
  | **Guides** | |
143
152
  | [Events](./docs/en/guides/events.md) | Three-channel event system |
144
153
  | [Tools](./docs/en/guides/tools.md) | Built-in tools & custom tools |
154
+ | [E2B Sandbox](./docs/en/guides/e2b-sandbox.md) | E2B cloud sandbox integration |
155
+ | [OpenSandbox](./docs/en/guides/opensandbox-sandbox.md) | OpenSandbox self-hosted sandbox integration |
156
+ | [Skills](./docs/en/guides/skills.md) | Skills system for reusable prompts |
145
157
  | [Providers](./docs/en/guides/providers.md) | Model provider configuration |
146
158
  | [Database](./docs/en/guides/database.md) | SQLite/PostgreSQL persistence |
147
159
  | [Resume & Fork](./docs/en/guides/resume-fork.md) | Crash recovery & branching |
160
+ | **Project** | |
161
+ | [Contribution Guide](./docs/en/contribution.md) | How to contribute |
148
162
  | **Reference** | |
149
163
  | [API Reference](./docs/en/reference/api.md) | Complete API documentation |
150
164
  | [Examples](./docs/en/examples/playbooks.md) | All examples explained |
package/README.zh-CN.md CHANGED
@@ -10,7 +10,7 @@
10
10
  - **长时运行与恢复** - 七段断点机制,支持 Safe-Fork-Point 崩溃恢复
11
11
  - **多 Agent 协作** - AgentPool、Room 消息、任务委派
12
12
  - **企业级持久化** - 支持 SQLite/PostgreSQL,统一 WAL 日志
13
- - **云端沙箱** - 集成 [E2B](https://e2b.dev),提供隔离的远程代码执行环境
13
+ - **云端沙箱** - 集成 [E2B](https://e2b.dev) 与 OpenSandbox,提供隔离的远程代码执行环境
14
14
  - **可扩展生态** - MCP 工具、自定义 Provider、Skills 系统
15
15
 
16
16
  ## 快速开始
@@ -79,6 +79,15 @@ npm run example:getting-started # 最简对话
79
79
  npm run example:agent-inbox # 事件驱动收件箱
80
80
  npm run example:approval # 工具审批流程
81
81
  npm run example:room # 多Agent协作
82
+ npm run example:opensandbox # OpenSandbox 基础使用
83
+ ```
84
+
85
+ OpenSandbox 快速配置:
86
+
87
+ ```bash
88
+ export OPEN_SANDBOX_API_KEY=... # 可选(仅在服务开启鉴权时需要)
89
+ export OPEN_SANDBOX_ENDPOINT=http://127.0.0.1:8080 # 可选
90
+ export OPEN_SANDBOX_IMAGE=ubuntu # 可选
82
91
  ```
83
92
 
84
93
  ## 支持的 Provider
@@ -102,9 +111,14 @@ npm run example:room # 多Agent协作
102
111
  | **使用指南** | |
103
112
  | [事件系统](./docs/zh-CN/guides/events.md) | 三通道事件系统 |
104
113
  | [工具系统](./docs/zh-CN/guides/tools.md) | 内置工具与自定义工具 |
114
+ | [E2B 沙箱](./docs/zh-CN/guides/e2b-sandbox.md) | E2B 云端沙箱接入 |
115
+ | [OpenSandbox 沙箱](./docs/zh-CN/guides/opensandbox-sandbox.md) | OpenSandbox 自托管沙箱接入 |
116
+ | [Skills 系统](./docs/zh-CN/guides/skills.md) | Skills 可复用提示词系统 |
105
117
  | [Provider 配置](./docs/zh-CN/guides/providers.md) | 模型 Provider 配置 |
106
118
  | [数据库存储](./docs/zh-CN/guides/database.md) | SQLite/PostgreSQL 持久化 |
107
119
  | [恢复与分叉](./docs/zh-CN/guides/resume-fork.md) | 崩溃恢复与分支 |
120
+ | **项目** | |
121
+ | [贡献指南](./docs/zh-CN/contribution.md) | 提交 PR 的要求与流程 |
108
122
  | **参考** | |
109
123
  | [API 参考](./docs/zh-CN/reference/api.md) | 完整 API 文档 |
110
124
  | [示例集](./docs/zh-CN/examples/playbooks.md) | 所有示例详解 |
@@ -9,6 +9,7 @@ import { SandboxFactory } from '../infra/sandbox-factory';
9
9
  import { ModelProvider, ModelConfig } from '../infra/provider';
10
10
  import { ToolRegistry, ToolInstance, ToolDescriptor } from '../tools/registry';
11
11
  import { ContextManagerOptions } from './context-manager';
12
+ import { ResumeError } from './errors';
12
13
  import { SendOptions as QueueSendOptions } from './agent/message-queue';
13
14
  export interface ModelFactory {
14
15
  (config: ModelConfig): ModelProvider;
@@ -180,6 +181,12 @@ export declare class Agent {
180
181
  strategy?: ResumeStrategy;
181
182
  overrides?: Partial<AgentConfig>;
182
183
  }): Promise<Agent>;
184
+ static resumeOrCreate(agentId: string, config: AgentConfig, deps: AgentDependencies, opts?: {
185
+ autoRun?: boolean;
186
+ strategy?: ResumeStrategy;
187
+ overrides?: Partial<AgentConfig>;
188
+ onCorrupted?: (agentId: string, error: ResumeError) => Promise<void> | void;
189
+ }): Promise<Agent>;
183
190
  private ensureProcessing;
184
191
  private runStep;
185
192
  private executeTools;
@@ -195,12 +195,22 @@ class Agent {
195
195
  config.agentId = Agent.generateAgentId();
196
196
  }
197
197
  const template = deps.templateRegistry.get(config.templateId);
198
- const sandboxConfig = config.sandbox && 'kind' in config.sandbox
198
+ const sandboxConfigSource = config.sandbox && 'kind' in config.sandbox
199
199
  ? config.sandbox
200
200
  : template.sandbox;
201
+ const sandboxConfig = sandboxConfigSource
202
+ ? { ...sandboxConfigSource }
203
+ : undefined;
201
204
  const sandbox = typeof config.sandbox === 'object' && 'exec' in config.sandbox
202
205
  ? config.sandbox
203
- : deps.sandboxFactory.create(sandboxConfig || { kind: 'local', workDir: process.cwd() });
206
+ : await deps.sandboxFactory.createAsync(sandboxConfig || { kind: 'local', workDir: process.cwd() });
207
+ // OpenSandbox creates sandbox id at runtime; persist it for strict resume.
208
+ if (sandboxConfig?.kind === 'opensandbox' && typeof sandbox.getSandboxId === 'function') {
209
+ const sandboxId = sandbox.getSandboxId();
210
+ if (sandboxId) {
211
+ sandboxConfig.sandboxId = sandboxId;
212
+ }
213
+ }
204
214
  const model = config.model
205
215
  ? config.model
206
216
  : config.modelConfig
@@ -541,7 +551,7 @@ class Agent {
541
551
  }
542
552
  let sandbox;
543
553
  try {
544
- sandbox = deps.sandboxFactory.create(metadata.sandboxConfig || { kind: 'local', workDir: process.cwd() });
554
+ sandbox = await deps.sandboxFactory.createAsync(metadata.sandboxConfig || { kind: 'local', workDir: process.cwd() });
545
555
  }
546
556
  catch (error) {
547
557
  throw new errors_1.ResumeError('SANDBOX_INIT_FAILED', error?.message || 'Failed to create sandbox');
@@ -653,6 +663,30 @@ class Agent {
653
663
  const overrides = opts?.overrides ?? {};
654
664
  return Agent.resume(agentId, { ...baseConfig, ...overrides }, deps, opts);
655
665
  }
666
+ static async resumeOrCreate(agentId, config, deps, opts) {
667
+ try {
668
+ return await Agent.resumeFromStore(agentId, deps, opts);
669
+ }
670
+ catch (error) {
671
+ if (!(error instanceof errors_1.ResumeError)) {
672
+ throw error;
673
+ }
674
+ switch (error.code) {
675
+ case 'AGENT_NOT_FOUND':
676
+ return Agent.create({ ...config, agentId }, deps);
677
+ case 'CORRUPTED_DATA': {
678
+ if (opts?.onCorrupted) {
679
+ await opts.onCorrupted(agentId, error);
680
+ }
681
+ const store = Agent.requireStore(deps);
682
+ await store.delete(agentId);
683
+ return Agent.create({ ...config, agentId }, deps);
684
+ }
685
+ default:
686
+ throw error;
687
+ }
688
+ }
689
+ }
656
690
  ensureProcessing() {
657
691
  // 检查是否超时
658
692
  if (this.processingPromise) {
@@ -1231,7 +1265,7 @@ class Agent {
1231
1265
  'application/pdf',
1232
1266
  ];
1233
1267
  for (const block of blocks) {
1234
- if (block.type === 'image' || block.type === 'audio' || block.type === 'file') {
1268
+ if (block.type === 'image' || block.type === 'audio' || block.type === 'video' || block.type === 'file') {
1235
1269
  const url = block.url;
1236
1270
  const fileId = block.file_id;
1237
1271
  const base64 = block.base64;
@@ -1277,12 +1311,81 @@ class Agent {
1277
1311
  }
1278
1312
  async resolveMultimodalBlocks(blocks) {
1279
1313
  const model = this.model;
1280
- if (typeof model.uploadFile !== 'function') {
1281
- return blocks;
1282
- }
1283
- const provider = this.model.toConfig().provider;
1314
+ const config = this.model.toConfig();
1315
+ const provider = config.provider;
1316
+ const multimodal = config.multimodal || {};
1317
+ // Check provider capabilities for audio/video
1318
+ const supportsAudio = provider === 'gemini' || provider === 'openai';
1319
+ const supportsVideo = provider === 'gemini';
1284
1320
  const resolved = [];
1285
1321
  for (const block of blocks) {
1322
+ // Handle audio block with callback fallback
1323
+ if (block.type === 'audio') {
1324
+ if (!supportsAudio && multimodal.audio?.customTranscriber) {
1325
+ try {
1326
+ const transcript = await multimodal.audio.customTranscriber({
1327
+ base64: block.base64,
1328
+ url: block.url,
1329
+ mimeType: block.mime_type,
1330
+ });
1331
+ resolved.push({ type: 'text', text: `[Audio Transcript]: ${transcript}` });
1332
+ continue;
1333
+ }
1334
+ catch (error) {
1335
+ this.events.emitMonitor({
1336
+ channel: 'monitor',
1337
+ type: 'error',
1338
+ severity: 'warn',
1339
+ phase: 'system',
1340
+ message: 'audio transcription failed, falling back to placeholder',
1341
+ detail: { error: error?.message || String(error) },
1342
+ });
1343
+ }
1344
+ }
1345
+ // If supported or no callback, pass through (provider will handle or degrade)
1346
+ resolved.push(block);
1347
+ continue;
1348
+ }
1349
+ // Handle video block with callback fallback
1350
+ if (block.type === 'video') {
1351
+ if (!supportsVideo && multimodal.video?.customFrameExtractor) {
1352
+ try {
1353
+ const frames = await multimodal.video.customFrameExtractor({
1354
+ base64: block.base64,
1355
+ url: block.url,
1356
+ mimeType: block.mime_type,
1357
+ });
1358
+ // Add all extracted frames as image blocks
1359
+ for (const frame of frames) {
1360
+ resolved.push({
1361
+ type: 'image',
1362
+ base64: frame.base64,
1363
+ mime_type: frame.mimeType,
1364
+ });
1365
+ }
1366
+ resolved.push({ type: 'text', text: `[Video: ${frames.length} frames extracted]` });
1367
+ continue;
1368
+ }
1369
+ catch (error) {
1370
+ this.events.emitMonitor({
1371
+ channel: 'monitor',
1372
+ type: 'error',
1373
+ severity: 'warn',
1374
+ phase: 'system',
1375
+ message: 'video frame extraction failed, falling back to placeholder',
1376
+ detail: { error: error?.message || String(error) },
1377
+ });
1378
+ }
1379
+ }
1380
+ // If supported or no callback, pass through (provider will handle or degrade)
1381
+ resolved.push(block);
1382
+ continue;
1383
+ }
1384
+ // Handle image and file uploads (existing logic)
1385
+ if (typeof model.uploadFile !== 'function') {
1386
+ resolved.push(block);
1387
+ continue;
1388
+ }
1286
1389
  if ((block.type === 'image' || block.type === 'file') &&
1287
1390
  block.base64 &&
1288
1391
  block.mime_type &&
@@ -1,120 +1,144 @@
1
1
  /**
2
- * 技能管理器模块(路径1 - 技能管理)
2
+ * 技能管理器模块(路径1 - 技能管理)
3
3
  *
4
4
  * 设计原则 (UNIX哲学):
5
- * - 简洁: 只负责技能文件系统的CRUD操作
6
- * - 模块化: 协调OperationQueue、SandboxFileManager进行文件系统操作
7
- * - 隔离: 与Agent运行时完全隔离,不参与Agent使用
5
+ * - 简洁: 只负责技能文件系统的管理操作
6
+ * - 模块化: 单一职责,易于测试和维护
7
+ * - 隔离: 与Agent运行时完全隔离,不参与Agent使用
8
8
  *
9
9
  * ⚠️ 重要说明:
10
- * - 此模块专门用于路径1(技能管理)
11
- * - 与路径2Agent运行时)完全独立
10
+ * - 此模块专门用于路径1(技能管理)
11
+ * - 与路径2(Agent运行时)完全独立
12
12
  * - 请勿与SkillsManager混淆
13
+ *
14
+ * @see docs/skills-management-implementation-plan.md
13
15
  */
14
- import { OperationTask } from './operation-queue';
15
- import type { SkillInfo, SkillDetail, SkillFileTree, CreateSkillOptions, ArchivedSkillInfo } from './types';
16
- import { SandboxFactory } from '../../infra/sandbox-factory';
16
+ import type { SkillInfo, ArchivedSkillInfo } from './types';
17
17
  /**
18
18
  * 技能管理器类
19
19
  *
20
20
  * 职责:
21
- * - 提供所有技能管理操作的统一接口(CRUD操作)
22
- * - 协调OperationQueue、SandboxFileManager进行文件系统操作
21
+ * - 提供所有技能管理操作的统一接口(导入、复制、重命名、归档、导出)
23
22
  * - 处理业务逻辑和权限验证
23
+ * - 所有操作严格遵循Specification.md规范
24
24
  * - ❌ 不参与Agent运行时
25
25
  * - ❌ 不提供技能加载、扫描等Agent使用的功能
26
26
  */
27
27
  export declare class SkillsManagementManager {
28
- private skillsManager;
29
- private operationQueue;
30
- private sandboxFileManager;
31
28
  private skillsDir;
32
29
  private archivedDir;
33
- constructor(skillsDir: string, sandboxFactory?: SandboxFactory, archivedDir?: string);
30
+ constructor(skillsDir: string, archivedDir?: string);
34
31
  /**
35
- * 获取所有在线技能列表(不包含archived技能)
32
+ * 1. 列出在线技能
33
+ * 扫描skills目录,排除.archived子目录
34
+ * 返回技能清单及其元数据信息
36
35
  */
37
36
  listSkills(): Promise<SkillInfo[]>;
38
37
  /**
39
- * 获取单个在线技能详细信息
40
- * @param skillName 技能名称
38
+ * 2. 安装新技能
39
+ * @param source 技能来源(名称/GitHub仓库/Git URL/本地路径)
40
+ * @param onProgress 可选的进度回调函数,用于实时传递安装日志
41
+ * 执行命令: npx -y ai-agent-skills install --agent project [source]
42
+ * 直接安装到.skills目录
41
43
  */
42
- getSkillInfo(skillName: string): Promise<SkillDetail | null>;
44
+ installSkill(source: string, onProgress?: (data: {
45
+ type: 'log' | 'error';
46
+ message: string;
47
+ }) => void): Promise<void>;
43
48
  /**
44
- * 获取已归档技能列表(只读,不支持修改)
49
+ * 3. 列出归档技能
50
+ * 扫描.archived目录
51
+ * 返回归档技能清单及其元数据信息
45
52
  */
46
53
  listArchivedSkills(): Promise<ArchivedSkillInfo[]>;
47
54
  /**
48
- * 创建新技能
55
+ * 4. 导入技能
56
+ * @param zipFilePath zip文件路径
57
+ * @param originalFileName 原始上传文件名(可选,用于无嵌套目录时的技能命名)
58
+ * 验证SKILL.md格式,解压并放置在在线技能目录中
59
+ *
60
+ * 检测逻辑:
61
+ * - 如果解压后根目录直接包含SKILL.md,视为无嵌套目录,使用originalFileName作为技能名称
62
+ * - 如果根目录不包含SKILL.md但包含多个子目录,每个子目录都有SKILL.md,则批量导入
63
+ */
64
+ importSkill(zipFilePath: string, originalFileName?: string): Promise<void>;
65
+ /**
66
+ * 5. 复制技能
49
67
  * @param skillName 技能名称
50
- * @param options 技能配置(名称、描述等)
68
+ * 新技能名称: {原技能名称}-{XXXXXXXX}
51
69
  */
52
- createSkill(skillName: string, options: CreateSkillOptions): Promise<SkillDetail>;
70
+ copySkill(skillName: string): Promise<string>;
53
71
  /**
54
- * 重命名技能
55
- * @param oldName 旧技能名称
56
- * @param newName 新技能名称
72
+ * 6. 重命名技能
73
+ * @param oldName 旧技能文件夹名称
74
+ * @param newName 新技能文件夹名称
75
+ * 不支持操作归档技能
57
76
  */
58
77
  renameSkill(oldName: string, newName: string): Promise<void>;
59
78
  /**
60
- * 编辑技能文件
79
+ * 7. 在线技能转归档
61
80
  * @param skillName 技能名称
62
- * @param filePath 文件路径(相对于技能根目录,如"SKILL.md")
63
- * @param content 文件内容
64
- * @param useSandbox 是否使用sandbox(默认true)
81
+ * 归档名称: {原技能名称}-{XXXXXXXX}
82
+ */
83
+ archiveSkill(skillName: string): Promise<void>;
84
+ /**
85
+ * 8. 归档技能转在线
86
+ * @param archivedSkillName archived中的技能名称(含后缀)
87
+ * 移入前检测重名
65
88
  */
66
- editSkillFile(skillName: string, filePath: string, content: string, useSandbox?: boolean): Promise<void>;
89
+ unarchiveSkill(archivedSkillName: string): Promise<void>;
67
90
  /**
68
- * 删除技能(移动到archived)
91
+ * 9. 查看在线技能内容
69
92
  * @param skillName 技能名称
93
+ * 返回SKILL.md完整内容(包含frontmatter和正文)
70
94
  */
71
- deleteSkill(skillName: string): Promise<void>;
95
+ getOnlineSkillContent(skillName: string): Promise<string>;
72
96
  /**
73
- * 恢复已删除的技能
74
- * @param archivedSkillName archived中的技能名称(含时间戳)
97
+ * 10. 查看归档技能内容
98
+ * @param archivedSkillName 归档技能名称(含8位后缀)
99
+ * 返回SKILL.md完整内容(包含frontmatter和正文)
75
100
  */
76
- restoreSkill(archivedSkillName: string): Promise<void>;
101
+ getArchivedSkillContent(archivedSkillName: string): Promise<string>;
77
102
  /**
78
- * 获取技能文件树(仅在线技能)
103
+ * 11. 查看在线技能文件目录结构
79
104
  * @param skillName 技能名称
105
+ * 返回JSON格式的目录树结构
80
106
  */
81
- getSkillFileTree(skillName: string): Promise<SkillFileTree>;
107
+ getOnlineSkillStructure(skillName: string): Promise<object>;
82
108
  /**
83
- * 获取队列状态
109
+ * 12. 查看归档技能文件目录结构
110
+ * @param archivedSkillName 归档技能名称(含8位后缀)
111
+ * 返回JSON格式的目录树结构
84
112
  */
85
- getQueueStatus(): {
86
- length: number;
87
- processing: boolean;
88
- tasks: OperationTask[];
89
- };
113
+ getArchivedSkillStructure(archivedSkillName: string): Promise<object>;
90
114
  /**
91
- * 执行创建技能
115
+ * 13. 导出技能
116
+ * @param skillName 技能名称(在线或归档)
117
+ * @param isArchived 是否为归档技能
118
+ * 使用系统zip命令打包,放入临时目录
92
119
  */
93
- private doCreateSkill;
120
+ exportSkill(skillName: string, isArchived: boolean): Promise<string>;
94
121
  /**
95
- * 执行重命名技能
122
+ * 递归构建目录树
96
123
  */
97
- private doRenameSkill;
124
+ private buildDirectoryTree;
98
125
  /**
99
- * 执行编辑技能文件
126
+ * 解析SKILL.md的YAML frontmatter
100
127
  */
101
- private doEditSkillFile;
128
+ private parseSkillMd;
102
129
  /**
103
- * 执行删除技能
130
+ * 验证SKILL.md格式(遵循Specification.md规范)
104
131
  */
105
- private doDeleteSkill;
132
+ private validateSkillMd;
106
133
  /**
107
- * 执行恢复技能
134
+ * 生成8位随机后缀
135
+ * 规则: uuidv4 → sha256 → 全小写 → 取前8位
108
136
  */
109
- private doRestoreSkill;
137
+ private generateRandomSuffix;
110
138
  /**
111
139
  * 验证技能名称
112
140
  */
113
141
  private isValidSkillName;
114
- /**
115
- * 生成SKILL.md内容
116
- */
117
- private generateSkillMd;
118
142
  /**
119
143
  * 检查文件是否存在
120
144
  */
@@ -124,7 +148,16 @@ export declare class SkillsManagementManager {
124
148
  */
125
149
  private safeGetFileStat;
126
150
  /**
127
- * 等待任务完成
151
+ * 解压zip文件
152
+ */
153
+ private extractZip;
154
+ /**
155
+ * 跨平台的安全重命名方法
156
+ * Windows上使用"复制-删除"方式避免EPERM错误
157
+ */
158
+ private safeRename;
159
+ /**
160
+ * 创建zip文件
128
161
  */
129
- private waitForTask;
162
+ private createZip;
130
163
  }